Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions components/filter-due-date.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
"use client";

import type { FilterCriteria } from "@/lib/filters";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

interface FilterDueDateProps {
criteria: FilterCriteria;
onChange: (updates: Partial<FilterCriteria>) => void;
}

/**
* Due date filter section
*
* Provides quick filters (overdue, today, this week, no deadline)
* and a custom date range picker
*/
export function FilterDueDate({ criteria, onChange }: FilterDueDateProps) {
return (
<div className="space-y-3">
{/* Quick Filters */}
<div className="flex gap-2">
<button
onClick={() => onChange({
overdue: !criteria.overdue,
dueToday: undefined,
dueThisWeek: undefined,
noDueDate: undefined,
dueDateRange: undefined
})}
className={`flex-1 rounded-lg border px-3 py-2 text-sm transition ${
criteria.overdue
? "border-red-500 bg-red-50 font-medium text-red-700"
: "border-card-border hover:border-accent/50"
}`}
>
Overdue
</button>
<button
onClick={() => onChange({
dueToday: !criteria.dueToday,
overdue: undefined,
dueThisWeek: undefined,
noDueDate: undefined,
dueDateRange: undefined
})}
className={`flex-1 rounded-lg border px-3 py-2 text-sm transition ${
criteria.dueToday
? "border-amber-500 bg-amber-50 font-medium text-amber-700"
: "border-card-border hover:border-accent/50"
}`}
>
Today
</button>
</div>

<div className="flex gap-2">
<button
onClick={() => onChange({
dueThisWeek: !criteria.dueThisWeek,
overdue: undefined,
dueToday: undefined,
noDueDate: undefined,
dueDateRange: undefined
})}
className={`flex-1 rounded-lg border px-3 py-2 text-sm transition ${
criteria.dueThisWeek
? "border-accent bg-accent/10 font-medium"
: "border-card-border hover:border-accent/50"
}`}
>
This Week
</button>
<button
onClick={() => onChange({
noDueDate: !criteria.noDueDate,
overdue: undefined,
dueToday: undefined,
dueThisWeek: undefined,
dueDateRange: undefined
})}
className={`flex-1 rounded-lg border px-3 py-2 text-sm transition ${
criteria.noDueDate
? "border-accent bg-accent/10 font-medium"
: "border-card-border hover:border-accent/50"
}`}
>
No Deadline
</button>
</div>

{/* Custom Date Range */}
<details className="rounded-lg border border-card-border">
<summary className="cursor-pointer px-3 py-2 text-sm font-medium text-foreground-muted hover:text-foreground">
Custom date range...
</summary>
<div className="grid grid-cols-2 gap-2 p-3 pt-0">
<div>
<Label htmlFor="date-start" className="mb-1 block text-xs">From</Label>
<Input
id="date-start"
type="date"
value={criteria.dueDateRange?.start?.slice(0, 10) || ''}
onChange={(e) => {
const start = e.target.value ? new Date(e.target.value).toISOString() : undefined;
onChange({
dueDateRange: start || criteria.dueDateRange?.end
? { start, end: criteria.dueDateRange?.end }
: undefined,
overdue: undefined,
dueToday: undefined,
dueThisWeek: undefined,
noDueDate: undefined
});
}}
className="h-9"
/>
</div>
<div>
<Label htmlFor="date-end" className="mb-1 block text-xs">To</Label>
<Input
id="date-end"
type="date"
value={criteria.dueDateRange?.end?.slice(0, 10) || ''}
onChange={(e) => {
const end = e.target.value ? new Date(e.target.value).toISOString() : undefined;
onChange({
dueDateRange: criteria.dueDateRange?.start || end
? { start: criteria.dueDateRange?.start, end }
: undefined,
overdue: undefined,
dueToday: undefined,
dueThisWeek: undefined,
noDueDate: undefined
});
}}
className="h-9"
/>
</div>
</div>
</details>
</div>
);
}
127 changes: 2 additions & 125 deletions components/filter-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { useState } from "react";
import { ChevronDownIcon, SaveIcon } from "lucide-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { TagMultiselect } from "@/components/tag-multiselect";
import { FilterDueDate } from "@/components/filter-due-date";
import type { FilterCriteria, QuadrantId, RecurrenceType } from "@/lib/filters";
import { quadrants } from "@/lib/quadrants";

