Ghassan Yusuf 84cd474bcb Rebuild desktop shell on the real AdminLTE 4 package
Install admin-lte@4.1.0 (+ bootstrap 5.3.8, bootstrap-icons as peer deps) and
rebuild every desktop component against actual AdminLTE markup pulled from
its live docs/demo, rather than a Tailwind approximation:

- Real app-wrapper/app-header/app-sidebar/app-main/app-footer structure
- Working PushMenu (sidebar collapse), Treeview, ColorMode (light/dark/auto)
- Navbar: functional search (wired to Users list ?search=), notifications
  dropdown (MQTT-driven), user menu dropdown
- Sidebar: grouped nav-header sections, active-route highlighting
- Breadcrumbs, footer, Bootstrap card/table/pagination/progress widgets

Tailwind is now imported utilities-only (no preflight) so it coexists with
Bootstrap's reset; mobile shell and modals are untouched and still Tailwind.
2026-08-04 13:47:02 +03:00

112 lines
3.2 KiB
TypeScript

import type { ReactNode } from 'react'
import { Card } from '../desktop/Card'
export interface DataTableColumn<T> {
key: string
label: string
sortable?: boolean
render: (row: T) => ReactNode
}
interface DataTableProps<T> {
columns: DataTableColumn<T>[]
rows: T[]
rowKey: (row: T) => string
sort?: string
direction?: 'asc' | 'desc'
onSortChange?: (key: string) => void
page: number
lastPage: number
onPageChange: (page: number) => void
isLoading?: boolean
}
export function DataTable<T>({
columns,
rows,
rowKey,
sort,
direction,
onSortChange,
page,
lastPage,
onPageChange,
isLoading,
}: DataTableProps<T>) {
const pages = Array.from({ length: lastPage }, (_, i) => i + 1)
return (
<Card>
<div className="table-responsive">
<table className="table table-striped table-hover align-middle mb-0">
<thead>
<tr>
{columns.map((col) => (
<th key={col.key}>
{col.sortable ? (
<button
onClick={() => onSortChange?.(col.key)}
className="btn btn-link p-0 text-decoration-none fw-semibold"
>
{col.label} {sort === col.key && <i className={`bi bi-caret-${direction === 'asc' ? 'up' : 'down'}-fill`}></i>}
</button>
) : (
col.label
)}
</th>
))}
</tr>
</thead>
<tbody>
{isLoading ? (
<tr>
<td colSpan={columns.length} className="text-center text-secondary py-4">
Loading
</td>
</tr>
) : rows.length === 0 ? (
<tr>
<td colSpan={columns.length} className="text-center text-secondary py-4">
No results.
</td>
</tr>
) : (
rows.map((row) => (
<tr key={rowKey(row)}>
{columns.map((col) => (
<td key={col.key}>{col.render(row)}</td>
))}
</tr>
))
)}
</tbody>
</table>
</div>
{lastPage > 1 && (
<nav className="mt-3" aria-label="Table pagination">
<ul className="pagination pagination-sm justify-content-end mb-0">
<li className={`page-item ${page <= 1 ? 'disabled' : ''}`}>
<button className="page-link" onClick={() => onPageChange(page - 1)}>
Previous
</button>
</li>
{pages.map((p) => (
<li key={p} className={`page-item ${p === page ? 'active' : ''}`}>
<button className="page-link" onClick={() => onPageChange(p)}>
{p}
</button>
</li>
))}
<li className={`page-item ${page >= lastPage ? 'disabled' : ''}`}>
<button className="page-link" onClick={() => onPageChange(page + 1)}>
Next
</button>
</li>
</ul>
</nav>
)}
</Card>
)
}