import type { LoaderFunctionArgs } from "react-router"; import { Link, useLoaderData } from "react-router"; import { authenticate } from "../shopify.server"; import db from "../db.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { session } = await authenticate.admin(request); const [settings, recent, counts] = await Promise.all([ db.shopSettings.findUnique({ where: { shopDomain: session.shop } }), db.invoice.findMany({ where: { shopDomain: session.shop }, orderBy: [{ issuedAt: "desc" }], take: 8, }), db.invoice.groupBy({ by: ["status"], where: { shopDomain: session.shop }, _count: { _all: true }, }), ]); const total = counts.reduce((acc, row) => acc + row._count._all, 0); const issuedCount = counts.find((c) => c.status === "issued")?._count._all ?? 0; const sentCount = counts.find((c) => c.status === "sent")?._count._all ?? 0; const cancelledCount = counts.find((c) => c.status === "cancelled")?._count._all ?? 0; const settingsConfigured = !!( settings && settings.companyName && settings.addressLine1 && settings.iban ); return { settingsConfigured, metrics: { total, issuedCount, sentCount, cancelledCount }, recent: recent.map((i) => ({ id: i.id, number: i.invoiceNumber, kind: i.kind, orderName: i.orderName, version: i.version, status: i.status, sentAt: i.sentAt?.toISOString() ?? null, cancelledAt: i.cancelledAt?.toISOString() ?? null, issuedAt: i.issuedAt.toISOString(), pdfUrl: i.pdfUrl, })), }; }; const dateFmt = new Intl.DateTimeFormat("en-GB", { day: "2-digit", month: "short", year: "numeric", }); function formatDate(iso: string | null): string { if (!iso) return "—"; return dateFmt.format(new Date(iso)); } interface RecentInvoice { id: string; number: string; kind: string; orderName: string; version: number; status: string; sentAt: string | null; cancelledAt: string | null; issuedAt: string; pdfUrl: string; } function statusBadge(invoice: RecentInvoice): { tone: "success" | "info" | "critical" | "warning"; label: string; } { if (invoice.cancelledAt) return { tone: "critical", label: "Cancelled" }; if (invoice.kind === "storno") return { tone: "warning", label: "Storno" }; if (invoice.sentAt) return { tone: "success", label: "Sent" }; return { tone: "info", label: "Issued" }; } export default function Index() { const { settingsConfigured, metrics, recent } = useLoaderData(); return ( {!settingsConfigured && ( Complete your company, bank and numbering details so generated invoices are legally compliant. Open settings → )} {recent.length === 0 ? ( No invoices yet Generate your first invoice from the Invoices page or directly from a Shopify order. Open invoices → ) : ( Invoice Order Issued Status PDF {recent.map((invoice) => { const badge = statusBadge(invoice); return ( {invoice.number} {invoice.version > 1 ? ( v{invoice.version} ) : null} {invoice.orderName} {formatDate(invoice.issuedAt)} {badge.label} {invoice.pdfUrl ? ( Open ) : ( )} ); })} )} View all invoices → LinumIQ Invoice generates Austrian-compliant PDF invoices for your Shopify orders. PDFs are stored on Shopify Files and linked to each order via metafields. Trigger generation from the order page (Generate invoice action), via Shopify Flow, or in bulk from the Invoices page. ); } function Metric({ label, value, tone, }: { label: string; value: number; tone?: "info" | "success" | "critical"; }) { const valueTone = tone ?? "neutral"; return ( {label} {value} ); }