367 lines
10 KiB
TypeScript
367 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import { useMemo, useState } from 'react';
|
|
|
|
import { ColumnDef } from '@tanstack/react-table';
|
|
import { Ellipsis } from 'lucide-react';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
import { formatCurrency } from '@kit/shared/utils';
|
|
import { Database } from '@kit/supabase/database';
|
|
import { Badge } from '@kit/ui/badge';
|
|
import { Button } from '@kit/ui/button';
|
|
import { DataTable } from '@kit/ui/data-table';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from '@kit/ui/dropdown-menu';
|
|
import { If } from '@kit/ui/if';
|
|
import { Input } from '@kit/ui/input';
|
|
import { ProfileAvatar } from '@kit/ui/profile-avatar';
|
|
import { Trans } from '@kit/ui/trans';
|
|
|
|
import { RemoveMemberDialog } from './remove-member-dialog';
|
|
import { RoleBadge } from './role-badge';
|
|
import { TransferOwnershipDialog } from './transfer-ownership-dialog';
|
|
import UpdateEmployeeBenefitDialog from './update-employee-benefit-dialog';
|
|
import { UpdateMemberRoleDialog } from './update-member-role-dialog';
|
|
|
|
type Members =
|
|
Database['medreport']['Functions']['get_account_members']['Returns'];
|
|
|
|
interface Permissions {
|
|
canUpdateRole: (roleHierarchy: number) => boolean;
|
|
canRemoveFromAccount: (roleHierarchy: number) => boolean;
|
|
canTransferOwnership: boolean;
|
|
canUpdateBenefit: boolean;
|
|
}
|
|
|
|
type AccountMembersTableProps = {
|
|
members: Members;
|
|
currentUserId: string;
|
|
currentAccountId: string;
|
|
userRoleHierarchy: number;
|
|
isPrimaryOwner: boolean;
|
|
canManageRoles: boolean;
|
|
canUpdateBenefit: boolean;
|
|
membersBenefitsUsage: {
|
|
personal_account_id: string;
|
|
benefit_amount: number;
|
|
benefit_unused_amount: number;
|
|
}[];
|
|
};
|
|
|
|
export function AccountMembersTable({
|
|
members,
|
|
currentUserId,
|
|
currentAccountId,
|
|
isPrimaryOwner,
|
|
userRoleHierarchy,
|
|
canManageRoles,
|
|
canUpdateBenefit,
|
|
membersBenefitsUsage,
|
|
}: AccountMembersTableProps) {
|
|
const [search, setSearch] = useState('');
|
|
const { t } = useTranslation('teams');
|
|
|
|
const permissions = {
|
|
canUpdateRole: (targetRole: number) => {
|
|
return (
|
|
isPrimaryOwner || (canManageRoles && userRoleHierarchy < targetRole)
|
|
);
|
|
},
|
|
canRemoveFromAccount: (targetRole: number) => {
|
|
return (
|
|
isPrimaryOwner || (canManageRoles && userRoleHierarchy < targetRole)
|
|
);
|
|
},
|
|
canTransferOwnership: isPrimaryOwner,
|
|
canUpdateBenefit,
|
|
};
|
|
|
|
const columns = useGetColumns(permissions, {
|
|
currentUserId,
|
|
currentAccountId,
|
|
currentRoleHierarchy: userRoleHierarchy,
|
|
membersBenefitsUsage,
|
|
});
|
|
|
|
const searchString = search.toLowerCase();
|
|
const filteredMembers =
|
|
searchString.length > 0
|
|
? members.filter((member) => {
|
|
const displayName = (
|
|
member.name ??
|
|
member.email.split('@')[0] ??
|
|
''
|
|
).toLowerCase();
|
|
|
|
return (
|
|
displayName.includes(searchString) ||
|
|
member.role.toLowerCase().includes(searchString) ||
|
|
(member.personal_code || '').includes(searchString)
|
|
);
|
|
})
|
|
: members;
|
|
|
|
return (
|
|
<div className={'flex flex-col space-y-2'}>
|
|
<Input
|
|
value={search}
|
|
onInput={(e) => setSearch((e.target as HTMLInputElement).value)}
|
|
placeholder={t(`searchMembersPlaceholder`)}
|
|
/>
|
|
|
|
<DataTable columns={columns} data={filteredMembers} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function useGetColumns(
|
|
permissions: Permissions,
|
|
params: {
|
|
currentUserId: string;
|
|
currentAccountId: string;
|
|
currentRoleHierarchy: number;
|
|
membersBenefitsUsage: {
|
|
personal_account_id: string;
|
|
benefit_amount: number;
|
|
benefit_unused_amount: number;
|
|
}[];
|
|
},
|
|
): ColumnDef<Members[0]>[] {
|
|
const {
|
|
t,
|
|
i18n: { language },
|
|
} = useTranslation('teams');
|
|
|
|
return useMemo(
|
|
() => [
|
|
{
|
|
header: t('memberName'),
|
|
size: 200,
|
|
cell: ({ row }) => {
|
|
const member = row.original;
|
|
const displayName = member.name ?? member.email.split('@')[0];
|
|
const isSelf = member.user_id === params.currentUserId;
|
|
|
|
return (
|
|
<span className={'flex items-center space-x-4 text-left'}>
|
|
<span>
|
|
<ProfileAvatar
|
|
displayName={displayName}
|
|
pictureUrl={member.picture_url}
|
|
/>
|
|
</span>
|
|
|
|
<span>{displayName}</span>
|
|
|
|
<If condition={isSelf}>
|
|
<Badge variant={'outline'}>{t('youLabel')}</Badge>
|
|
</If>
|
|
</span>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
header: t('emailLabel'),
|
|
accessorKey: 'email',
|
|
cell: ({ row }) => {
|
|
return row.original.email ?? '-';
|
|
},
|
|
},
|
|
{
|
|
header: t('personalCode'),
|
|
accessorKey: 'personal_code',
|
|
cell: ({ row }) => {
|
|
return row.original.personal_code ?? '-';
|
|
},
|
|
},
|
|
{
|
|
header: t('distributedBenefitsAmount'),
|
|
cell: ({ row }) => {
|
|
let benefitAmount = params.membersBenefitsUsage.find(
|
|
(usage) => usage.personal_account_id === row.original.id,
|
|
)?.benefit_amount;
|
|
if (typeof benefitAmount !== 'number') {
|
|
benefitAmount = 0;
|
|
}
|
|
|
|
return formatCurrency({
|
|
currencyCode: 'EUR',
|
|
locale: language,
|
|
value: benefitAmount,
|
|
});
|
|
},
|
|
},
|
|
{
|
|
header: t('distributedBenefitsUnusedAmount'),
|
|
cell: ({ row }) => {
|
|
let benefitUnusedAmount = params.membersBenefitsUsage.find(
|
|
(usage) => usage.personal_account_id === row.original.id,
|
|
)?.benefit_unused_amount;
|
|
if (typeof benefitUnusedAmount !== 'number') {
|
|
benefitUnusedAmount = 0;
|
|
}
|
|
|
|
return formatCurrency({
|
|
currencyCode: 'EUR',
|
|
locale: language,
|
|
value: benefitUnusedAmount,
|
|
});
|
|
},
|
|
},
|
|
{
|
|
header: t('roleLabel'),
|
|
cell: ({ row }) => {
|
|
const { role } = row.original;
|
|
|
|
return (
|
|
<span
|
|
className={
|
|
'flex flex-wrap items-center space-y-1 space-x-1 sm:flex-nowrap sm:space-y-0'
|
|
}
|
|
>
|
|
<RoleBadge role={role} />
|
|
</span>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
header: t('joinedAtLabel'),
|
|
cell: ({ row }) => {
|
|
return new Date(row.original.created_at).toLocaleDateString();
|
|
},
|
|
},
|
|
{
|
|
header: '',
|
|
id: 'actions',
|
|
cell: ({ row }) => (
|
|
<ActionsDropdown
|
|
permissions={permissions}
|
|
member={row.original}
|
|
currentTeamAccountId={params.currentAccountId}
|
|
currentRoleHierarchy={params.currentRoleHierarchy}
|
|
/>
|
|
),
|
|
},
|
|
],
|
|
[t, params, permissions, language],
|
|
);
|
|
}
|
|
|
|
function ActionsDropdown({
|
|
permissions,
|
|
member,
|
|
currentTeamAccountId,
|
|
currentRoleHierarchy,
|
|
}: {
|
|
permissions: Permissions;
|
|
member: Members[0];
|
|
currentTeamAccountId: string;
|
|
currentRoleHierarchy: number;
|
|
}) {
|
|
const [isRemoving, setIsRemoving] = useState(false);
|
|
const [isTransferring, setIsTransferring] = useState(false);
|
|
const [isUpdatingRole, setIsUpdatingRole] = useState(false);
|
|
const [isUpdatingBenefit, setIsUpdatingBenefit] = useState(false);
|
|
|
|
const isPrimaryOwner = member.primary_owner_user_id === member.user_id;
|
|
|
|
const memberRoleHierarchy = member.role_hierarchy_level;
|
|
const canUpdateRole = permissions.canUpdateRole(memberRoleHierarchy);
|
|
|
|
const canRemoveFromAccount =
|
|
permissions.canRemoveFromAccount(memberRoleHierarchy);
|
|
|
|
// if has no permission to update role, transfer ownership or remove from account
|
|
// do not render the dropdown menu
|
|
if (
|
|
!canUpdateRole &&
|
|
!permissions.canTransferOwnership &&
|
|
!canRemoveFromAccount
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant={'ghost'} size={'icon'}>
|
|
<Ellipsis className={'h-5 w-5'} />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
|
|
<DropdownMenuContent>
|
|
<If condition={canUpdateRole && !isPrimaryOwner}>
|
|
<DropdownMenuItem onClick={() => setIsUpdatingRole(true)}>
|
|
<Trans i18nKey={'teams:updateRole'} />
|
|
</DropdownMenuItem>
|
|
</If>
|
|
|
|
<If condition={permissions.canTransferOwnership && !isPrimaryOwner}>
|
|
<DropdownMenuItem onClick={() => setIsTransferring(true)}>
|
|
<Trans i18nKey={'teams:transferOwnership'} />
|
|
</DropdownMenuItem>
|
|
</If>
|
|
|
|
<If condition={canRemoveFromAccount && !isPrimaryOwner}>
|
|
<DropdownMenuItem onClick={() => setIsRemoving(true)}>
|
|
<Trans i18nKey={'teams:removeMember'} />
|
|
</DropdownMenuItem>
|
|
</If>
|
|
|
|
<If condition={permissions.canUpdateBenefit}>
|
|
<DropdownMenuItem onClick={() => setIsUpdatingBenefit(true)}>
|
|
<Trans i18nKey={'teams:updateBenefit'} />
|
|
</DropdownMenuItem>
|
|
</If>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
<If condition={isRemoving}>
|
|
<RemoveMemberDialog
|
|
isOpen
|
|
setIsOpen={setIsRemoving}
|
|
teamAccountId={currentTeamAccountId}
|
|
userId={member.user_id}
|
|
/>
|
|
</If>
|
|
|
|
<If condition={isUpdatingRole}>
|
|
<UpdateMemberRoleDialog
|
|
isOpen
|
|
setIsOpen={setIsUpdatingRole}
|
|
userId={member.user_id}
|
|
userRole={member.role}
|
|
teamAccountId={currentTeamAccountId}
|
|
userRoleHierarchy={currentRoleHierarchy}
|
|
/>
|
|
</If>
|
|
|
|
<If condition={isTransferring}>
|
|
<TransferOwnershipDialog
|
|
isOpen
|
|
setIsOpen={setIsTransferring}
|
|
targetDisplayName={member.name ?? member.email}
|
|
accountId={member.account_id}
|
|
userId={member.user_id}
|
|
/>
|
|
</If>
|
|
|
|
<If condition={isUpdatingBenefit}>
|
|
<UpdateEmployeeBenefitDialog
|
|
isOpen
|
|
setIsOpen={setIsUpdatingBenefit}
|
|
accountId={member.account_id}
|
|
userId={member.user_id}
|
|
isEligibleForBenefits={member.is_eligible_for_benefits}
|
|
/>
|
|
</If>
|
|
</>
|
|
);
|
|
}
|