diff --git a/src/app/dashboard/businesses/[id]/page.tsx b/src/app/dashboard/businesses/[id]/page.tsx index 18c8c8f..7e9cd1a 100644 --- a/src/app/dashboard/businesses/[id]/page.tsx +++ b/src/app/dashboard/businesses/[id]/page.tsx @@ -50,7 +50,7 @@ export default async function BusinessDetailPage({ variant="gradient" > - - - - }> - - - - - ); +export default function BusinessesPage() { + redirect("/dashboard/entities?tab=businesses"); } diff --git a/src/app/dashboard/clients/[id]/page.tsx b/src/app/dashboard/clients/[id]/page.tsx index 961ce12..3353b16 100644 --- a/src/app/dashboard/clients/[id]/page.tsx +++ b/src/app/dashboard/clients/[id]/page.tsx @@ -64,7 +64,7 @@ export default async function ClientDetailPage({ variant="gradient" > - - - - - - - ); +export default function ClientsPage() { + redirect("/dashboard/entities?tab=clients"); } diff --git a/src/app/dashboard/entities/_components/entities-view.tsx b/src/app/dashboard/entities/_components/entities-view.tsx new file mode 100644 index 0000000..04cef7d --- /dev/null +++ b/src/app/dashboard/entities/_components/entities-view.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { Plus } from "lucide-react"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { PageHeader } from "~/components/layout/page-header"; +import { Button } from "~/components/ui/button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; +import { ClientsTable } from "../../clients/_components/clients-table"; +import { BusinessesTable } from "../../businesses/_components/businesses-table"; + +type EntityTab = "clients" | "businesses"; + +export function EntitiesView({ initialTab }: { initialTab: EntityTab }) { + const router = useRouter(); + const searchParams = useSearchParams(); + const tab: EntityTab = + searchParams.get("tab") === "businesses" ? "businesses" : initialTab; + + function handleTabChange(value: string) { + const next = value === "businesses" ? "businesses" : "clients"; + router.replace(`/dashboard/entities?tab=${next}`, { scroll: false }); + } + + const addHref = + tab === "clients" ? "/dashboard/clients/new" : "/dashboard/businesses/new"; + const addLabel = tab === "clients" ? "Add client" : "Add business"; + + return ( +
+ + + + + + + Clients + Businesses + + + + + + + + + + +
+ ); +} diff --git a/src/app/dashboard/entities/page.tsx b/src/app/dashboard/entities/page.tsx new file mode 100644 index 0000000..abfde3c --- /dev/null +++ b/src/app/dashboard/entities/page.tsx @@ -0,0 +1,26 @@ +import { Suspense } from "react"; +import { DataTableSkeleton } from "~/components/data/data-table"; +import { api, HydrateClient } from "~/trpc/server"; +import { EntitiesView } from "./_components/entities-view"; + +export default async function EntitiesPage({ + searchParams, +}: { + searchParams: Promise<{ tab?: string }>; +}) { + const params = await searchParams; + const initialTab = params.tab === "businesses" ? "businesses" : "clients"; + + void api.clients.getAll.prefetch(); + void api.businesses.getAll.prefetch(); + + return ( +
+ + }> + + + +
+ ); +} diff --git a/src/app/dashboard/time-clock/page.tsx b/src/app/dashboard/time-clock/page.tsx index c5d8203..12f9d3d 100644 --- a/src/app/dashboard/time-clock/page.tsx +++ b/src/app/dashboard/time-clock/page.tsx @@ -17,7 +17,7 @@ export default async function TimeClockPage({ } return ( -
+
{ toast.success("Client created successfully"); - router.push("/dashboard/clients"); + router.push("/dashboard/entities?tab=clients"); }, onError: (error) => { toast.error(error.message || "Failed to create client"); @@ -109,7 +109,7 @@ export function ClientForm({ clientId, mode }: ClientFormProps) { const updateClient = api.clients.update.useMutation({ onSuccess: () => { toast.success("Client updated successfully"); - router.push("/dashboard/clients"); + router.push("/dashboard/entities?tab=clients"); }, onError: (error) => { toast.error(error.message || "Failed to update client"); @@ -232,7 +232,7 @@ export function ClientForm({ clientId, mode }: ClientFormProps) { ); if (!confirmed) return; } - router.push("/dashboard/clients"); + router.push("/dashboard/entities?tab=clients"); }; if (mode === "edit" && isLoadingClient) { diff --git a/src/components/forms/invoice-form.tsx b/src/components/forms/invoice-form.tsx index 2dc6551..2b2b68b 100644 --- a/src/components/forms/invoice-form.tsx +++ b/src/components/forms/invoice-form.tsx @@ -52,6 +52,7 @@ import { import { STATUS_OPTIONS } from "./invoice/types"; import type { InvoiceFormData, InvoiceItem } from "./invoice/types"; import type { ParsedLineItem } from "~/lib/parse-line-item"; +import { InvoicePdfPreviewPanel } from "./invoice/invoice-pdf-preview-panel"; import { CountUp } from "~/components/ui/count-up"; @@ -135,6 +136,15 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [activeTab, setActiveTab] = useState("details"); const [previewTab, setPreviewTab] = useState("pdf"); + const [previewPinned, setPreviewPinned] = useState(false); + + useEffect(() => { + const media = window.matchMedia("(min-width: 1024px)"); + const update = () => setPreviewPinned(media.matches); + update(); + media.addEventListener("change", update); + return () => media.removeEventListener("change", update); + }, []); // Queries (Same as before) const { data: clients, isLoading: loadingClients } = @@ -254,17 +264,6 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { [formData], ); - const { data: pdfPreview, isFetching: pdfPreviewLoading } = - api.invoices.previewPdf.useQuery(pdfPreviewInput, { - enabled: - activeTab === "preview" && - previewTab === "pdf" && - Boolean(formData.clientId) && - formData.items.length > 0 && - formData.items.every((item) => item.description.trim() !== ""), - refetchOnWindowFocus: false, - staleTime: 0, - }); const selectedClient = React.useMemo( () => clients?.find((client) => client.id === formData.clientId), [clients, formData.clientId], @@ -480,9 +479,10 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { - +
+ {/* TAB SELECTOR: w-full, p-1, visible background */} - + Preview @@ -863,43 +863,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { - - - - PDF Preview - - - -
- {!formData.clientId ? ( -
- Select a client to generate the PDF preview. -
- ) : formData.items.some( - (item) => item.description.trim() === "", - ) ? ( -
- Add descriptions for all line items to generate the - PDF preview. -
- ) : pdfPreviewLoading && !pdfPreview ? ( -
- Generating server PDF preview... -
- ) : pdfPreview ? ( -