feat(uat-batch-3): wave-1 primitives — DatePicker, DateTimePicker, FileInputButton, ColumnPicker hideAll
Builds the foundational primitives that subsequent waves depend on. None of these introduce new deps — date-fns, react-day-picker, and shadcn Calendar were already in the tree. - `<DatePicker>` and `<DateTimePicker>` in src/components/ui — desktop popover wrapping the existing shadcn Calendar (caption-dropdown nav so reps can jump months/years for the SkipAheadBanner backfill UX), mobile native input via useIsMobile. Drop-in for `<Input type=date>` / `<Input type=datetime-local>`. - `<FileInputButton>` in src/components/ui — styled Button + hidden input, replaces browser-default file picker UI. Most queued sweep sites already used the hidden-input + Button-trigger pattern; the primitive lands for any new caller plus consistent filename display + clear button. - ColumnPicker `hideAll()` footer item — symmetric to existing `showAll()`, with the same visibility gate. Lands platform-wide via the shared component. - Migrated highest-leverage call sites to the new primitives: * MilestoneAdvanceButton (backfill UX) * Reminder form (datetime-local → DateTimePicker) * Snooze dialog (datetime-local → DateTimePicker) * External-EOI upload dialog (date + file picker) * Payments section (received-on date) - Remaining 15+ date-input call sites parked for a follow-up sweep — several use react-hook-form `register` patterns that need careful migration to the new controlled-value contract. tsc clean. 1419/1419 vitest pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,8 @@ import { Loader2, Upload } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DatePicker } from '@/components/ui/date-picker';
|
||||
import { FileInputButton } from '@/components/ui/file-input-button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
@@ -132,12 +134,13 @@ export function ExternalEoiUploadDialog({ open, onOpenChange, interestId, onSucc
|
||||
<div className="space-y-3 py-2">
|
||||
<div>
|
||||
<Label>PDF file *</Label>
|
||||
<Input
|
||||
type="file"
|
||||
accept="application/pdf,.pdf"
|
||||
onChange={(e) => setFile(e.target.files?.[0] ?? null)}
|
||||
className="mt-1"
|
||||
/>
|
||||
<div className="mt-1">
|
||||
<FileInputButton
|
||||
accept="application/pdf,.pdf"
|
||||
label={file ? file.name : 'Choose PDF'}
|
||||
onFilesPicked={(files) => setFile(files[0] ?? null)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Title (optional)</Label>
|
||||
@@ -153,12 +156,9 @@ export function ExternalEoiUploadDialog({ open, onOpenChange, interestId, onSucc
|
||||
</div>
|
||||
<div>
|
||||
<Label>Date signed</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={signedAt}
|
||||
onChange={(e) => setSignedAt(e.target.value)}
|
||||
className="mt-1"
|
||||
/>
|
||||
<div className="mt-1">
|
||||
<DatePicker value={signedAt} onChange={setSignedAt} toDate={new Date()} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Signer names (comma-separated)</Label>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { parsePhone } from '@/lib/i18n/phone';
|
||||
|
||||
import type { DetailTab } from '@/components/shared/detail-layout';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { DatePicker } from '@/components/ui/date-picker';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { NotesList } from '@/components/shared/notes-list';
|
||||
import { InlineEditableField } from '@/components/shared/inline-editable-field';
|
||||
@@ -315,13 +315,12 @@ function MilestoneAdvanceButton({
|
||||
<label className="text-xs font-medium" htmlFor="milestone-date">
|
||||
Date completed
|
||||
</label>
|
||||
<Input
|
||||
<DatePicker
|
||||
id="milestone-date"
|
||||
type="date"
|
||||
value={date}
|
||||
max={new Date().toISOString().slice(0, 10)}
|
||||
onChange={(e) => setDate(e.target.value)}
|
||||
className="h-9"
|
||||
toDate={new Date()}
|
||||
onChange={setDate}
|
||||
placeholder="Pick a date"
|
||||
/>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
Defaults to today — back-date if the event happened earlier.
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Plus, Trash2, Receipt } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DatePicker } from '@/components/ui/date-picker';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
@@ -325,12 +326,12 @@ function RecordPaymentSheet({
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="payment-date">Received on</Label>
|
||||
<Input
|
||||
<DatePicker
|
||||
id="payment-date"
|
||||
type="date"
|
||||
value={receivedAt}
|
||||
onChange={(e) => setReceivedAt(e.target.value)}
|
||||
required
|
||||
onChange={setReceivedAt}
|
||||
toDate={new Date()}
|
||||
placeholder="Pick a date"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user