Skip to content

Commit

Permalink
feat: scaffold invoice details page
Browse files Browse the repository at this point in the history
Signed-off-by: Prince Muel <[email protected]>
  • Loading branch information
princemuel committed Dec 17, 2023
1 parent abd3311 commit b41ea03
Show file tree
Hide file tree
Showing 8 changed files with 454 additions and 395 deletions.
205 changes: 205 additions & 0 deletions app/components/templates.invoice.desktop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { calculateTotal, formatAmount, hasValues } from "@/helpers/utils";
import { loader } from "@/routes/invoices.$slug";
import { useLoaderData } from "@remix-run/react";
import { format } from "date-fns";
import { Button } from "./button";
import { Text } from "./text";

type Props = { className?: string };

export function InvoiceDesktop({ className }: Props) {
const data = useLoaderData<typeof loader>();
const invoice = data.invoice;
return (
<section className={className}>
<header className="container">
<div className="flex items-center gap-5 rounded-lg bg-white px-8 py-5 shadow-100 dark:bg-brand-700">
<Text as="p" variant="accent">
Status
</Text>

{/* <StatusButton status={invoice?.status} /> */}

<div className="ml-auto flex items-center justify-between gap-2">
<Button variant="soft">Edit</Button>
<Button variant="destructive">Delete</Button>
<Button variant="primary">Mark as Paid</Button>
</div>
</div>
</header>

<article className="pb-20 container">
<div className="flex flex-col gap-12 rounded-lg bg-white px-8 py-8 shadow-100 dark:bg-brand-700">
<div className="flex justify-between gap-7">
<div className="> * + * space-y-3">
<Text as="p" weight="bold" className="text-base uppercase">
<span className="text-brand-400">#</span>
<span>{invoice.slug}</span>
</Text>

<Text as="h1" id="heading-desktop" variant="primary">
{invoice.description}
</Text>
</div>

<address>
<Text as="p" variant="primary" size="xs">
{invoice?.senderAddress?.street}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.senderAddress?.city}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.senderAddress?.postCode}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.senderAddress?.country}
</Text>
</address>
</div>

{/* gap-x-14 */}
<div className="flex justify-between gap-x-[clamp(3rem,10vw,5rem)] gap-y-10 max-md:flex-wrap">
<div className="flex flex-initial flex-col gap-7">
<div className="> * + * space-y-3">
<Text as="p" variant="primary">
Invoice Date
</Text>

<Text
as="time"
dateTime={new Date(invoice?.paymentDue).toISOString()}
size="sm"
className="inline-block"
>
{format(new Date(invoice?.paymentDue), "dd MMM yyyy")}
</Text>
</div>

<div className="> * + * space-y-3">
<Text as="p" variant="primary">
Payment Due
</Text>

<Text
as="time"
dateTime={new Date(invoice?.paymentDue).toISOString()}
size="sm"
className="inline-block"
>
{format(new Date(invoice?.paymentDue), "dd MMM yyyy")}
</Text>
</div>
</div>

<div className="mr-auto flex flex-initial flex-col gap-5">
<div className="> * + * space-y-3">
<Text as="p" variant="primary">
Bill To
</Text>
<Text size="sm" className="truncate">
{invoice?.clientName}
</Text>
</div>

<address>
<Text as="p" variant="primary" size="xs">
{invoice?.clientAddress?.street}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.clientAddress?.city}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.clientAddress?.postCode}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.clientAddress?.country}
</Text>
</address>
</div>

<div className="> * + * flex-1 space-y-3">
<Text as="p" variant="primary">
Sent to
</Text>
<Text size="sm" className="truncate">
{invoice?.clientEmail}
</Text>
</div>
</div>
<table className="grid grid-cols-1 overflow-clip rounded-lg bg-neutral-200 dark:bg-brand-600">
<caption className="sr-only">Items and Services Purchased</caption>

<thead className="px-8 py-5">
<tr className="grid grid-cols-4 justify-items-end">
<Text
as="th"
variant="primary"
size="xs"
className="justify-self-start"
>
Item Name
</Text>
<Text as="th" variant="primary" size="xs" className="">
QTY.
</Text>
<Text as="th" variant="primary" size="xs" className="">
Price
</Text>
<Text as="th" variant="primary" size="xs" className="">
Total
</Text>
</tr>
</thead>

<tbody className="flex flex-col gap-8 px-8 py-5">
{/* {hasValues([]) ? ( */}
{hasValues(invoice?.items || []) ?
invoice.items.map((item) => (
<tr
key={item?.slug}
className="grid grid-cols-4 justify-items-end gap-2"
>
<Text as="td" weight="bold" className="justify-self-start">
{item?.name}
</Text>

<Text as="td" weight="bold">
{item?.quantity}
</Text>

<Text as="td" variant="secondary" weight="bold">
{formatAmount(item?.price)}
</Text>
<Text as="td" variant="secondary" weight="bold">
{formatAmount(item?.total)}
</Text>
</tr>
))
: <tr className="grid grid-cols-4 justify-items-end gap-2">
<Text as="td" weight="bold">
No items to show
</Text>
</tr>
}
</tbody>

