Fix mobile density issues from S24 Ultra testing
- Page titles: text-xl on mobile, text-2xl on desktop (7 pages) - Stat cards: reduce padding/gap on mobile, hide icons below sm (3 pages) - TodoItem: two-line layout on mobile (title row + metadata row) - ReminderItem: same two-line treatment - FullCalendar: smaller event font/padding on mobile via CSS media query Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0b84352b09
commit
ec8f5a9b4e
@ -23,7 +23,7 @@ export default function AdminPortal() {
|
||||
<div className="p-1.5 rounded-md bg-red-500/10">
|
||||
<ShieldCheck className="h-5 w-5 text-red-400" />
|
||||
</div>
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight">Admin Portal</h1>
|
||||
<h1 className="font-heading text-xl md:text-2xl font-bold tracking-tight">Admin Portal</h1>
|
||||
</div>
|
||||
|
||||
{/* Horizontal tab navigation */}
|
||||
|
||||
@ -286,7 +286,7 @@ export default function LocationsPage() {
|
||||
<div className="flex flex-col h-full animate-fade-in">
|
||||
{/* Header */}
|
||||
<div className="border-b bg-card px-4 md:px-6 min-h-[4rem] flex items-center gap-4 flex-wrap py-2 md:py-0 md:h-16 md:flex-nowrap shrink-0">
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight">Locations</h1>
|
||||
<h1 className="font-heading text-xl md:text-2xl font-bold tracking-tight">Locations</h1>
|
||||
|
||||
<div className="w-full md:flex-1 md:w-auto min-w-0 order-last md:order-none">
|
||||
<CategoryFilterBar
|
||||
|
||||
@ -556,7 +556,7 @@ export default function PeoplePage() {
|
||||
<div className="flex flex-col h-full animate-fade-in">
|
||||
{/* Header */}
|
||||
<div className="border-b bg-card px-4 md:px-6 min-h-[4rem] flex items-center gap-4 flex-wrap py-2 md:py-0 md:h-16 md:flex-nowrap shrink-0">
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight">People</h1>
|
||||
<h1 className="font-heading text-xl md:text-2xl font-bold tracking-tight">People</h1>
|
||||
<div className="w-full md:flex-1 md:w-auto min-w-0 order-last md:order-none">
|
||||
<CategoryFilterBar
|
||||
activeFilters={activeFilters}
|
||||
|
||||
@ -349,7 +349,7 @@ export default function ProjectDetail() {
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate('/projects')}>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight flex-1">Loading...</h1>
|
||||
<h1 className="font-heading text-xl md:text-2xl font-bold tracking-tight flex-1">Loading...</h1>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto px-4 md:px-6 py-5">
|
||||
<ListSkeleton rows={4} />
|
||||
@ -379,7 +379,7 @@ export default function ProjectDetail() {
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate('/projects')}>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight flex-1 truncate">
|
||||
<h1 className="font-heading text-xl md:text-2xl font-bold tracking-tight flex-1 truncate">
|
||||
{project.name}
|
||||
</h1>
|
||||
<Badge className={statusColors[project.status]}>
|
||||
|
||||
@ -72,7 +72,7 @@ export default function ProjectsPage() {
|
||||
<div className="flex flex-col h-full animate-fade-in">
|
||||
{/* Header */}
|
||||
<div className="border-b bg-card px-4 md:px-6 min-h-[4rem] flex items-center gap-4 flex-wrap py-2 md:py-0 md:h-16 md:flex-nowrap shrink-0">
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight">Projects</h1>
|
||||
<h1 className="font-heading text-xl md:text-2xl font-bold tracking-tight">Projects</h1>
|
||||
|
||||
<Select
|
||||
value={statusFilter}
|
||||
@ -123,37 +123,37 @@ export default function ProjectsPage() {
|
||||
<div className="flex-1 overflow-y-auto px-4 md:px-6 py-5">
|
||||
{/* Summary stats */}
|
||||
{!isLoading && projects.length > 0 && (
|
||||
<div className="grid gap-2.5 grid-cols-3 mb-5">
|
||||
<div className="grid gap-1.5 md:gap-2.5 grid-cols-3 mb-5">
|
||||
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className="p-1.5 rounded-md bg-blue-500/10">
|
||||
<CardContent className="p-2.5 md:p-4 flex items-center gap-2 md:gap-3">
|
||||
<div className="p-1.5 rounded-md bg-blue-500/10 hidden sm:block">
|
||||
<Layers className="h-4 w-4 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] tracking-wider uppercase text-muted-foreground">Total</p>
|
||||
<p className="font-heading text-xl font-bold tabular-nums">{projects.length}</p>
|
||||
<p className="text-[9px] md:text-[10px] tracking-wider uppercase text-muted-foreground">Total</p>
|
||||
<p className="font-heading text-lg md:text-xl font-bold tabular-nums">{projects.length}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className="p-1.5 rounded-md bg-purple-500/10">
|
||||
<CardContent className="p-2.5 md:p-4 flex items-center gap-2 md:gap-3">
|
||||
<div className="p-1.5 rounded-md bg-purple-500/10 hidden sm:block">
|
||||
<PlayCircle className="h-4 w-4 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] tracking-wider uppercase text-muted-foreground">In Progress</p>
|
||||
<p className="font-heading text-xl font-bold tabular-nums">{inProgressCount}</p>
|
||||
<p className="text-[9px] md:text-[10px] tracking-wider uppercase text-muted-foreground">In Progress</p>
|
||||
<p className="font-heading text-lg md:text-xl font-bold tabular-nums">{inProgressCount}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className="p-1.5 rounded-md bg-green-500/10">
|
||||
<CardContent className="p-2.5 md:p-4 flex items-center gap-2 md:gap-3">
|
||||
<div className="p-1.5 rounded-md bg-green-500/10 hidden sm:block">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] tracking-wider uppercase text-muted-foreground">Completed</p>
|
||||
<p className="font-heading text-xl font-bold tabular-nums">{completedCount}</p>
|
||||
<p className="text-[9px] md:text-[10px] tracking-wider uppercase text-muted-foreground">Completed</p>
|
||||
<p className="font-heading text-lg md:text-xl font-bold tabular-nums">{completedCount}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -73,14 +73,14 @@ export default function ReminderItem({ reminder, onEdit }: ReminderItemProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md transition-colors duration-150',
|
||||
'flex items-start md:items-center gap-2 md:gap-3 px-3 py-2 rounded-md transition-colors duration-150',
|
||||
'hover:bg-card-elevated',
|
||||
reminder.is_dismissed && 'opacity-50'
|
||||
)}
|
||||
>
|
||||
<Bell
|
||||
className={cn(
|
||||
'h-4 w-4 shrink-0',
|
||||
'h-4 w-4 shrink-0 mt-0.5 md:mt-0',
|
||||
isOverdue
|
||||
? 'text-red-400'
|
||||
: reminder.is_dismissed
|
||||
@ -89,78 +89,86 @@ export default function ReminderItem({ reminder, onEdit }: ReminderItemProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
<span
|
||||
className={cn(
|
||||
'text-sm font-medium truncate flex-1 min-w-0 cursor-pointer',
|
||||
reminder.is_dismissed && 'line-through text-muted-foreground'
|
||||
)}
|
||||
onClick={() => onEdit(reminder)}
|
||||
>
|
||||
{reminder.title}
|
||||
</span>
|
||||
|
||||
{reminder.recurrence_rule && (
|
||||
<span className="text-[9px] px-1.5 py-0.5 rounded font-medium uppercase tracking-wide bg-purple-500/15 text-purple-400 shrink-0">
|
||||
{recurrenceLabels[reminder.recurrence_rule] || reminder.recurrence_rule}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{remindDate && (
|
||||
{/* Content wrapper — stacks on mobile, inline on desktop */}
|
||||
<div className="flex-1 min-w-0 flex flex-col md:flex-row md:items-center gap-1 md:gap-3">
|
||||
<span
|
||||
className={cn(
|
||||
'text-[11px] shrink-0',
|
||||
isOverdue ? 'text-red-400' : isDueToday ? 'text-yellow-400' : 'text-muted-foreground'
|
||||
'text-sm font-medium truncate cursor-pointer md:flex-1 md:min-w-0',
|
||||
reminder.is_dismissed && 'line-through text-muted-foreground'
|
||||
)}
|
||||
onClick={() => onEdit(reminder)}
|
||||
>
|
||||
{format(remindDate, 'MMM d, h:mm a')}
|
||||
{reminder.title}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!reminder.is_dismissed && (
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{reminder.recurrence_rule && (
|
||||
<span className="text-[9px] px-1.5 py-0.5 rounded font-medium uppercase tracking-wide bg-purple-500/15 text-purple-400 shrink-0">
|
||||
{recurrenceLabels[reminder.recurrence_rule] || reminder.recurrence_rule}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{remindDate && (
|
||||
<span
|
||||
className={cn(
|
||||
'text-[11px] shrink-0',
|
||||
isOverdue ? 'text-red-400' : isDueToday ? 'text-yellow-400' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{format(remindDate, 'MMM d, h:mm a')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-0.5 shrink-0">
|
||||
{!reminder.is_dismissed && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => dismissMutation.mutate()}
|
||||
disabled={dismissMutation.isPending}
|
||||
className="h-7 w-7 hover:bg-orange-500/10 hover:text-orange-400"
|
||||
aria-label="Dismiss reminder"
|
||||
>
|
||||
<BellOff className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => dismissMutation.mutate()}
|
||||
disabled={dismissMutation.isPending}
|
||||
className="h-7 w-7 shrink-0 hover:bg-orange-500/10 hover:text-orange-400"
|
||||
aria-label="Dismiss reminder"
|
||||
onClick={() => onEdit(reminder)}
|
||||
className="h-7 w-7"
|
||||
aria-label="Edit reminder"
|
||||
>
|
||||
<BellOff className="h-3 w-3" />
|
||||
<Pencil className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onEdit(reminder)}
|
||||
className="h-7 w-7 shrink-0"
|
||||
aria-label="Edit reminder"
|
||||
>
|
||||
<Pencil className="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
{confirmingDelete ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-label="Confirm delete"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
className="h-7 shrink-0 px-2 bg-destructive/20 text-destructive text-[11px] font-medium"
|
||||
>
|
||||
Sure?
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label="Delete reminder"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
className="h-7 w-7 shrink-0 hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
{confirmingDelete ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-label="Confirm delete"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
className="h-7 px-2 bg-destructive/20 text-destructive text-[11px] font-medium"
|
||||
>
|
||||
Sure?
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label="Delete reminder"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
className="h-7 w-7 hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ export default function RemindersPage() {
|
||||
<div className="flex flex-col h-full animate-fade-in">
|
||||
{/* Header */}
|
||||
<div className="border-b bg-card px-4 md:px-6 min-h-[4rem] flex items-center gap-4 flex-wrap py-2 md:py-0 md:h-16 md:flex-nowrap shrink-0">
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight">Reminders</h1>
|
||||
<h1 className="font-heading text-xl md:text-2xl font-bold tracking-tight">Reminders</h1>
|
||||
|
||||
<Select
|
||||
value={filter}
|
||||
@ -160,43 +160,43 @@ export default function RemindersPage() {
|
||||
<div className="px-4 md:px-6 py-5">
|
||||
{/* Summary stats */}
|
||||
{!isLoading && reminders.length > 0 && (
|
||||
<div className="grid gap-2.5 grid-cols-3 mb-5">
|
||||
<div className="grid gap-1.5 md:gap-2.5 grid-cols-3 mb-5">
|
||||
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className="p-1.5 rounded-md bg-orange-500/10">
|
||||
<CardContent className="p-2.5 md:p-4 flex items-center gap-2 md:gap-3">
|
||||
<div className="p-1.5 rounded-md bg-orange-500/10 hidden sm:block">
|
||||
<Bell className="h-4 w-4 text-orange-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
<p className="text-[9px] md:text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
Active
|
||||
</p>
|
||||
<p className="font-heading text-xl font-bold tabular-nums">{activeCount}</p>
|
||||
<p className="font-heading text-lg md:text-xl font-bold tabular-nums">{activeCount}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className="p-1.5 rounded-md bg-red-500/10">
|
||||
<CardContent className="p-2.5 md:p-4 flex items-center gap-2 md:gap-3">
|
||||
<div className="p-1.5 rounded-md bg-red-500/10 hidden sm:block">
|
||||
<AlertCircle className="h-4 w-4 text-red-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
<p className="text-[9px] md:text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
Overdue
|
||||
</p>
|
||||
<p className="font-heading text-xl font-bold tabular-nums">{overdueCount}</p>
|
||||
<p className="font-heading text-lg md:text-xl font-bold tabular-nums">{overdueCount}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className="p-1.5 rounded-md bg-gray-500/10">
|
||||
<CardContent className="p-2.5 md:p-4 flex items-center gap-2 md:gap-3">
|
||||
<div className="p-1.5 rounded-md bg-gray-500/10 hidden sm:block">
|
||||
<BellOff className="h-4 w-4 text-gray-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
<p className="text-[9px] md:text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
Dismissed
|
||||
</p>
|
||||
<p className="font-heading text-xl font-bold tabular-nums">{dismissedCount}</p>
|
||||
<p className="font-heading text-lg md:text-xl font-bold tabular-nums">{dismissedCount}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -52,7 +52,6 @@ export default function TodoItem({ todo, onEdit }: TodoItemProps) {
|
||||
await api.delete(`/todos/${todo.id}`);
|
||||
},
|
||||
onMutate: async () => {
|
||||
// Optimistic removal
|
||||
await queryClient.cancelQueries({ queryKey: ['todos'] });
|
||||
const previous = queryClient.getQueryData<Todo[]>(['todos']);
|
||||
queryClient.setQueryData<Todo[]>(['todos'], (old) =>
|
||||
@ -65,7 +64,6 @@ export default function TodoItem({ todo, onEdit }: TodoItemProps) {
|
||||
toast.success('Todo deleted');
|
||||
},
|
||||
onError: (_err, _vars, context) => {
|
||||
// Rollback on failure
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(['todos'], context.previous);
|
||||
}
|
||||
@ -87,7 +85,7 @@ export default function TodoItem({ todo, onEdit }: TodoItemProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md transition-colors duration-150',
|
||||
'flex items-start md:items-center gap-2 md:gap-3 px-3 py-2 rounded-md transition-colors duration-150',
|
||||
'hover:bg-card-elevated',
|
||||
todo.completed && 'opacity-50'
|
||||
)}
|
||||
@ -96,100 +94,108 @@ export default function TodoItem({ todo, onEdit }: TodoItemProps) {
|
||||
checked={todo.completed}
|
||||
onChange={() => toggleMutation.mutate()}
|
||||
disabled={toggleMutation.isPending}
|
||||
className="mt-0.5 md:mt-0"
|
||||
/>
|
||||
|
||||
<span
|
||||
className={cn(
|
||||
'text-sm font-medium truncate flex-1 min-w-0 cursor-pointer',
|
||||
todo.completed && 'line-through text-muted-foreground'
|
||||
)}
|
||||
onClick={() => onEdit(todo)}
|
||||
>
|
||||
{todo.title}
|
||||
</span>
|
||||
|
||||
{/* Inline pills */}
|
||||
<span
|
||||
className={cn(
|
||||
'text-[9px] px-1.5 py-0.5 rounded-full font-medium shrink-0',
|
||||
priorityStyles[todo.priority]
|
||||
)}
|
||||
>
|
||||
{todo.priority}
|
||||
</span>
|
||||
|
||||
{todo.category && (
|
||||
<span className="text-[9px] px-1.5 py-0.5 rounded font-medium uppercase tracking-wide bg-blue-500/15 text-blue-400 shrink-0">
|
||||
{todo.category}
|
||||
{/* Content wrapper — stacks on mobile, inline on desktop */}
|
||||
<div className="flex-1 min-w-0 flex flex-col md:flex-row md:items-center gap-1 md:gap-3">
|
||||
{/* Title row — always takes full width on mobile */}
|
||||
<span
|
||||
className={cn(
|
||||
'text-sm font-medium truncate cursor-pointer md:flex-1 md:min-w-0',
|
||||
todo.completed && 'line-through text-muted-foreground'
|
||||
)}
|
||||
onClick={() => onEdit(todo)}
|
||||
>
|
||||
{todo.title}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{todo.recurrence_rule && (
|
||||
<span className="text-[9px] px-1.5 py-0.5 rounded font-medium uppercase tracking-wide bg-purple-500/15 text-purple-400 shrink-0">
|
||||
{recurrenceLabels[todo.recurrence_rule] || todo.recurrence_rule}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Date / time / reset info — right-aligned cluster */}
|
||||
{showResetInfo ? (
|
||||
<div className="flex items-center gap-1 text-[11px] text-purple-400 shrink-0">
|
||||
<RefreshCw className="h-3 w-3" />
|
||||
<span>
|
||||
Resets {format(resetDate, 'EEE dd/MM')}
|
||||
{nextDueDate && (
|
||||
<> · Due {format(nextDueDate, 'dd/MM')}{todo.due_time ? ` ${todo.due_time.slice(0, 5)}` : ''}</>
|
||||
{/* Metadata row — wraps on second line on mobile */}
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<span
|
||||
className={cn(
|
||||
'text-[9px] px-1.5 py-0.5 rounded-full font-medium shrink-0',
|
||||
priorityStyles[todo.priority]
|
||||
)}
|
||||
>
|
||||
{todo.priority}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{dueDate && (
|
||||
<span
|
||||
className={cn(
|
||||
'flex items-center gap-1 text-[11px] shrink-0',
|
||||
isOverdue ? 'text-red-400' : isDueToday ? 'text-yellow-400' : 'text-muted-foreground'
|
||||
|
||||
{todo.category && (
|
||||
<span className="text-[9px] px-1.5 py-0.5 rounded font-medium uppercase tracking-wide bg-blue-500/15 text-blue-400 shrink-0">
|
||||
{todo.category}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{todo.recurrence_rule && (
|
||||
<span className="text-[9px] px-1.5 py-0.5 rounded font-medium uppercase tracking-wide bg-purple-500/15 text-purple-400 shrink-0">
|
||||
{recurrenceLabels[todo.recurrence_rule] || todo.recurrence_rule}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{showResetInfo ? (
|
||||
<div className="flex items-center gap-1 text-[11px] text-purple-400 shrink-0">
|
||||
<RefreshCw className="h-3 w-3" />
|
||||
<span>
|
||||
Resets {format(resetDate, 'EEE dd/MM')}
|
||||
{nextDueDate && (
|
||||
<>{' \u00b7 '}Due {format(nextDueDate, 'dd/MM')}{todo.due_time ? ` ${todo.due_time.slice(0, 5)}` : ''}</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{dueDate && (
|
||||
<span
|
||||
className={cn(
|
||||
'flex items-center gap-1 text-[11px] shrink-0',
|
||||
isOverdue ? 'text-red-400' : isDueToday ? 'text-yellow-400' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{isOverdue ? <AlertCircle className="h-3 w-3" /> : <Calendar className="h-3 w-3" />}
|
||||
{isOverdue ? 'Overdue \u00b7 ' : isDueToday ? 'Today \u00b7 ' : ''}
|
||||
{format(dueDate, 'MMM d')}
|
||||
</span>
|
||||
)}
|
||||
>
|
||||
{isOverdue ? <AlertCircle className="h-3 w-3" /> : <Calendar className="h-3 w-3" />}
|
||||
{isOverdue ? 'Overdue · ' : isDueToday ? 'Today · ' : ''}
|
||||
{format(dueDate, 'MMM d')}
|
||||
</span>
|
||||
{todo.due_time && (
|
||||
<span className="flex items-center gap-1 text-[11px] text-muted-foreground shrink-0">
|
||||
<Clock className="h-3 w-3" />
|
||||
{todo.due_time.slice(0, 5)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{todo.due_time && (
|
||||
<span className="flex items-center gap-1 text-[11px] text-muted-foreground shrink-0">
|
||||
<Clock className="h-3 w-3" />
|
||||
{todo.due_time.slice(0, 5)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<Button variant="ghost" size="icon" onClick={() => onEdit(todo)} className="h-7 w-7 shrink-0" aria-label="Edit todo">
|
||||
<Pencil className="h-3 w-3" />
|
||||
</Button>
|
||||
{confirmingDelete ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-label="Confirm delete"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
className="h-7 shrink-0 px-2 bg-destructive/20 text-destructive text-[11px] font-medium"
|
||||
>
|
||||
Sure?
|
||||
<div className="flex items-center gap-0.5 shrink-0">
|
||||
<Button variant="ghost" size="icon" onClick={() => onEdit(todo)} className="h-7 w-7" aria-label="Edit todo">
|
||||
<Pencil className="h-3 w-3" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label="Delete todo"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
className="h-7 w-7 shrink-0 hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
{confirmingDelete ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-label="Confirm delete"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
className="h-7 px-2 bg-destructive/20 text-destructive text-[11px] font-medium"
|
||||
>
|
||||
Sure?
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label="Delete todo"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
className="h-7 w-7 hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -130,7 +130,7 @@ export default function TodosPage() {
|
||||
<div className="flex flex-col h-full animate-fade-in">
|
||||
{/* Header */}
|
||||
<div className="border-b bg-card px-4 md:px-6 min-h-[4rem] flex items-center gap-4 flex-wrap py-2 md:py-0 md:h-16 md:flex-nowrap shrink-0">
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight">Todos</h1>
|
||||
<h1 className="font-heading text-xl md:text-2xl font-bold tracking-tight">Todos</h1>
|
||||
|
||||
{/* Priority filter */}
|
||||
<Select
|
||||
@ -195,43 +195,43 @@ export default function TodosPage() {
|
||||
<div className="px-4 md:px-6 py-5">
|
||||
{/* Summary stats */}
|
||||
{!isLoading && todos.length > 0 && (
|
||||
<div className="grid gap-2.5 grid-cols-3 mb-5">
|
||||
<div className="grid gap-1.5 md:gap-2.5 grid-cols-3 mb-5">
|
||||
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className="p-1.5 rounded-md bg-blue-500/10">
|
||||
<CardContent className="p-2.5 md:p-4 flex items-center gap-2 md:gap-3">
|
||||
<div className="p-1.5 rounded-md bg-blue-500/10 hidden sm:block">
|
||||
<CheckSquare className="h-4 w-4 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
<p className="text-[9px] md:text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
Open
|
||||
</p>
|
||||
<p className="font-heading text-xl font-bold tabular-nums">{totalCount}</p>
|
||||
<p className="font-heading text-lg md:text-xl font-bold tabular-nums">{totalCount}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className="p-1.5 rounded-md bg-green-500/10">
|
||||
<CardContent className="p-2.5 md:p-4 flex items-center gap-2 md:gap-3">
|
||||
<div className="p-1.5 rounded-md bg-green-500/10 hidden sm:block">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
<p className="text-[9px] md:text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
Completed
|
||||
</p>
|
||||
<p className="font-heading text-xl font-bold tabular-nums">{completedCount}</p>
|
||||
<p className="font-heading text-lg md:text-xl font-bold tabular-nums">{completedCount}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className="p-1.5 rounded-md bg-red-500/10">
|
||||
<CardContent className="p-2.5 md:p-4 flex items-center gap-2 md:gap-3">
|
||||
<div className="p-1.5 rounded-md bg-red-500/10 hidden sm:block">
|
||||
<AlertCircle className="h-4 w-4 text-red-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
<p className="text-[9px] md:text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
Overdue
|
||||
</p>
|
||||
<p className="font-heading text-xl font-bold tabular-nums">{overdueCount}</p>
|
||||
<p className="font-heading text-lg md:text-xl font-bold tabular-nums">{overdueCount}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -223,6 +223,41 @@ form[data-submitted] input:invalid + button {
|
||||
box-shadow: 0 0 0 2px hsl(0 62.8% 50% / 0.25);
|
||||
}
|
||||
|
||||
|
||||
/* ── FullCalendar mobile overrides ── */
|
||||
@media (max-width: 767px) {
|
||||
.fc .fc-daygrid-event {
|
||||
font-size: 0.65rem;
|
||||
padding: 0 2px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.fc .fc-timegrid-event {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.fc .fc-timegrid-event .fc-event-main {
|
||||
padding: 1px 2px;
|
||||
}
|
||||
|
||||
.fc .fc-daygrid-day-number {
|
||||
font-size: 0.75rem;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.fc .fc-col-header-cell-cushion {
|
||||
font-size: 0.7rem;
|
||||
padding: 4px 2px;
|
||||
}
|
||||
|
||||
.fc .fc-timegrid-slot-label {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.fc .fc-daygrid-more-link {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
}
|
||||
/* ── Ambient background animations ── */
|
||||
|
||||
@keyframes drift-1 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user