add billing

This commit is contained in:
2025-12-12 20:28:47 -03:00
parent 3147ec2317
commit c516960b01
16 changed files with 496 additions and 97 deletions

View File

@@ -1,21 +1,186 @@
import type { Route } from './+types/route'
import { DateTime } from 'luxon'
import { Suspense } from 'react'
import { ClockIcon } from 'lucide-react'
import { request as req } from '@repo/util/request'
import { Skeleton } from '@repo/ui/components/skeleton'
import { Await } from 'react-router'
import { billingPeriod, formatDate } from './util'
import { Card, CardContent } from '@repo/ui/components/ui/card'
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow
} from '@repo/ui/components/ui/table'
import { Abbr } from '@repo/ui/components/abbr'
import { RangePeriod } from './range-period'
import { Button } from '@repo/ui/components/ui/button'
export function meta({}) {
return [{ title: 'Resumo de cobranças' }]
}
export default function Route({}: Route.ComponentProps) {
export async function loader({ context, request, params }: Route.LoaderArgs) {
const { searchParams } = new URL(request.url)
const subscription = await req({
url: `/orgs/${params.orgid}/subscription`,
context,
request
}).then((r) => r.json())
const [startDate, endDate] = billingPeriod(
subscription?.billing_day,
new Date()
)
const start = searchParams.get('start') || formatDate(startDate)
const end = searchParams.get('end') || formatDate(endDate)
const billing = req({
url: `/orgs/${params.orgid}/billing?start_date=${start}&end_date=${end}`,
context,
request
}).then((r) => r.json())
return {
subscription,
billing,
startDate: DateTime.fromISO(start).toJSDate(),
endDate: DateTime.fromISO(end).toJSDate()
}
}
export default function Route({
loaderData: { subscription, billing, startDate, endDate }
}: Route.ComponentProps) {
const sk = `START#${formatDate(startDate)}#END#${formatDate(endDate)}`
return (
<>
<div className="space-y-0.5 mb-8">
<h1 className="text-2xl font-bold tracking-tight">
Resumo de cobranças
</h1>
<p className="text-muted-foreground">
Acompanhe as cobranças em tempo real e garanta mais eficiência no
controle financeiro.
</p>
</div>
<Suspense fallback={<Skeleton />}>
<div className="space-y-0.5 mb-8">
<h1 className="text-2xl font-bold tracking-tight">
Resumo de cobranças
</h1>
<p className="text-muted-foreground">
Acompanhe as cobranças em tempo real e garanta mais eficiência no
controle financeiro.
</p>
</div>
<Await resolve={billing}>
{({ items }) => {
const billing = items.find((item) => item.sk === sk)
return (
<Card>
<CardContent className="space-y-2.5">
<div className="flex max-lg:flex-col gap-2.5">
<Button
className="pointer-events-none"
variant="outline"
asChild
>
<span>
<ClockIcon className="size-3.5" /> {billing?.status}
</span>
</Button>
<RangePeriod
startDate={startDate}
endDate={endDate}
billingDay={subscription.billing_day}
/>
</div>
<Table className="table-auto w-full">
<TableHeader>
<TableRow>
<TableHead>Colaborador</TableHead>
<TableHead>Curso</TableHead>
<TableHead>Matriculado por</TableHead>
<TableHead>Matriculado em</TableHead>
<TableHead>Valor unit.</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{items
?.filter((item) => 'course' in item)
?.map(
(
{
user,
course,
author: created_by,
unit_price,
enrolled_at
},
index
) => (
<TableRow key={index}>
<TableCell>
<Abbr>{user.name}</Abbr>
</TableCell>
<TableCell>
<Abbr>{course.name}</Abbr>
</TableCell>
<TableCell>
<Abbr>
{created_by ? created_by.name : 'N/A'}
</Abbr>
</TableCell>
<TableCell>
{datetime.format(new Date(enrolled_at))}
</TableCell>
<TableCell>
{currency.format(unit_price)}
</TableCell>
</TableRow>
)
)}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={4} className="text-right">
Total
</TableCell>
<TableCell>
{currency.format(
items
?.filter((item) => 'course' in item)
.reduce(
(acc, { unit_price }) => acc + unit_price,
0
)
)}
</TableCell>
</TableRow>
</TableFooter>
</Table>
</CardContent>
</Card>
)
}}
</Await>
</Suspense>
</>
)
}
const currency = new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
})
const datetime = new Intl.DateTimeFormat('pt-BR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})