diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/assigned.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/assigned.tsx index c6d75a2..4dcd76e 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/assigned.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/assigned.tsx @@ -4,6 +4,7 @@ import { useForm, useFieldArray, Controller, useWatch } from 'react-hook-form' import { useParams } from 'react-router' 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 { @@ -31,6 +32,9 @@ import { ScheduledForInput } from '../_.$orgid.enrollments.add/scheduled-for' import { Cell } from '../_.$orgid.enrollments.add/route' import { CoursePicker } from '../_.$orgid.enrollments.add/course-picker' import { UserPicker } from '../_.$orgid.enrollments.add/user-picker' +import { Summary } from './bulk' +import { applyDiscount } from './discount' +import { currency } from './utils' const emptyRow = { user: undefined, @@ -38,6 +42,18 @@ const emptyRow = { scheduled_for: undefined } +const formSchema_ = formSchema.extend({ + coupon: z + .object({ + code: z.string(), + type: z.enum(['FIXED', 'PERCENT']), + amount: z.number().positive() + }) + .optional() +}) + +type Schema = z.infer + type AssignedProps = { onSubmit: (value: any) => void | Promise courses: Promise<{ hits: Course[] }> @@ -46,10 +62,10 @@ type AssignedProps = { export function Assigned({ courses, onSubmit }: AssignedProps) { const { orgid } = useParams() const form = useForm({ - resolver: zodResolver(formSchema), + resolver: zodResolver(formSchema_), defaultValues: { enrollments: [emptyRow] } }) - const { formState, control, handleSubmit } = form + const { formState, control, handleSubmit, setValue } = form const { fields, remove, append } = useFieldArray({ control, name: 'enrollments' @@ -58,10 +74,15 @@ export function Assigned({ courses, onSubmit }: AssignedProps) { control, name: 'enrollments' }) + const coupon = useWatch({ control, name: 'coupon' }) const subtotal = items.reduce( (acc, { course }) => acc + (course?.unit_price || 0), 0 ) + const discount = coupon + ? applyDiscount(subtotal, coupon.amount, coupon.type) * -1 + : 0 + const total = subtotal > 0 ? subtotal + discount : 0 const onSearch = async (search: string) => { const params = new URLSearchParams({ q: search }) @@ -70,7 +91,7 @@ export function Assigned({ courses, onSubmit }: AssignedProps) { return hits } - const onSubmit_ = async (data: any) => { + const onSubmit_ = async (data: Schema) => { await onSubmit(data) } @@ -193,7 +214,7 @@ export function Assigned({ courses, onSubmit }: AssignedProps) { className="pointer-events-none" tabIndex={-1} readOnly - value={currency.format(unit_price)} + value={currency(unit_price)} /> @@ -226,69 +247,7 @@ export function Assigned({ courses, onSubmit }: AssignedProps) { - {/* Subtotal */} - <> -
- Subtotal -
- - - - Subtotal - - - - - - {/* Discount */} - <> -
- Cupom -
- - - Cupom - - - - - - - - - {/* Total */} - <> -
- Total -
- - - Total - - - - + @@ -307,8 +266,3 @@ export function Assigned({ courses, onSubmit }: AssignedProps) { ) } - -const currency = new Intl.NumberFormat('pt-BR', { - style: 'currency', - currency: 'BRL' -}) diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/bulk.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/bulk.tsx index e8db762..60a593b 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/bulk.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/bulk.tsx @@ -1,6 +1,12 @@ import { Fragment } from 'react' import { MinusIcon, PlusIcon, Trash2Icon, XIcon } from 'lucide-react' -import { useForm, useFieldArray, Controller, useWatch } from 'react-hook-form' +import { + useForm, + useFieldArray, + Controller, + useWatch, + type UseFormSetValue +} from 'react-hook-form' import { ErrorMessage } from '@hookform/error-message' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' @@ -15,13 +21,14 @@ 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 { Spinner } from '@repo/ui/components/ui/spinner' +import { Kbd } from '@repo/ui/components/ui/kbd' +import { Abbr } from '@repo/ui/components/abbr' import { Cell } from '../_.$orgid.enrollments.add/route' import { CoursePicker } from '../_.$orgid.enrollments.add/course-picker' import { MAX_ITEMS, type Course } from '../_.$orgid.enrollments.add/data' -import { Discount } from './discount' -import { Kbd } from '@repo/ui/components/ui/kbd' -import { Abbr } from '@repo/ui/components/abbr' +import { Discount, applyDiscount } from './discount' +import { currency } from './utils' const emptyRow = { course: undefined @@ -93,7 +100,7 @@ export function Bulk({ courses, onSubmit }: BulkProps) { const discount = coupon ? applyDiscount(subtotal, coupon.amount, coupon.type) * -1 : 0 - const total = subtotal > 0 ? subtotal - discount : 0 + const total = subtotal > 0 ? subtotal + discount : 0 const onSubmit_ = async (data: Schema) => { await onSubmit(data) @@ -277,101 +284,7 @@ export function Bulk({ courses, onSubmit }: BulkProps) { - {/* Subtotal */} - <> -
- Subtotal -
- - - - Subtotal - - - - - - {/* Discount */} - <> -
- {coupon ? ( - - Descontos - - {coupon.code} - - - ) : ( - <>Cupom - )} -
- - - - {coupon ? <>Descontos : <>Cupom} - - - - - - {coupon ? ( - { - setValue('coupon', undefined) - }} - > - - - ) : ( - { - setValue('coupon', { - code: sk, - amount: discount_amount, - type: discount_type - }) - }} - /> - )} - - - - - {/* Total */} - <> -
- Total -
- - - Total - - - - + @@ -391,25 +304,122 @@ export function Bulk({ courses, onSubmit }: BulkProps) { ) } -function currency(value: number) { - return new Intl.NumberFormat('pt-BR', { - style: 'currency', - currency: 'BRL' - }).format(value) -} -function applyDiscount( - subtotal: number, - discountAmount: number, - discountType: 'FIXED' | 'PERCENT' -) { - if (subtotal <= 0) { - return 0 +type SummaryProps = { + subtotal: number + total: number + discount: number + coupon?: { + code: string + type: 'FIXED' | 'PERCENT' + amount: number } - - const amount = - discountType === 'PERCENT' - ? (subtotal * discountAmount) / 100 - : discountAmount - - return Math.min(amount, subtotal) + setValue: UseFormSetValue +} + +export function Summary({ + subtotal, + total, + discount, + coupon, + setValue +}: SummaryProps) { + return ( + <> + {/* Subtotal */} + <> +
+ Subtotal +
+ + + + Subtotal + + + + + + {/* Discount */} + <> +
+ {coupon ? ( + + Descontos + + {coupon.code} + + + ) : ( + <>Cupom + )} +
+ + + + {coupon ? <>Descontos : <>Cupom} + + + + + + {coupon ? ( + { + setValue('coupon', undefined) + }} + > + + + ) : ( + { + setValue('coupon', { + code: sk, + amount: discount_amount, + type: discount_type + }) + }} + /> + )} + + + + + {/* Total */} + <> +
+ Total +
+ + + Total + + + + + + ) } diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/discount.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/discount.tsx index 4ae1af3..c5ba0c7 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/discount.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/discount.tsx @@ -134,3 +134,20 @@ export function Discount({ onChange, ...props }: DiscountProps) { ) } + +export function applyDiscount( + subtotal: number, + discountAmount: number, + discountType: 'FIXED' | 'PERCENT' +) { + if (subtotal <= 0) { + return 0 + } + + const amount = + discountType === 'PERCENT' + ? (subtotal * discountAmount) / 100 + : discountAmount + + return Math.min(amount, subtotal) +} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/utils.ts b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/utils.ts new file mode 100644 index 0000000..582e948 --- /dev/null +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/utils.ts @@ -0,0 +1,6 @@ +export function currency(value: number) { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL' + }).format(value) +}