Expand Down Expand Up @@ -143,129 +142,7 @@ export function FilterPopover({

{/* Due Date Section */}
<CollapsibleSection title="Due Date" defaultOpen={false}>
<div className="space-y-3">
{/* Quick Filters */}
<div className="flex gap-2">
<button
onClick={() => updateCriteria({
overdue: !criteria.overdue,
dueToday: undefined,
dueThisWeek: undefined,
noDueDate: undefined,
dueDateRange: undefined
})}
className={`flex-1 rounded-lg border px-3 py-2 text-sm transition ${
criteria.overdue
? "border-red-500 bg-red-50 font-medium text-red-700"
: "border-card-border hover:border-accent/50"
}`}
>
Overdue
</button>
<button
onClick={() => updateCriteria({
dueToday: !criteria.dueToday,
overdue: undefined,
dueThisWeek: undefined,
noDueDate: undefined,
dueDateRange: undefined
})}
className={`flex-1 rounded-lg border px-3 py-2 text-sm transition ${
criteria.dueToday
? "border-amber-500 bg-amber-50 font-medium text-amber-700"
: "border-card-border hover:border-accent/50"
}`}
>
Today
</button>
</div>

<div className="flex gap-2">
<button
onClick={() => updateCriteria({
dueThisWeek: !criteria.dueThisWeek,
overdue: undefined,
dueToday: undefined,
noDueDate: undefined,
dueDateRange: undefined
})}
className={`flex-1 rounded-lg border px-3 py-2 text-sm transition ${
criteria.dueThisWeek
? "border-accent bg-accent/10 font-medium"
: "border-card-border hover:border-accent/50"
}`}
>
This Week
</button>
<button
onClick={() => updateCriteria({
noDueDate: !criteria.noDueDate,
overdue: undefined,
dueToday: undefined,
dueThisWeek: undefined,
dueDateRange: undefined
})}
className={`flex-1 rounded-lg border px-3 py-2 text-sm transition ${
criteria.noDueDate
? "border-accent bg-accent/10 font-medium"
: "border-card-border hover:border-accent/50"
}`}
>
No Deadline
</button>
</div>

{/* Custom Date Range */}
<details className="rounded-lg border border-card-border">
<summary className="cursor-pointer px-3 py-2 text-sm font-medium text-foreground-muted hover:text-foreground">
Custom date range...
</summary>
<div className="grid grid-cols-2 gap-2 p-3 pt-0">
<div>
<Label htmlFor="date-start" className="mb-1 block text-xs">From</Label>
<Input
id="date-start"
type="date"
value={criteria.dueDateRange?.start?.slice(0, 10) || ''}
onChange={(e) => {
const start = e.target.value ? new Date(e.target.value).toISOString() : undefined;
updateCriteria({
dueDateRange: start || criteria.dueDateRange?.end
? { start, end: criteria.dueDateRange?.end }
: undefined,
overdue: undefined,
dueToday: undefined,
dueThisWeek: undefined,
noDueDate: undefined
});
}}
className="h-9"
/>
</div>
<div>
<Label htmlFor="date-end" className="mb-1 block text-xs">To</Label>
<Input
id="date-end"
type="date"
value={criteria.dueDateRange?.end?.slice(0, 10) || ''}
onChange={(e) => {
const end = e.target.value ? new Date(e.target.value).toISOString() : undefined;
updateCriteria({
dueDateRange: criteria.dueDateRange?.start || end
? { start: criteria.dueDateRange?.start, end }
: undefined,
overdue: undefined,
dueToday: undefined,
dueThisWeek: undefined,
noDueDate: undefined
});
}}
className="h-9"
/>
</div>
</div>
</details>
</div>
<FilterDueDate criteria={criteria} onChange={updateCriteria} />
</CollapsibleSection>

{/* Recurrence Section */}
Expand Down
Loading