diff --git a/convex/statuses.ts b/convex/statuses.ts index c313f59..0183bb4 100644 --- a/convex/statuses.ts +++ b/convex/statuses.ts @@ -14,23 +14,23 @@ type RWCtx = MutationCtx | QueryCtx; type StatusRow = { user: { - id: Id<'users'>, - name: string | null, - imageId: Id<'_storage'> | null, - imageUrl: string | null, - } + id: Id<'users'>; + name: string | null; + imageId: Id<'_storage'> | null; + imageUrl: string | null; + }; latest: { - id: Id<'statuses'>, - message: string, - updatedAt: number, - updatedBy: Id<'users'>, - } | null, + id: Id<'statuses'>; + message: string; + updatedAt: number; + updatedBy: Id<'users'>; + } | null; updatedByUser: { - id: Id<'users'>, - name: string | null, - imageId: Id<'_storage'> | null, - imageUrl: string | null, - } | null, + id: Id<'users'>; + name: string | null; + imageId: Id<'_storage'> | null; + imageUrl: string | null; + } | null; }; // CHANGED: typed helpers @@ -159,8 +159,7 @@ const getName = (u: Doc<'users'>): string | null => const getImageId = (u: Doc<'users'>): Id<'_storage'> | null => { if (!('image' in u)) return null; - const img = - (u as { image?: unknown }).image as string | undefined; + const img = (u as { image?: unknown }).image as string | undefined; return img && img.length > 0 ? (img as Id<'_storage'>) : null; }; diff --git a/src/components/layout/status/list/index.tsx b/src/components/layout/status/list/index.tsx index 65dd72c..0a7de6d 100644 --- a/src/components/layout/status/list/index.tsx +++ b/src/components/layout/status/list/index.tsx @@ -1,12 +1,7 @@ 'use client'; import Link from 'next/link'; import { useState } from 'react'; -import { - type Preloaded, - usePreloadedQuery, - useMutation, - useQuery, -} from 'convex/react'; +import { type Preloaded, usePreloadedQuery, useMutation } from 'convex/react'; import { api } from '~/convex/_generated/api'; import { type Id } from '~/convex/_generated/dataModel'; import { useTVMode } from '@/components/providers'; @@ -22,7 +17,7 @@ import { } from '@/components/ui'; import { toast } from 'sonner'; import { ccn, formatTime, formatDate } from '@/lib/utils'; -import { RefreshCw, Clock, Calendar, CheckCircle2 } from 'lucide-react'; +import { Clock, Calendar, CheckCircle2 } from 'lucide-react'; type StatusListProps = { preloadedUser: Preloaded; @@ -46,13 +41,15 @@ export const StatusList = ({ const toggleUser = (id: Id<'users'>) => { setSelectedUserIds((prev) => - prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id] + prev.some((i) => i === id) + ? prev.filter((prevId) => prevId !== id) + : [...prev, id], ); - } + }; const handleSelectAllClick = () => { if (selectAll) setSelectedUserIds([]); - else setSelectedUserIds([]); + else setSelectedUserIds(statuses.map((s) => s.user.id)); setSelectAll(!selectAll); }; @@ -61,16 +58,17 @@ export const StatusList = ({ setUpdatingStatus(true); try { if (message.length < 3 || message.length > 80) - throw new Error('Status must be between 3 & 80 characters') - if (selectedUserIds.length === 0) - throw new Error('You must select at least one user.') - await bulkCreate({ message, userIds: selectedUserIds}) - toast.success('Status updated.') + throw new Error('Status must be between 3 & 80 characters'); + if (selectedUserIds.length === 0 && user?.id) + await bulkCreate({ message, userIds: [user.id] }); + else throw new Error("Hmm.. this shouldn't happen"); + await bulkCreate({ message, userIds: selectedUserIds }); + toast.success('Status updated.'); setSelectedUserIds([]); setSelectAll(false); setStatusInput(''); } catch (error) { - toast.error(`Update failed. ${error as Error}`) + toast.error(`Update failed. ${error as Error}`); } finally { setUpdatingStatus(false); } @@ -143,9 +141,11 @@ export const StatusList = ({ className={`relative transition-all duration-200 cursor-pointer hover:shadow-md ${tvMode ? 'p-4' : 'p-3'} - ${isSelected - ? 'ring-2 ring-primary bg-primary/5 shadow-md' - : 'hover:bg-muted/30'} + ${ + isSelected + ? 'ring-2 ring-primary bg-primary/5 shadow-md' + : 'hover:bg-muted/30' + } `} onClick={() => toggleUser(u.id)} > @@ -193,15 +193,14 @@ export const StatusList = ({ -
- + - {latest ? formatTime(latest.updatedAt.toString()) : '--:--'} + {latest ? formatTime(latest.updatedAt) : '--:--'}
@@ -209,7 +208,7 @@ export const StatusList = ({ className={tvMode ? 'w-6 h-6' : 'w-5 h-5'} /> - {latest ? formatDate(latest.updatedAt.toString()) : '--/--'} + {latest ? formatDate(latest.updatedAt) : '--/--'}
@@ -220,9 +219,7 @@ export const StatusList = ({ fullName={updatedByUser.name ?? 'User'} className={tvMode ? 'w-6 h-6' : 'w-5 h-5'} /> - +

Updated by

{updatedByUser.name ?? 'User'} @@ -236,13 +233,15 @@ export const StatusList = ({
- ) + ); })}
{statuses.length === 0 && ( -

+

No status updates have been made in the past day.

@@ -263,11 +262,7 @@ export const StatusList = ({ disabled={updatingStatus} onChange={(e) => setStatusInput(e.target.value)} onKeyDown={(e) => { - if ( - e.key === 'Enter' && - !e.shiftKey && - !updatingStatus - ) { + if (e.key === 'Enter' && !e.shiftKey && !updatingStatus) { e.preventDefault(); handleUpdateStatus(); } @@ -299,7 +294,6 @@ export const StatusList = ({ )} - ); }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6fbb004..fdee135 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -19,18 +19,40 @@ export const ccn = ({ return twMerge(className, context ? on : off); }; -export const formatTime = (timestamp: string) => { - const date = new Date(timestamp); - const time = date.toLocaleTimeString('en-US', { +type Timestamp = number | string | Date; + +const toDate = (ts: Timestamp): Date | null => { + if (ts instanceof Date) return isNaN(ts.getTime()) ? null : ts; + + if (typeof ts === 'number') { + // Heuristic: treat small numbers as seconds + const ms = ts < 1_000_000_000_000 ? ts * 1000 : ts; + const d = new Date(ms); + return isNaN(d.getTime()) ? null : d; + } + + // string: try numeric first, then ISO/date string + const asNum = Number(ts); + const d = + Number.isFinite(asNum) && asNum !== 0 ? toDate(asNum) : new Date(ts); + + return d && !isNaN(d.getTime()) ? d : null; +}; + +export const formatTime = (timestamp: Timestamp, locale = 'en-US'): string => { + const date = toDate(timestamp); + if (!date) return '--:--'; + return date.toLocaleTimeString(locale, { hour: 'numeric', minute: 'numeric', }); - return time; }; -export const formatDate = (timestamp: string) => { - const date = new Date(timestamp); - const day = date.getDate(); - const month = date.toLocaleString('default', { month: 'long' }); - return `${month} ${day}`; +export const formatDate = (timestamp: Timestamp, locale = 'en-US'): string => { + const date = toDate(timestamp); + if (!date) return '--/--'; + return date.toLocaleDateString(locale, { + month: 'long', + day: 'numeric', + }); };