diff --git a/src/components/layout/header/index.tsx b/src/components/layout/header/index.tsx index f0bd02e..b68ea7b 100644 --- a/src/components/layout/header/index.tsx +++ b/src/components/layout/header/index.tsx @@ -48,10 +48,10 @@ const Header = (headerProps: ComponentProps<'header'>) => { alt='Tech Tracker Logo' width={100} height={100} - className='max-w-[40px] md:max-w-[120px]' + className='w-10 md:w-[120px]' />

{ const [pageIndex, setPageIndex] = useState(0); - // cursor for page N is the continueCursor returned from page N-1 const [cursors, setCursors] = useState<(string | null)[]>([null]); const args = useMemo(() => { @@ -33,17 +33,16 @@ export const HistoryTable = () => { }, [cursors, pageIndex]); const data = useQuery(api.statuses.listHistory, args); - - // Track loading const isLoading = data === undefined; - // When a page loads, cache its "next" cursor if we don't have it yet useEffect(() => { if (!data) return; const nextIndex = pageIndex + 1; setCursors((prev) => { const copy = [...prev]; - if (copy[nextIndex] === undefined) copy[nextIndex] = data.continueCursor; + if (copy[nextIndex] === undefined) { + copy[nextIndex] = data.continueCursor; + } return copy; }); }, [data, pageIndex]); @@ -62,13 +61,84 @@ export const HistoryTable = () => { }; const rows = data?.page ?? []; + return ( -
-
- +
+ {/* Mobile: card list */} +
+ {isLoading ? (
-
+
+
+ ) : rows.length === 0 ? ( +
+

No history found

+
+ ) : ( +
+ {rows.map((r, idx) => { + const key = `${r.status?.id ?? 'no-status'}-${idx}`; + const name = r.user.name ?? 'Technician'; + const msg = r.status?.message ?? 'No status'; + const updatedBy = r.status?.updatedBy?.name ?? null; + const stamp = r.status + ? `${formatTime(r.status.updatedAt)} · ${formatDate( + r.status.updatedAt, + )}` + : '--:-- · --/--'; + + return ( +
+
+
+
{name}
+
+ {msg} +
+
+ {updatedBy && ( + + {updatedBy} + + )} +
+ +
+ {stamp} +
+
+ ); + })} +
+ )} + +
+ + {/* Desktop: original table */} +
+ + {isLoading ? ( +
+
) : rows.length === 0 ? (
@@ -115,32 +185,38 @@ export const HistoryTable = () => {
- - - { - e.preventDefault(); - handlePrev(); - }} - aria-disabled={!canPrev} - className={!canPrev ? 'pointer-events-none opacity-50' : ''} - /> -
- Page - {pageIndex + 1} -
- { - e.preventDefault(); - handleNext(); - }} - aria-disabled={!canNext} - className={!canNext ? 'pointer-events-none opacity-50' : ''} - /> -
-
+ {/* Pagination */} +
+ + + { + e.preventDefault(); + handlePrev(); + }} + aria-disabled={!canPrev} + className={!canPrev ? 'pointer-events-none opacity-50' : ''} + /> +
+ Page + {pageIndex + 1} +
+ { + e.preventDefault(); + handleNext(); + }} + aria-disabled={!canNext} + className={!canNext ? 'pointer-events-none opacity-50' : ''} + /> +
+
+
); }; diff --git a/src/components/layout/status/list/index.tsx b/src/components/layout/status/list/index.tsx index b5a95d0..1ba137f 100644 --- a/src/components/layout/status/list/index.tsx +++ b/src/components/layout/status/list/index.tsx @@ -44,8 +44,8 @@ export const StatusList = ({ }: StatusListProps) => { const user = usePreloadedQuery(preloadedUser); const statuses = usePreloadedQuery(preloadedStatuses); - const { tvMode } = useTVMode(); + const [selectedUserIds, setSelectedUserIds] = useState[]>([]); const [selectAll, setSelectAll] = useState(false); const [statusInput, setStatusInput] = useState(''); @@ -59,17 +59,20 @@ export const StatusList = ({ const newAnimatingIds = new Set(); statuses.forEach((curr) => { const previous = previousStatuses.find((p) => p.user.id === curr.user.id); - if (previous?.status?.updatedAt !== curr.status?.updatedAt) + if (previous?.status?.updatedAt !== curr.status?.updatedAt) { newAnimatingIds.add(curr.user.id); + } }); if (newAnimatingIds.size > 0) { setAnimatingIds(newAnimatingIds); setTimeout(() => setAnimatingIds(new Set()), 800); } setPreviousStatuses( - statuses.sort( - (a, b) => (b.status?.updatedAt ?? 0) - (a.status?.updatedAt ?? 0), - ), + statuses + .slice() + .sort( + (a, b) => (b.status?.updatedAt ?? 0) - (a.status?.updatedAt ?? 0), + ), ); }, [statuses]); @@ -91,11 +94,14 @@ export const StatusList = ({ const message = statusInput.trim(); setUpdatingStatus(true); try { - if (message.length < 3 || message.length > 80) + if (message.length < 3 || message.length > 80) { throw new Error('Status must be between 3 & 80 characters'); - if (selectedUserIds.length === 0 && user?.id) + } + if (selectedUserIds.length === 0 && user?.id) { await bulkCreate({ message, userIds: [user.id] }); - else await bulkCreate({ message, userIds: selectedUserIds }); + } else { + await bulkCreate({ message, userIds: selectedUserIds }); + } toast.success('Status updated.'); setSelectedUserIds([]); setSelectAll(false); @@ -118,14 +124,14 @@ export const StatusList = ({ const containerCn = ccn({ context: tvMode, - className: 'w-full max-w-4xl mx-auto', - on: 'px-12 max-w-3xl', - off: 'px-6', + className: 'max-w-4xl mx-auto', + on: 'px-6', + off: 'px-4 sm:px-6', }); const tabsCn = ccn({ context: tvMode, - className: 'w-full py-8', + className: 'w-full py-4 sm:py-8', on: 'hidden', off: '', }); @@ -134,31 +140,54 @@ export const StatusList = ({ context: tvMode, className: 'w-full mb-2', on: 'hidden', - off: 'flex justify-end items-center', + off: 'hidden sm:flex justify-end items-center', }); return (
- -
- -

Team Status

+ +
+ +

Team Status

- -
- -

Status History

+ +
+ +

+ Status History +

+ + {/* Mobile toolbar */} +
+
+ + {statuses.length} members +
+
+ + + Table + +
+
+ + {/* Desktop header */}
-
+
- + {statuses.length} members
@@ -168,83 +197,78 @@ export const StatusList = ({
-
+ + {/* Card list */} +
{statuses.map((statusData) => { const { user: u, status: s } = statusData; const isSelected = selectedUserIds.includes(u.id); const isAnimating = animatingIds.has(u.id); const isUpdatedByOther = s?.updatedBy?.id !== u.id; + return (
handleSelectUser(u.id) : undefined} + role='button' + aria-pressed={isSelected} > - {/* Selection indicator */} {isSelected && !tvMode && (
)} - {/* Enhanced animation effect */} - {isAnimating && ( -
- )} - -
+
{/* Avatar */} -
+
- {/* Main Content */} + {/* Content */}
-
+

{u.name ?? u.email ?? 'User'}

{isUpdatedByOther && s?.updatedBy && ( -
- - via - +
+ via - + {s.updatedBy.name ?? s.updatedBy.email ?? 'another user'} @@ -255,36 +279,41 @@ export const StatusList = ({
{s?.message ?? 'No status yet.'}
- {/* Time Info */} -
-
- - + {/* Meta */} +
+
+ + {s ? formatTime(s.updatedAt) : '--:--'}
-
- - +
+ + {s ? formatDate(s.updatedAt) : '--/--'}
{s && ( -
- - +
+ + {getStatusAge(s.updatedAt)}
@@ -292,43 +321,72 @@ export const StatusList = ({
- {/* History Drawer */} + {/* Actions */} {!tvMode && ( - - - - - - +
+ + + + + + +
)}
+ + {/* Mobile "via user" line */} + {isUpdatedByOther && s?.updatedBy && ( +
+ via + + + {s.updatedBy.name ?? + s.updatedBy.email ?? + 'another user'} + +
+ )}
); })}
- {/* Update Status Section */} + + {/* Desktop composer */} {!tvMode && ( - +

Update Status

{selectedUserIds.length > 0 && ( - + {selectedUserIds.length} selected )}
-
{selectedUserIds.length > 0 - ? `Update ${selectedUserIds.length} ${selectedUserIds.length > 1 ? 'users' : 'user'}` + ? `Update ${selectedUserIds.length} ${ + selectedUserIds.length > 1 ? 'users' : 'user' + }` : 'Update Status'}
-
+
+
+ setStatusInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.shiftKey && !updatingStatus) { + e.preventDefault(); + void handleUpdateStatus(); + } + }} + /> + + Update + +
+
+ )} +