update billing
This commit is contained in:
@@ -169,10 +169,10 @@ function List({ items, search }) {
|
||||
}, [search, fuse, items])
|
||||
|
||||
const charges = filtered
|
||||
?.filter((item) => 'course' in item && item?.unit_price > 0)
|
||||
?.filter((item) => item?.unit_price > 0)
|
||||
?.sort(sortBy('enrolled_at'))
|
||||
const credits = filtered
|
||||
?.filter((item) => 'course' in item && item?.unit_price < 0)
|
||||
?.filter((item) => item?.unit_price < 0)
|
||||
?.sort(sortBy('created_at'))
|
||||
|
||||
if (items.length === 0) {
|
||||
@@ -207,6 +207,16 @@ function List({ items, search }) {
|
||||
)
|
||||
}
|
||||
|
||||
const subtotal = filtered
|
||||
?.filter(({ unit_price }) => unit_price > 0)
|
||||
?.reduce((acc, { unit_price }) => acc + unit_price, 0)
|
||||
|
||||
const discounts = filtered
|
||||
?.filter(({ unit_price }) => unit_price < 0)
|
||||
?.reduce((acc, { unit_price }) => acc + unit_price, 0)
|
||||
|
||||
const total = filtered?.reduce((acc, { unit_price }) => acc + unit_price, 0)
|
||||
|
||||
return (
|
||||
<Table className="table-auto w-full">
|
||||
{charges.length ? (
|
||||
@@ -288,16 +298,28 @@ function List({ items, search }) {
|
||||
) : null}
|
||||
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-right pointer-events-none">
|
||||
Subtotal
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Currency>{subtotal}</Currency>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-right pointer-events-none">
|
||||
Descontos
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Currency>{discounts}</Currency>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-right pointer-events-none">
|
||||
Total
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Currency>
|
||||
{filtered
|
||||
?.filter((x) => 'course' in x)
|
||||
?.reduce((acc, { unit_price }) => acc + unit_price, 0)}
|
||||
</Currency>
|
||||
<Currency>{total}</Currency>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
|
||||
@@ -6,7 +6,11 @@ import { ErrorMessage } from '@hookform/error-message'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
|
||||
import { Form } from '@repo/ui/components/ui/form'
|
||||
import { InputGroup, InputGroupInput } from '@repo/ui/components/ui/input-group'
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput
|
||||
} from '@repo/ui/components/ui/input-group'
|
||||
import { Button } from '@repo/ui/components/ui/button'
|
||||
import { Separator } from '@repo/ui/components/ui/separator'
|
||||
import {
|
||||
@@ -17,6 +21,7 @@ import {
|
||||
|
||||
import {
|
||||
MAX_ITEMS,
|
||||
formSchema,
|
||||
type Course,
|
||||
type User
|
||||
} from '../_.$orgid.enrollments.add/data'
|
||||
@@ -39,11 +44,11 @@ type AssignedProps = {
|
||||
export function Assigned({ courses }: AssignedProps) {
|
||||
const { orgid } = useParams()
|
||||
const form = useForm({
|
||||
// resolver: zodResolver(formSchema),
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: { enrollments: [emptyRow] }
|
||||
})
|
||||
const { formState, control, handleSubmit } = form
|
||||
const { fields, insert, remove, append } = useFieldArray({
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
control,
|
||||
name: 'enrollments'
|
||||
})
|
||||
@@ -51,6 +56,10 @@ export function Assigned({ courses }: AssignedProps) {
|
||||
control,
|
||||
name: 'enrollments'
|
||||
})
|
||||
const subtotal = items.reduce(
|
||||
(acc, { course }) => acc + (course?.unit_price || 0),
|
||||
0
|
||||
)
|
||||
|
||||
const onSearch = async (search: string) => {
|
||||
const params = new URLSearchParams({ q: search })
|
||||
@@ -108,6 +117,7 @@ export function Assigned({ courses }: AssignedProps) {
|
||||
{/* Separator only for mobile */}
|
||||
{index >= 1 && <div className="h-2.5 lg:hidden"></div>}
|
||||
|
||||
{/* User */}
|
||||
<Controller
|
||||
control={control}
|
||||
name={`enrollments.${index}.user`}
|
||||
@@ -134,6 +144,7 @@ export function Assigned({ courses }: AssignedProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Course */}
|
||||
<Controller
|
||||
control={control}
|
||||
name={`enrollments.${index}.course`}
|
||||
@@ -161,6 +172,7 @@ export function Assigned({ courses }: AssignedProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Scheduled for */}
|
||||
<Controller
|
||||
control={control}
|
||||
name={`enrollments.${index}.scheduled_for`}
|
||||
@@ -169,7 +181,12 @@ export function Assigned({ courses }: AssignedProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Unit price */}
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Valor unit.
|
||||
</InputGroupAddon>
|
||||
|
||||
<InputGroupInput
|
||||
className="pointer-events-none"
|
||||
tabIndex={-1}
|
||||
@@ -178,6 +195,7 @@ export function Assigned({ courses }: AssignedProps) {
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
{/* Action */}
|
||||
<Button
|
||||
tabIndex={-1}
|
||||
variant="destructive"
|
||||
@@ -190,8 +208,9 @@ export function Assigned({ courses }: AssignedProps) {
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Add button */}
|
||||
<div className="max-lg:mb-2.5">
|
||||
<Button
|
||||
type="button"
|
||||
// @ts-ignore
|
||||
@@ -203,6 +222,72 @@ export function Assigned({ courses }: AssignedProps) {
|
||||
>
|
||||
<PlusIcon /> Adicionar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Subtotal */}
|
||||
<>
|
||||
<div className="col-start-3 flex items-center justify-end text-sm font-medium max-lg:hidden">
|
||||
Subtotal
|
||||
</div>
|
||||
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Subtotal
|
||||
</InputGroupAddon>
|
||||
<InputGroupInput
|
||||
name="subtotal"
|
||||
value={currency.format(subtotal)}
|
||||
className="pointer-events-none text-muted-foreground"
|
||||
readOnly
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
|
||||
{/* Discount */}
|
||||
<>
|
||||
<div className="col-start-3 flex items-center justify-end text-sm font-medium max-lg:hidden">
|
||||
Cupom
|
||||
</div>
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Cupom
|
||||
</InputGroupAddon>
|
||||
<InputGroupInput
|
||||
name="discount"
|
||||
value={currency.format(0)}
|
||||
className="pointer-events-none text-muted-foreground"
|
||||
readOnly
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-xs cursor-pointer h-6 px-2"
|
||||
type="button"
|
||||
>
|
||||
Adicionar
|
||||
</Button>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</>
|
||||
|
||||
{/* Total */}
|
||||
<>
|
||||
<div className="col-start-3 flex items-center justify-end text-sm font-medium max-lg:hidden">
|
||||
Total
|
||||
</div>
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Total
|
||||
</InputGroupAddon>
|
||||
<InputGroupInput
|
||||
name="total"
|
||||
value={currency.format(subtotal)}
|
||||
className="pointer-events-none text-muted-foreground"
|
||||
readOnly
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
|
||||
@@ -5,14 +5,15 @@ import { ErrorMessage } from '@hookform/error-message'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { Form } from '@repo/ui/components/ui/form'
|
||||
import { Button } from '@repo/ui/components/ui/button'
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput
|
||||
} from '@repo/ui/components/ui/input-group'
|
||||
import { Input } from '@repo/ui/components/ui/input'
|
||||
import { Form } from '@repo/ui/components/ui/form'
|
||||
import { Button } from '@repo/ui/components/ui/button'
|
||||
import { Separator } from '@repo/ui/components/ui/separator'
|
||||
|
||||
import { Cell } from '../_.$orgid.enrollments.add/route'
|
||||
@@ -70,6 +71,13 @@ export function Bulk({ courses }: BulkProps) {
|
||||
control,
|
||||
name: 'items'
|
||||
})
|
||||
const subtotal = items.reduce(
|
||||
(acc, { course, quantity }) =>
|
||||
acc +
|
||||
(course?.unit_price || 0) *
|
||||
(Number.isFinite(quantity) && quantity > 0 ? quantity : 1),
|
||||
0
|
||||
)
|
||||
|
||||
const onSubmit = async (data: Schema) => {
|
||||
console.log(data)
|
||||
@@ -98,6 +106,7 @@ export function Bulk({ courses }: BulkProps) {
|
||||
{/* Separator only for mobile */}
|
||||
{index >= 1 && <div className="h-2.5 lg:hidden"></div>}
|
||||
|
||||
{/* Course */}
|
||||
<Controller
|
||||
control={control}
|
||||
name={`items.${index}.course`}
|
||||
@@ -128,7 +137,12 @@ export function Bulk({ courses }: BulkProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Quantity */}
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Qtd.
|
||||
</InputGroupAddon>
|
||||
|
||||
<InputGroupInput
|
||||
type="number"
|
||||
min={1}
|
||||
@@ -182,7 +196,11 @@ export function Bulk({ courses }: BulkProps) {
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
|
||||
{/* Unit price */}
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Valor unit.
|
||||
</InputGroupAddon>
|
||||
<InputGroupInput
|
||||
tabIndex={-1}
|
||||
className="pointer-events-none"
|
||||
@@ -191,7 +209,12 @@ export function Bulk({ courses }: BulkProps) {
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
{/* Total */}
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Total
|
||||
</InputGroupAddon>
|
||||
|
||||
<InputGroupInput
|
||||
tabIndex={-1}
|
||||
className="pointer-events-none"
|
||||
@@ -205,6 +228,7 @@ export function Bulk({ courses }: BulkProps) {
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
{/* Action */}
|
||||
<Button
|
||||
tabIndex={-1}
|
||||
variant="destructive"
|
||||
@@ -217,8 +241,9 @@ export function Bulk({ courses }: BulkProps) {
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Add button */}
|
||||
<div className="max-lg:mb-2.5">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -229,12 +254,77 @@ export function Bulk({ courses }: BulkProps) {
|
||||
})
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
disabled={fields.length == MAX_ITEMS}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
<PlusIcon /> Adicionar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Subtotal */}
|
||||
<>
|
||||
<div className="col-start-3 flex items-center justify-end text-sm font-medium max-lg:hidden">
|
||||
Subtotal
|
||||
</div>
|
||||
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Subtotal
|
||||
</InputGroupAddon>
|
||||
<InputGroupInput
|
||||
name="subtotal"
|
||||
value={currency.format(subtotal)}
|
||||
className="pointer-events-none text-muted-foreground"
|
||||
readOnly
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
|
||||
{/* Discount */}
|
||||
<>
|
||||
<div className="col-start-3 flex items-center justify-end text-sm font-medium max-lg:hidden">
|
||||
Cupom
|
||||
</div>
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Cupom
|
||||
</InputGroupAddon>
|
||||
<InputGroupInput
|
||||
name="discount"
|
||||
value={currency.format(0)}
|
||||
className="pointer-events-none text-muted-foreground"
|
||||
readOnly
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-xs cursor-pointer h-6 px-2"
|
||||
type="button"
|
||||
>
|
||||
Adicionar
|
||||
</Button>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</>
|
||||
|
||||
{/* Total */}
|
||||
<>
|
||||
<div className="col-start-3 flex items-center justify-end text-sm font-medium max-lg:hidden">
|
||||
Total
|
||||
</div>
|
||||
<InputGroup>
|
||||
<InputGroupAddon className="border-r pr-2.5 w-1/3 lg:hidden justify-end">
|
||||
Total
|
||||
</InputGroupAddon>
|
||||
<InputGroupInput
|
||||
name="total"
|
||||
value={currency.format(subtotal)}
|
||||
className="pointer-events-none text-muted-foreground"
|
||||
readOnly
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ export const enrollment = z.object({
|
||||
{
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
access_period: z.number()
|
||||
access_period: z.number(),
|
||||
unit_price: z.number().optional()
|
||||
},
|
||||
{ error: 'Escolha um curso' }
|
||||
)
|
||||
|
||||
@@ -349,8 +349,8 @@ export default function Route({
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
|
||||
<div className="max-lg:mt-2.5">
|
||||
<Button
|
||||
type="button"
|
||||
// @ts-ignore
|
||||
@@ -362,6 +362,8 @@ export default function Route({
|
||||
>
|
||||
<PlusIcon /> Adicionar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user