diff --git a/src/app/dashboard/invoices/import/page.tsx b/src/app/dashboard/invoices/import/page.tsx index ea6b434..6be64bb 100644 --- a/src/app/dashboard/invoices/import/page.tsx +++ b/src/app/dashboard/invoices/import/page.tsx @@ -238,9 +238,10 @@ function FormatInstructions() {

Sample Row:

- "Acme - Corp","john@acme.com","INV-001","2024-01-15","2024-02-14","Web - development work","40","75.00","8.5" + "Acme + Corp","john@acme.com","INV-001","2024-01-15","2024-02-14","Web + development + work","40","75.00","8.5"

@@ -276,7 +277,7 @@ function ImportantNotes() { @@ -429,7 +430,7 @@ export default async function ImportPage() { - {[...Array(4)].map((_, i) => ( + {Array.from({ length: 4 }, (_, i) => (
diff --git a/src/app/dashboard/invoices/new/page.tsx b/src/app/dashboard/invoices/new/page.tsx index 83f11ff..3272401 100644 --- a/src/app/dashboard/invoices/new/page.tsx +++ b/src/app/dashboard/invoices/new/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { api } from "~/trpc/react"; @@ -227,9 +227,6 @@ export default function NewInvoicePage() { ], }); - // Floating action bar ref - const footerRef = useRef(null); - // Queries const { data: clients, isLoading: clientsLoading } = api.clients.getAll.useQuery(); @@ -386,7 +383,7 @@ export default function NewInvoicePage() { } return ( -
+
- - {/* Action Buttons */} -
- - - - -
- - -
-
- + +
+ +
+
+

+ Creating a new invoice +

+

+ Complete the form to create your invoice +

+
+ + } + > - - - +
+ +
+
+

+ {mode === "create" + ? "Creating a new business" + : "Editing business details"} +

+

+ {mode === "create" + ? "Complete the form to create your business" + : "Update your business information"} +

+
+ } > - - - +
+ +
+
+

+ {mode === "create" + ? "Creating a new client" + : "Editing client details"} +

+

+ {mode === "create" + ? "Complete the form to create your client" + : "Update your client information"} +

+
+ } > + {/* Form Content */}
@@ -698,6 +725,7 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { onUpdateItem={updateItem} onMoveUp={moveItemUp} onMoveDown={moveItemDown} + onReorderItems={reorderItems} /> @@ -768,63 +796,21 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) {
- - {/* Footer for floating bar trigger */} -
-
-
- -
-
-

- {invoiceId ? "Edit Invoice" : "Create Invoice"} -

-

- {invoiceId ? "Update invoice details" : "Create a new invoice"} -

-
-
-
- -
-
{/* Floating Action Bar */} -
- +
+
-

+

{invoiceId ? "Edit Invoice" : "Create Invoice"}

-

- {invoiceId ? "Update invoice details" : "Create a new invoice"} +

+ Update invoice details

diff --git a/src/components/forms/invoice-line-items.tsx b/src/components/forms/invoice-line-items.tsx index 6c69eef..edde820 100644 --- a/src/components/forms/invoice-line-items.tsx +++ b/src/components/forms/invoice-line-items.tsx @@ -15,6 +15,23 @@ import { ChevronDown, } from "lucide-react"; import { cn } from "~/lib/utils"; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + type DragEndEvent, +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; +import { useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; interface InvoiceItem { id: string; @@ -36,6 +53,7 @@ interface InvoiceLineItemsProps { ) => void; onMoveUp: (index: number) => void; onMoveDown: (index: number) => void; + onReorderItems: (items: InvoiceItem[]) => void; className?: string; } @@ -55,19 +73,100 @@ interface LineItemRowProps { isLast: boolean; } -function LineItemRow({ +interface SortableLineItemProps { + item: InvoiceItem; + index: number; + canRemove: boolean; + onRemove: (index: number) => void; + onUpdate: ( + index: number, + field: string, + value: string | number | Date, + ) => void; + onMoveUp: (index: number) => void; + onMoveDown: (index: number) => void; + isFirst: boolean; + isLast: boolean; +} + +function SortableLineItem({ item, index, canRemove, onRemove, onUpdate, -}: LineItemRowProps) { + onMoveUp, + onMoveDown, + isFirst, + isLast, +}: SortableLineItemProps) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: item.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + return ( -
+