feat(MED-97): show benefits amount for each member

This commit is contained in:
2025-09-26 15:39:33 +03:00
parent 428cbd9477
commit eb6ef2abe1
6 changed files with 63 additions and 7 deletions

View File

@@ -5,6 +5,7 @@ import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@/packages/supabase/src/database.types'; import { Database } from '@/packages/supabase/src/database.types';
import { loadTeamWorkspace } from '~/home/[account]/_lib/server/team-account-workspace.loader'; import { loadTeamWorkspace } from '~/home/[account]/_lib/server/team-account-workspace.loader';
import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client';
/** /**
* Load data for the members page * Load data for the members page
@@ -15,11 +16,13 @@ export async function loadMembersPageData(
client: SupabaseClient<Database>, client: SupabaseClient<Database>,
slug: string, slug: string,
) { ) {
const workspace = await loadTeamWorkspace(slug);
return Promise.all([ return Promise.all([
loadAccountMembers(client, slug), loadAccountMembers(client, slug),
loadInvitations(client, slug), loadInvitations(client, slug),
canAddMember, canAddMember,
loadTeamWorkspace(slug), workspace,
loadAccountMembersBenefitsUsage(getSupabaseServerAdminClient(), workspace.account.id),
]); ]);
} }
@@ -60,6 +63,27 @@ async function loadAccountMembers(
return data ?? []; return data ?? [];
} }
export async function loadAccountMembersBenefitsUsage(
client: SupabaseClient<Database>,
accountId: string,
): Promise<{
personal_account_id: string;
benefit_amount: number;
}[]> {
const { data, error } = await client
.schema('medreport')
.rpc('get_benefits_usages_for_company_members', {
p_account_id: accountId,
});
if (error) {
console.error('Failed to load account members benefits usage', error);
return [];
}
return data ?? [];
}
/** /**
* Load account invitations * Load account invitations
* @param client * @param client

View File

@@ -42,7 +42,7 @@ async function TeamAccountMembersPage({ params }: TeamAccountMembersPageProps) {
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();
const slug = (await params).account; const slug = (await params).account;
const [members, invitations, canAddMember, { user, account }] = const [members, invitations, canAddMember, { user, account }, membersBenefitsUsage] =
await loadMembersPageData(client, slug); await loadMembersPageData(client, slug);
const canManageRoles = account.permissions.includes('roles.manage'); const canManageRoles = account.permissions.includes('roles.manage');
@@ -96,6 +96,7 @@ async function TeamAccountMembersPage({ params }: TeamAccountMembersPageProps) {
members={members} members={members}
isPrimaryOwner={isPrimaryOwner} isPrimaryOwner={isPrimaryOwner}
canManageRoles={canManageRoles} canManageRoles={canManageRoles}
membersBenefitsUsage={membersBenefitsUsage}
/> />
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -20,6 +20,7 @@ import { If } from '@kit/ui/if';
import { Input } from '@kit/ui/input'; import { Input } from '@kit/ui/input';
import { ProfileAvatar } from '@kit/ui/profile-avatar'; import { ProfileAvatar } from '@kit/ui/profile-avatar';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { formatCurrency } from '@kit/shared/utils';
import { RemoveMemberDialog } from './remove-member-dialog'; import { RemoveMemberDialog } from './remove-member-dialog';
import { RoleBadge } from './role-badge'; import { RoleBadge } from './role-badge';
@@ -42,6 +43,10 @@ type AccountMembersTableProps = {
userRoleHierarchy: number; userRoleHierarchy: number;
isPrimaryOwner: boolean; isPrimaryOwner: boolean;
canManageRoles: boolean; canManageRoles: boolean;
membersBenefitsUsage: {
personal_account_id: string;
benefit_amount: number;
}[];
}; };
export function AccountMembersTable({ export function AccountMembersTable({
@@ -51,6 +56,7 @@ export function AccountMembersTable({
isPrimaryOwner, isPrimaryOwner,
userRoleHierarchy, userRoleHierarchy,
canManageRoles, canManageRoles,
membersBenefitsUsage,
}: AccountMembersTableProps) { }: AccountMembersTableProps) {
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const { t } = useTranslation('teams'); const { t } = useTranslation('teams');
@@ -73,6 +79,7 @@ export function AccountMembersTable({
currentUserId, currentUserId,
currentAccountId, currentAccountId,
currentRoleHierarchy: userRoleHierarchy, currentRoleHierarchy: userRoleHierarchy,
membersBenefitsUsage,
}); });
const filteredMembers = members const filteredMembers = members
@@ -122,9 +129,13 @@ function useGetColumns(
currentUserId: string; currentUserId: string;
currentAccountId: string; currentAccountId: string;
currentRoleHierarchy: number; currentRoleHierarchy: number;
membersBenefitsUsage: {
personal_account_id: string;
benefit_amount: number;
}[];
}, },
): ColumnDef<Members[0]>[] { ): ColumnDef<Members[0]>[] {
const { t } = useTranslation('teams'); const { t, i18n: { language } } = useTranslation('teams');
return useMemo( return useMemo(
() => [ () => [
@@ -168,6 +179,23 @@ function useGetColumns(
return row.original.personal_code ?? '-'; return row.original.personal_code ?? '-';
}, },
}, },
{
header: t('distributedBenefitsAmount'),
cell: ({ row }) => {
const benefitAmount = params.membersBenefitsUsage.find(
(usage) => usage.personal_account_id === row.original.id
)?.benefit_amount;
if (typeof benefitAmount !== 'number') {
return '-';
}
return formatCurrency({
currencyCode: 'EUR',
locale: language,
value: benefitAmount,
});
},
},
{ {
header: t('roleLabel'), header: t('roleLabel'),
cell: ({ row }) => { cell: ({ row }) => {
@@ -175,7 +203,7 @@ function useGetColumns(
const isPrimaryOwner = primary_owner_user_id === user_id; const isPrimaryOwner = primary_owner_user_id === user_id;
return ( return (
<span className={'flex items-center space-x-1'}> <span className={'flex items-center space-x-1 flex-wrap space-y-1 sm:space-y-0 sm:flex-nowrap'}>
<RoleBadge role={role} /> <RoleBadge role={role} />
<If condition={isPrimaryOwner}> <If condition={isPrimaryOwner}>

View File

@@ -160,5 +160,6 @@
"leaveTeamInputDescription": "By leaving the company, you will no longer have access to it.", "leaveTeamInputDescription": "By leaving the company, you will no longer have access to it.",
"reservedNameError": "This name is reserved. Please choose a different one.", "reservedNameError": "This name is reserved. Please choose a different one.",
"specialCharactersError": "This name cannot contain special characters. Please choose a different one.", "specialCharactersError": "This name cannot contain special characters. Please choose a different one.",
"personalCode": "Personal Code" "personalCode": "Personal Code",
"distributedBenefitsAmount": "Assigned benefits"
} }

View File

@@ -193,5 +193,6 @@
"reservedNameError": "See nimi on reserveeritud. Palun vali mõni teine.", "reservedNameError": "See nimi on reserveeritud. Palun vali mõni teine.",
"specialCharactersError": "Nimi ei tohi sisaldada erimärke. Palun vali mõni teine.", "specialCharactersError": "Nimi ei tohi sisaldada erimärke. Palun vali mõni teine.",
"personalCode": "Isikukood", "personalCode": "Isikukood",
"teamOwnerPersonalCodeLabel": "Omaniku isikukood" "teamOwnerPersonalCodeLabel": "Omaniku isikukood",
"distributedBenefitsAmount": "Väljastatud toetus"
} }

View File

@@ -193,5 +193,6 @@
"reservedNameError": "Это имя зарезервировано. Пожалуйста, выберите другое.", "reservedNameError": "Это имя зарезервировано. Пожалуйста, выберите другое.",
"specialCharactersError": "Это имя не может содержать специальные символы. Пожалуйста, выберите другое.", "specialCharactersError": "Это имя не может содержать специальные символы. Пожалуйста, выберите другое.",
"personalCode": "Идентификационный код", "personalCode": "Идентификационный код",
"teamOwnerPersonalCodeLabel": "Идентификационный код владельца" "teamOwnerPersonalCodeLabel": "Идентификационный код владельца",
"distributedBenefitsAmount": "Распределенные выплаты"
} }