feat(billing): enhance health benefit form and yearly expenses overview with employee count and loading state
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { UpdateHealthBenefitSchema } from '@/packages/billing/core/src/schema';
|
||||
import { Database } from '@/packages/supabase/src/database.types';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
@@ -22,32 +24,52 @@ import YearlyExpensesOverview from './yearly-expenses-overview';
|
||||
const HealthBenefitForm = ({
|
||||
account,
|
||||
companyParams,
|
||||
employeeCount,
|
||||
}: {
|
||||
account: Database['medreport']['Tables']['accounts']['Row'];
|
||||
companyParams: Database['medreport']['Tables']['company_params']['Row'];
|
||||
employeeCount: number;
|
||||
}) => {
|
||||
const [currentCompanyParams, setCurrentCompanyParams] =
|
||||
useState<Database['medreport']['Tables']['company_params']['Row']>(
|
||||
companyParams,
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const form = useForm({
|
||||
resolver: zodResolver(UpdateHealthBenefitSchema),
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
occurance: companyParams.benefit_occurance || 'yearly',
|
||||
amount: companyParams.benefit_amount || 0,
|
||||
occurance: currentCompanyParams.benefit_occurance || 'yearly',
|
||||
amount: currentCompanyParams.benefit_amount || 0,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (data: { occurance: string; amount: number }) => {
|
||||
const promise = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await updateHealthBenefit({ ...data, accountId: account.id });
|
||||
setCurrentCompanyParams((prev) => ({
|
||||
...prev,
|
||||
benefit_amount: data.amount,
|
||||
benefit_occurance: data.occurance,
|
||||
}));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
toast.promise(promise, {
|
||||
success: 'Andmed uuendatud',
|
||||
error: 'error',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="flex flex-col gap-6 px-6 text-left"
|
||||
onSubmit={form.handleSubmit((data) => {
|
||||
toast.promise(
|
||||
() => updateHealthBenefit({ ...data, accountId: account.id }),
|
||||
{
|
||||
success: 'success',
|
||||
error: 'error',
|
||||
},
|
||||
);
|
||||
})}
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="mt-8 flex items-center justify-between">
|
||||
<div>
|
||||
@@ -61,13 +83,13 @@ const HealthBenefitForm = ({
|
||||
<Trans i18nKey="billing:description" />
|
||||
</p>
|
||||
</div>
|
||||
<Button type="submit" className="relative">
|
||||
{form.formState.isSubmitting && (
|
||||
<Button type="submit" className="relative" disabled={isLoading}>
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
<div className={cn({ invisible: form.formState.isSubmitting })}>
|
||||
<div className={cn({ invisible: isLoading })}>
|
||||
<Trans i18nKey="account:saveChanges" />
|
||||
</div>
|
||||
</Button>
|
||||
@@ -82,7 +104,7 @@ const HealthBenefitForm = ({
|
||||
<Trans i18nKey="billing:healthBenefitForm.description" />
|
||||
</p>
|
||||
<p className="pt-2 text-2xl font-semibold">
|
||||
{companyParams.benefit_amount || 0} €
|
||||
{currentCompanyParams.benefit_amount || 0} €
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -95,8 +117,8 @@ const HealthBenefitForm = ({
|
||||
|
||||
<div className="flex-1">
|
||||
<YearlyExpensesOverview
|
||||
employeeCount={22}
|
||||
companyParams={companyParams}
|
||||
employeeCount={employeeCount}
|
||||
companyParams={currentCompanyParams}
|
||||
/>
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
<Trans i18nKey="billing:healthBenefitForm.info" />
|
||||
|
||||
@@ -21,7 +21,7 @@ const YearlyExpensesOverview = ({
|
||||
case 'yearly':
|
||||
return (companyParams.benefit_amount / 12).toFixed(2);
|
||||
case 'quarterly':
|
||||
return (companyParams.benefit_amount / 4).toFixed(2);
|
||||
return (companyParams.benefit_amount / 3).toFixed(2);
|
||||
case 'monthly':
|
||||
return companyParams.benefit_amount.toFixed(2);
|
||||
default:
|
||||
@@ -38,7 +38,7 @@ const YearlyExpensesOverview = ({
|
||||
case 'yearly':
|
||||
return companyParams.benefit_amount.toFixed(2);
|
||||
case 'quarterly':
|
||||
return (companyParams.benefit_amount * 4).toFixed(2);
|
||||
return (companyParams.benefit_amount * 3).toFixed(2);
|
||||
case 'monthly':
|
||||
return (companyParams.benefit_amount * 12).toFixed(2);
|
||||
default:
|
||||
@@ -80,8 +80,15 @@ const YearlyExpensesOverview = ({
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="mt-4 flex justify-between">
|
||||
<p className="font-semibold">Kokku</p>
|
||||
<span className="font-semibold">13 200,00 €</span>
|
||||
<p className="font-semibold">
|
||||
<Trans i18nKey="billing:expensesOverview.sum" />
|
||||
</p>
|
||||
<span className="font-semibold">
|
||||
{companyParams.benefit_amount
|
||||
? companyParams.benefit_amount * employeeCount
|
||||
: 0}{' '}
|
||||
€
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createAccountsApi } from '@/packages/features/accounts/src/server/api';
|
||||
import { createTeamAccountsApi } from '@/packages/features/team-accounts/src/server/api';
|
||||
import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client';
|
||||
|
||||
@@ -23,13 +24,20 @@ export const generateMetadata = async () => {
|
||||
|
||||
async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) {
|
||||
const accountSlug = (await params).account;
|
||||
const api = createTeamAccountsApi(getSupabaseServerClient());
|
||||
const client = getSupabaseServerClient();
|
||||
const api = createTeamAccountsApi(client);
|
||||
|
||||
const account = await api.getTeamAccount(accountSlug);
|
||||
const companyParams = await api.getTeamAccountParams(account.id);
|
||||
const accounts = await api.getMembers(accountSlug);
|
||||
|
||||
return (
|
||||
<PageBody>
|
||||
<HealthBenefitForm account={account} companyParams={companyParams} />
|
||||
<HealthBenefitForm
|
||||
account={account}
|
||||
companyParams={companyParams}
|
||||
employeeCount={accounts.length}
|
||||
/>
|
||||
</PageBody>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BadgeX, Ban, ShieldPlus, VenetianMask } from 'lucide-react';
|
||||
import { Tables } from '@kit/supabase/database';
|
||||
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
import { createTeamAccountsApi } from '@kit/team-accounts/api';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
|
||||
import { Badge } from '@kit/ui/badge';
|
||||
@@ -162,7 +163,8 @@ async function PersonalAccountPage(props: { account: Account }) {
|
||||
async function TeamAccountPage(props: {
|
||||
account: Account & { memberships: Membership[] };
|
||||
}) {
|
||||
const members = await getMembers(props.account.slug ?? '');
|
||||
const api = createTeamAccountsApi(getSupabaseServerClient());
|
||||
const members = await api.getMembers(props.account.slug ?? '');
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -386,17 +388,3 @@ async function getMemberships(userId: string) {
|
||||
|
||||
return memberships.data;
|
||||
}
|
||||
|
||||
async function getMembers(accountSlug: string) {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const members = await client.schema('medreport').rpc('get_account_members', {
|
||||
account_slug: accountSlug,
|
||||
});
|
||||
|
||||
if (members.error) {
|
||||
throw members.error;
|
||||
}
|
||||
|
||||
return members.data;
|
||||
}
|
||||
|
||||
@@ -295,6 +295,20 @@ export class TeamAccountsApi {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getMembers(accountSlug: string) {
|
||||
const members = await this.client
|
||||
.schema('medreport')
|
||||
.rpc('get_account_members', {
|
||||
account_slug: accountSlug,
|
||||
});
|
||||
|
||||
if (members.error) {
|
||||
throw members.error;
|
||||
}
|
||||
|
||||
return members.data;
|
||||
}
|
||||
}
|
||||
|
||||
export function createTeamAccountsApi(client: SupabaseClient<Database>) {
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
"title": "Kulude ülevaade 2025 aasta raames",
|
||||
"monthly": "Kulu töötaja kohta kuus *",
|
||||
"yearly": "Maksimaalne kulu inimese kohta kokku aastas *",
|
||||
"total": "Maksimaalne kulu {{employeeCount}} töötaja kohta aastas *"
|
||||
"total": "Maksimaalne kulu {{employeeCount}} töötaja kohta aastas *",
|
||||
"sum": "Kokku"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user