<tfoot className="bg-accent-300 px-8 py-5 dark:bg-brand-900">
<tr className="flex items-center justify-between">
<Text as="th">Amount Due</Text>
<Text
as="td"
modifier="inverted"
weight="bold"
className="text-2xl leading-8 -tracking-[0.5px]"
>
{formatAmount(calculateTotal(invoice.items, "total"))}
</Text>
</tr>
</tfoot>
</table>
</div>
</article>
</section>
);
}
176 changes: 176 additions & 0 deletions app/components/templates.invoice.mobile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { calculateTotal, formatAmount, hasValues } from "@/helpers/utils";
import { loader } from "@/routes/invoices.$slug";
import { useLoaderData } from "@remix-run/react";
import { format } from "date-fns";
import { Button } from "./button";
import { Text } from "./text";

type Props = { className?: string };

export function InvoiceMobile({ className }: Props) {
const data = useLoaderData<typeof loader>();
const invoice = data.invoice;
return (
<section className={className}>
<header className="container">
<div className="flex items-center justify-between rounded-lg bg-white px-6 py-5 shadow-100 dark:bg-brand-700">
<Text as="p" variant="accent">
Status
</Text>

{/* <StatusButton status={invoice?.status} /> */}
</div>
</header>

<article className="pb-14 container">
<div className="flex flex-col gap-12 rounded-lg bg-white px-6 py-8 shadow-100 dark:bg-brand-700">
<div className="flex flex-col justify-between gap-7 xs:flex-row">
<div className="> * + * space-y-3">
<Text as="p" weight="bold" className="uppercase">
<span className="text-brand-400">#</span>
<span>{invoice.slug}</span>
</Text>

<Text as="h1" id="heading-mobile" variant="primary">
{invoice.description}
</Text>
</div>

<address>
<Text as="p" variant="primary" size="xs">
{invoice?.senderAddress?.street}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.senderAddress?.city}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.senderAddress?.postCode}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.senderAddress?.country}
</Text>
</address>
</div>

<div className="flex justify-between gap-x-14 gap-y-10 max-md:flex-wrap">
<div className="flex flex-initial flex-col gap-7">
<div className="> * + * space-y-3">
<Text as="p" variant="primary">
Invoice Date
</Text>

<Text
as="time"
dateTime={new Date(invoice?.paymentDue).toISOString()}
size="sm"
className="inline-block"
>
{format(new Date(invoice?.paymentDue), "dd MMM yyyy")}
</Text>
</div>

<div className="> * + * space-y-3">
<Text as="p" variant="primary">
Payment Due
</Text>

<Text
as="time"
dateTime={new Date(invoice?.paymentDue).toISOString()}
size="sm"
className="inline-block"
>
{format(new Date(invoice?.paymentDue), "dd MMM yyyy")}
</Text>
</div>
</div>

<div className="mr-auto flex flex-initial flex-col gap-5">
<div className="> * + * space-y-3">
<Text as="p" variant="primary">
Bill To
</Text>
<Text size="sm" className="truncate">
{invoice?.clientName}
</Text>
</div>

<address>
<Text as="p" variant="primary" size="xs">
{invoice?.clientAddress?.street}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.clientAddress?.city}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.clientAddress?.postCode}
</Text>
<Text as="p" variant="primary" size="xs">
{invoice?.clientAddress?.country}
</Text>
</address>
</div>

<div className="> * + * flex-1 space-y-3">
<Text as="p" variant="primary">
Sent to
</Text>
<Text size="sm" className="truncate">
{invoice?.clientEmail}
</Text>
</div>
</div>

<section className="overflow-clip rounded-lg bg-neutral-200 dark:bg-brand-600">
<ul className="flex flex-col gap-6 p-6 ">
{hasValues(invoice?.items || []) ?
invoice.items.map((item) => (
<li key={item?.slug}>
<article className="flex flex-col gap-2">
<header className="flex items-center justify-between">
<Text as="h4" weight="bold">
{item?.name}
</Text>

<Text as="output" weight="bold">
{formatAmount(item?.total)}
</Text>
</header>

<div className="">
<Text as="p" variant="secondary" weight="bold">
{item?.quantity} x {formatAmount(item?.price)}
</Text>
</div>
</article>
</li>
))
: <li></li>}
</ul>

<footer className="flex items-center justify-between bg-accent-300 p-6 dark:bg-brand-900">
<Text as="h4">Grand Total</Text>

<Text
as="output"
modifier="inverted"
weight="bold"
className="text-xl leading-8 -tracking-[0.42px]"
>
{formatAmount(calculateTotal(invoice.items, "total"))}
</Text>
</footer>
</section>
</div>
</article>

<div className="sticky bottom-0 w-full bg-white p-6 shadow-100 dark:bg-brand-700">
<div className="flex items-center justify-between gap-2">
<Button variant="soft">Edit</Button>
<Button variant="destructive">Delete</Button>
<Button variant="primary">Mark as Paid</Button>
</div>
</div>
</section>
);
}
Loading

0 comments on commit b41ea03

Please sign in to comment.