187 lines
6.0 KiB
TypeScript
187 lines
6.0 KiB
TypeScript
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 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 (
|
|
<>
|
|
<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'
|
|
})
|