Merge mobile card view into feature/mobile-responsive

This commit is contained in:
Kyle 2026-03-07 17:01:13 +08:00
commit d8f7f7ac92
3 changed files with 66 additions and 0 deletions

View File

@ -356,6 +356,17 @@ export default function LocationsPage() {
sortDir={sortDir} sortDir={sortDir}
onSort={handleSort} onSort={handleSort}
visibilityMode={visibilityMode} visibilityMode={visibilityMode}
mobileCardRender={(location) => (
<div className={`rounded-lg border p-3 transition-colors ${selectedLocationId === location.id ? 'border-accent/40 bg-accent/5' : 'border-border bg-card hover:bg-card-elevated'}`}>
<div className="flex items-center justify-between mb-1">
<span className="font-medium text-sm truncate flex-1">{location.name}</span>
{location.category && <span className="text-[10px] text-muted-foreground">{location.category}</span>}
</div>
{location.address && (
<p className="text-xs text-muted-foreground truncate">{location.address}</p>
)}
</div>
)}
/> />
)} )}
</div> </div>

View File

@ -744,6 +744,18 @@ export default function PeoplePage() {
sortDir={sortDir} sortDir={sortDir}
onSort={handleSort} onSort={handleSort}
visibilityMode={visibilityMode} visibilityMode={visibilityMode}
mobileCardRender={(person) => (
<div className={`rounded-lg border p-3 transition-colors ${selectedPersonId === person.id ? 'border-accent/40 bg-accent/5' : 'border-border bg-card hover:bg-card-elevated'}`}>
<div className="flex items-center justify-between mb-1">
<span className="font-medium text-sm truncate flex-1">{person.name}</span>
{person.category && <span className="text-[10px] text-muted-foreground">{person.category}</span>}
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
{person.email && <span className="truncate">{person.email}</span>}
{person.phone && <span>{person.phone}</span>}
</div>
</div>
)}
/> />
)} )}
</div> </div>

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'; import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
import type { VisibilityMode } from '@/hooks/useTableVisibility'; import type { VisibilityMode } from '@/hooks/useTableVisibility';
import { useMediaQuery } from '@/hooks/useMediaQuery';
export interface ColumnDef<T> { export interface ColumnDef<T> {
key: string; key: string;
@ -28,6 +29,7 @@ interface EntityTableProps<T extends { id: number }> {
onSort: (key: string) => void; onSort: (key: string) => void;
visibilityMode: VisibilityMode; visibilityMode: VisibilityMode;
loading?: boolean; loading?: boolean;
mobileCardRender?: (item: T) => React.ReactNode;
} }
const LEVEL_ORDER: VisibilityMode[] = ['essential', 'filtered', 'all']; const LEVEL_ORDER: VisibilityMode[] = ['essential', 'filtered', 'all'];
@ -127,10 +129,51 @@ export function EntityTable<T extends { id: number }>({
onSort, onSort,
visibilityMode, visibilityMode,
loading = false, loading = false,
mobileCardRender,
}: EntityTableProps<T>) { }: EntityTableProps<T>) {
const visibleColumns = columns.filter((col) => isVisible(col.visibilityLevel, visibilityMode)); const visibleColumns = columns.filter((col) => isVisible(col.visibilityLevel, visibilityMode));
const colCount = visibleColumns.length; const colCount = visibleColumns.length;
const showPinnedSection = showPinned && pinnedRows.length > 0; const showPinnedSection = showPinned && pinnedRows.length > 0;
const isMobile = useMediaQuery('(max-width: 767px)');
if (isMobile && mobileCardRender) {
return (
<div className="space-y-2">
{loading ? (
Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="animate-pulse rounded-lg bg-card border border-border p-4 h-20" />
))
) : (
<>
{showPinnedSection && (
<>
<p className="text-[11px] uppercase tracking-wider text-muted-foreground font-medium pt-2">{pinnedLabel}</p>
{pinnedRows.map((item) => (
<div key={item.id} onClick={() => onRowClick(item.id)} className="cursor-pointer">
{mobileCardRender(item)}
</div>
))}
</>
)}
{groups.map((group) => (
<React.Fragment key={group.label}>
{group.rows.length > 0 && (
<>
<p className="text-[11px] uppercase tracking-wider text-muted-foreground font-medium pt-2">{group.label}</p>
{group.rows.map((item) => (
<div key={item.id} onClick={() => onRowClick(item.id)} className="cursor-pointer">
{mobileCardRender(item)}
</div>
))}
</>
)}
</React.Fragment>
))}
</>
)}
</div>
);
}
return ( return (
<div className="w-full"> <div className="w-full">