diff --git a/src/components/forms/invoice-calendar-view.tsx b/src/components/forms/invoice-calendar-view.tsx index 7b07f85..2cc5580 100644 --- a/src/components/forms/invoice-calendar-view.tsx +++ b/src/components/forms/invoice-calendar-view.tsx @@ -4,12 +4,12 @@ import * as React from "react"; import { format, startOfWeek, endOfWeek, eachDayOfInterval, isSameDay, subWeeks, addWeeks, subMonths, addMonths } from "date-fns"; import { Calendar } from "~/components/ui/calendar"; import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogFooter, -} from "~/components/ui/dialog"; + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetFooter, +} from "~/components/ui/sheet"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; @@ -51,7 +51,7 @@ export function InvoiceCalendarView({ const [date, setDate] = React.useState(undefined); // Start unselected const [viewDate, setViewDate] = React.useState(new Date()); // Controls the view (month/week) const [view, setView] = React.useState<"month" | "week">("month"); - const [dialogOpen, setDialogOpen] = React.useState(false); + const [sheetOpen, setSheetOpen] = React.useState(false); // Derived state for selected date items - solves cursor jumping const selectedDateItems = React.useMemo(() => { if (!date) return []; @@ -76,9 +76,7 @@ export function InvoiceCalendarView({ const handleSelectDate = (newDate: Date | undefined) => { if (!newDate) return; setDate(newDate); - // Optionally update viewDate to match selection if desired, but user wants them decoupled during nav - // setViewDate(newDate); - setDialogOpen(true); + setSheetOpen(true); }; const handleAddNewItem = () => { @@ -92,8 +90,8 @@ export function InvoiceCalendarView({ const currentWeekEnd = endOfWeek(viewDate); const weekDays = eachDayOfInterval({ start: currentWeekStart, end: currentWeekEnd }); - const handleCloseDialog = (isOpen: boolean) => { - setDialogOpen(isOpen); + const handleCloseSheet = (isOpen: boolean) => { + setSheetOpen(isOpen); if (!isOpen) { setDate(undefined); } @@ -280,95 +278,104 @@ export function InvoiceCalendarView({ )} - {/* Dialog for Day Details - Now consistently used and rounded */} - - - - -
- + + + +
+
{date ? format(date, "EEEE, MMMM do") : "Details"} - - +
+
-
- {date && selectedDateItems.length === 0 ? ( -
- -
-

No hours logged

-

Add time entries for this day.

-
- -
- ) : ( -
- {selectedDateItems.map(({ item, index }) => ( -
-
-
- - onUpdateItem(index, "description", e.target.value)} - placeholder="What did you work on?" - className="bg-background/50 border-transparent focus:border-input focus:bg-background transition-all" - /> -
-
-
-
- - onUpdateItem(index, "hours", v)} - step={0.25} - min={0} - className="bg-background/50" - /> -
-
- - onUpdateItem(index, "rate", v)} - prefix="$" - min={0} - className="bg-background/50" - /> -
-
- ${(item.hours * item.rate).toFixed(2)} -
- -
+
+
+ {date && selectedDateItems.length === 0 ? ( +
+
+
- ))} - -
- )} +
+

No hours logged

+

There are no time entries recorded for this day yet.

+
+ +
+ ) : ( +
+ {selectedDateItems.map(({ item, index }) => ( +
+
+
+ + onUpdateItem(index, "description", e.target.value)} + placeholder="What did you work on?" + className="bg-muted/30 border-transparent focus:border-input focus:bg-background transition-all font-medium" + /> +
+
+
+
+ + onUpdateItem(index, "hours", v)} + step={0.25} + min={0} + className="bg-muted/30" + /> +
+
+ + onUpdateItem(index, "rate", v)} + prefix="$" + min={0} + className="bg-muted/30" + /> +
+
+ + ${(item.hours * item.rate).toFixed(2)} + +
+ +
+
+ ))} + +
+ )} +
- - - - -
+ + + + + ); } diff --git a/src/components/forms/invoice-form.tsx b/src/components/forms/invoice-form.tsx index c8339f8..a57de08 100644 --- a/src/components/forms/invoice-form.tsx +++ b/src/components/forms/invoice-form.tsx @@ -33,7 +33,8 @@ import { import { STATUS_OPTIONS } from "./invoice/types"; import type { InvoiceFormData, InvoiceItem } from "./invoice/types"; -// ... (Imports and Interfaces identical to previous) +import { CountUp } from "~/components/ui/count-up"; + interface InvoiceFormProps { invoiceId?: string; @@ -314,9 +315,9 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { {/* ITEMS TAB */}
- Total${totals.total.toFixed(2)} - Subtotal${totals.subtotal.toFixed(2)} - Hours{formData.items.reduce((s, i) => s + i.hours, 0)}h + Total + Subtotal + Hours s + i.hours, 0)} suffix="h" />
Invoice Items diff --git a/src/components/forms/invoice-line-items.tsx b/src/components/forms/invoice-line-items.tsx index bb6fd88..e266317 100644 --- a/src/components/forms/invoice-line-items.tsx +++ b/src/components/forms/invoice-line-items.tsx @@ -126,19 +126,19 @@ function SortableLineItem({ exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.2, ease: "easeOut" }} className={cn( - "bg-secondary hidden rounded-lg p-4 md:block transition-all", - isDragging && "opacity-50", + "bg-card border hidden rounded-xl p-4 md:block transition-all shadow-sm group hover:border-primary/20", + isDragging && "opacity-50 shadow-md rotate-1 scale-[1.01] z-50 ring-2 ring-primary/20", )} >
{/* Drag Handle and Arrow Controls */}
- +
@@ -262,7 +262,7 @@ function MobileLineItem({ transition={{ duration: 0.2, ease: "easeOut" }} className="border-border bg-card overflow-hidden rounded-lg border md:hidden" > -
+
{/* Description */}
@@ -447,17 +447,15 @@ export function InvoiceLineItems({ {/* Add Item Button */}
- - - +
diff --git a/src/components/ui/count-up.tsx b/src/components/ui/count-up.tsx new file mode 100644 index 0000000..98d117f --- /dev/null +++ b/src/components/ui/count-up.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { motion, useSpring, useTransform } from "framer-motion"; +import { useEffect } from "react"; + +export function CountUp({ value, prefix = "", suffix = "" }: { value: number, prefix?: string, suffix?: string }) { + const spring = useSpring(value, { mass: 0.8, stiffness: 75, damping: 15 }); + const display = useTransform(spring, (current) => `${prefix}${current.toFixed(2)}${suffix}`); + + useEffect(() => { + spring.set(value); + }, [spring, value]); + + return {display}; +}