import { CssProps, RefProps } from 'lupine.web'; import { NotificationColor, NotificationMessage, ActionSheetSelect, PagingLink, ModalWindow, getDefaultPageLimit, useState, } from 'lupine.components'; import { DemoStory } from '../demo/demo-types'; import { SearchInput } from '../component-pool/search-input'; // ------------------------------------------------------------- // 1. Mock Database & API // ------------------------------------------------------------- export interface MockUserRecord { id: number; name: string; email: string; role: string; status: 'Active' | 'Inactive'; } const db: MockUserRecord[] = Array.from({ length: 45 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: i % 3 === 0 ? 'Admin' : 'User', status: i % 5 === 0 ? 'Inactive' : 'Active', })); // Auto-increment ID counter let nextId = 46; // Simulated API Calls const mockApi = { async getList( searchValue: string = '', pageIndex: number = 0, pageLimit: number = 10 ): Promise<{ status: string; results: MockUserRecord[]; pageIndex: number; count: number }> { await new Promise((resolve) => setTimeout(resolve, 300)); // simulate network delay let filtered = db; if (searchValue) { filtered = db.filter( (item) => item.name.toLowerCase().includes(searchValue.toLowerCase()) || item.email.toLowerCase().includes(searchValue.toLowerCase()) ); } const totalCount = filtered.length; let maxPages = Math.ceil(totalCount / pageLimit); if (maxPages === 0) maxPages = 1; if (pageIndex >= maxPages) { pageIndex = 0; } const start = pageIndex * pageLimit; const end = start + pageLimit; const results = filtered.slice(start, end); return { status: 'ok', results, pageIndex, count: totalCount, }; }, async save(record: MockUserRecord): Promise<{ status: string; id?: number; message?: string }> { await new Promise((resolve) => setTimeout(resolve, 200)); if (!record.name || !record.email) { return { status: 'error', message: 'Name and Email are required.' }; } if (record.id === -1) { // Create record.id = nextId++; db.unshift(record); // Add to top } else { // Update const index = db.findIndex((item) => item.id === record.id); if (index > -1) { db[index] = { ...record }; } } return { status: 'ok', id: record.id }; }, async remove(id: number): Promise<{ status: string; message?: string }> { await new Promise((resolve) => setTimeout(resolve, 200)); const index = db.findIndex((item) => item.id === id); if (index > -1) { db.splice(index, 1); return { status: 'ok' }; } return { status: 'error', message: 'Record not found.' }; }, }; // ------------------------------------------------------------- // 2. Edit Modal Component // ------------------------------------------------------------- type UpdateRecordHookProps = { save?: () => Promise; cancel?: () => void; }; const ContentEditItem = (props: { item: MockUserRecord; hook: UpdateRecordHookProps }) => { const ref: RefProps = {}; const css: CssProps = { padding: '10px 0', '.pg-edit-label': { width: '80px', fontWeight: 'bold', }, }; props.hook.save = async () => { const name = ref.$('input.pg-e-name').value; const email = ref.$('input.pg-e-email').value; const role = ref.$('select.pg-e-role').value; const status = ref.$('select.pg-e-status').value as 'Active' | 'Inactive'; const newItem = { ...props.item, name, email, role, status, }; const res = await mockApi.save(newItem); if (res.status !== 'ok') { NotificationMessage.sendMessage(res.message || 'Error saving record', NotificationColor.Error); return null; } newItem.id = res.id!; return newItem; }; return (
Name:
Email:
Role:
Status:
); }; const showEditModal = async (item: MockUserRecord, onUpdate: (item: MockUserRecord) => void) => { const updateFn: UpdateRecordHookProps = {}; const handleClicked = async (index: number) => { if (index === 1) { // Cancel updateFn.cancel?.(); modalClose(); } if (index === 0) { // Save const newItem = await updateFn.save?.(); if (newItem) { onUpdate(newItem); modalClose(); } } }; const modalClose = await ModalWindow.show({ title: item.id === -1 ? 'Add New User' : 'Edit User', buttons: ['Save', 'Cancel'], handleClicked, children: , closeWhenClickOutside: false, }); }; // ------------------------------------------------------------- // 3. Row Component // ------------------------------------------------------------- const ContentOneRow = (props: { item: MockUserRecord; onRefreshList: () => void; // call parent to refresh when delete }) => { const ref: RefProps = {}; const makeDom = (item: MockUserRecord) => { const onEdit = () => { const update = async (updatedItem: MockUserRecord) => { // re-render just this row entirely await ref.mountInnerComponent!(makeDom(updatedItem)); NotificationMessage.sendMessage('Record saved successfully.', NotificationColor.Success); }; showEditModal(item, update); }; const onRemove = async () => { await ActionSheetSelect.show({ title: `Are you sure you want to delete ${item.name}?`, options: ['Delete'], cancelButtonText: 'Cancel', handleClicked: async (idx: number, close: any) => { close(); if (idx === 0) { const res = await mockApi.remove(item.id); if (res.status === 'ok') { NotificationMessage.sendMessage('Record deleted.', NotificationColor.Success); // Trigger a full list refresh from the parent props.onRefreshList(); } else { NotificationMessage.sendMessage(res.message || 'Error deleting.', NotificationColor.Error); } } }, }); }; return ( <> {item.id} {item.name} {item.email} {item.role} {item.status} ); }; return {makeDom(props.item)}; }; // ------------------------------------------------------------- // 4. Main Page Component // ------------------------------------------------------------- type PageListState = { searchValue: string; pageIndex: number; data: Awaited> | null; }; export const PagingLinkPage = () => { const [state, setState] = useState({ searchValue: '', pageIndex: 0, data: null }); const ref: RefProps = { onLoad: async () => { await loadAndSet('', 0); }, }; const css: CssProps = { display: 'flex', flexDirection: 'column', '.pg-main-tbl': { width: '100%', borderCollapse: 'collapse', marginTop: '10px', marginBottom: '10px', }, '.pg-main-tbl th, .pg-main-tbl td': { border: '1px solid var(--primary-border, #eee)', padding: '8px', textAlign: 'left', }, '.pg-main-tbl th': { backgroundColor: 'var(--secondary-bg-color, #f9f9f9)', }, '.pg-main-tbl tr:nth-child(odd)': { backgroundColor: 'var(--secondary-bg-color, #f9f9f9)', }, '.pg-main-tbl tr:hover': { backgroundColor: 'var(--info-bg-color, #e6f7ff)', }, '.pg-lst-row-ctl i': { cursor: 'pointer', }, }; const loadAndSet = async (search: string, page: number) => { const pageLimit = getDefaultPageLimit(); const result = await mockApi.getList(search, page, pageLimit); setState({ searchValue: search, pageIndex: result.pageIndex, data: result }); }; const onSearch = async (search?: string) => { await loadAndSet((search || '').trim(), 0); }; const onPageClick = async (index: number) => { await loadAndSet(state.searchValue, index); }; const onAdd = async () => { const newItem: MockUserRecord = { id: -1, name: '', email: '', role: 'User', status: 'Active' }; showEditModal(newItem, async () => { await loadAndSet(state.searchValue, state.pageIndex); NotificationMessage.sendMessage('User added successfully.', NotificationColor.Success); }); }; const refreshList = async () => { await loadAndSet(state.searchValue, state.pageIndex); }; return (
{state.data && ( <> {state.data.results.length === 0 ? (
No results found.
) : (
{state.data.results.map((item) => ( ))}
ID Name Email Role Status Actions
)} )}
); }; // ------------------------------------------------------------- // 5. Demo Export // ------------------------------------------------------------- export const pagingLinkDemo: DemoStory = { id: 'paging-link-demo', text: 'Data List & Paging', args: {}, argTypes: {}, render: () => { return ; }, };