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