'use client';
import { useEffect, useMemo, useState } from 'react';
import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select';
import {
createCrmUserRoleAssignmentMutation,
deleteCrmUserRoleAssignmentMutation,
updateCrmUserRoleAssignmentMutation
} from '../api/mutations';
import { crmUserRoleAssignmentsQueryOptions } from '../api/queries';
import type {
CrmUserRoleAssignmentMutationPayload,
CrmUserRoleAssignmentRecord,
UpdateCrmUserRoleAssignmentPayload
} from '../api/types';
type AssignmentEditorState = {
branchScopeMode: CrmUserRoleAssignmentMutationPayload['branchScopeMode'];
branchScopeIds: string[];
productTypeScopeMode: CrmUserRoleAssignmentMutationPayload['productTypeScopeMode'];
productTypeScopeIds: string[];
isPrimary: boolean;
isActive: boolean;
};
const emptyCreateState: CrmUserRoleAssignmentMutationPayload = {
userId: '',
roleProfileId: '',
branchScopeMode: 'inherit',
branchScopeIds: [],
productTypeScopeMode: 'inherit',
productTypeScopeIds: [],
isPrimary: false,
isActive: true
};
function ScopeSelector(props: {
label: string;
mode: CrmUserRoleAssignmentMutationPayload['branchScopeMode'];
ids: string[];
options: Array<{ id: string; label: string; code: string }>;
onModeChange: (value: CrmUserRoleAssignmentMutationPayload['branchScopeMode']) => void;
onIdsChange: (value: string[]) => void;
}) {
return (
{props.label}
Inherit role profile
All
Selected
None
{props.mode === 'selected' ? (
{props.options.map((option) => {
const checked = props.ids.includes(option.id);
return (
{
props.onIdsChange(
value === true
? [...new Set([...props.ids, option.id])]
: props.ids.filter((item) => item !== option.id)
);
}}
/>
{option.label}
{option.code}
);
})}
) : null}
);
}
export function UserRoleAssignmentsPage() {
const { data } = useSuspenseQuery(crmUserRoleAssignmentsQueryOptions());
const [selectedId, setSelectedId] = useState(data.assignments[0]?.id ?? null);
const [createState, setCreateState] =
useState(emptyCreateState);
const [editorState, setEditorState] = useState(null);
const selectedAssignment = useMemo(
() => data.assignments.find((assignment) => assignment.id === selectedId) ?? null,
[data.assignments, selectedId]
);
useEffect(() => {
if (!selectedAssignment) {
setEditorState(null);
return;
}
setEditorState({
branchScopeMode: selectedAssignment.branchScopeMode,
branchScopeIds: selectedAssignment.branchScopeIds,
productTypeScopeMode: selectedAssignment.productTypeScopeMode,
productTypeScopeIds: selectedAssignment.productTypeScopeIds,
isPrimary: selectedAssignment.isPrimary,
isActive: selectedAssignment.isActive
});
}, [selectedAssignment]);
const createMutation = useMutation({
...createCrmUserRoleAssignmentMutation,
onSuccess: () => {
toast.success('CRM role assignment created successfully');
setCreateState(emptyCreateState);
},
onError: (error) =>
toast.error(
error instanceof Error ? error.message : 'Unable to create CRM role assignment'
)
});
const updateMutation = useMutation({
...updateCrmUserRoleAssignmentMutation,
onSuccess: () => toast.success('CRM role assignment updated successfully'),
onError: (error) =>
toast.error(
error instanceof Error ? error.message : 'Unable to update CRM role assignment'
)
});
const deleteMutation = useMutation({
...deleteCrmUserRoleAssignmentMutation,
onSuccess: () => {
toast.success('CRM role assignment deleted successfully');
setSelectedId(null);
},
onError: (error) =>
toast.error(
error instanceof Error ? error.message : 'Unable to delete CRM role assignment'
)
});
async function handleCreate() {
if (!createState.userId || !createState.roleProfileId) {
toast.error('User and role profile are required');
return;
}
await createMutation.mutateAsync(createState);
}
async function handleUpdate() {
if (!selectedAssignment || !editorState) {
return;
}
await updateMutation.mutateAsync({
id: selectedAssignment.id,
payload: editorState satisfies UpdateCrmUserRoleAssignmentPayload
});
}
return (
Assign CRM Role
One user can hold multiple CRM roles in the same organization. Primary role is for
display; effective access unions across active assignments.
User
setCreateState({ ...createState, userId: value })}
>
{data.users.map((user) => (
{user.name} ({user.email})
))}
Role Profile
setCreateState({ ...createState, roleProfileId: value })
}
>
{data.roleProfiles.map((role) => (
{role.name} ({role.code})
))}
setCreateState({
...createState,
branchScopeMode: value,
branchScopeIds: value === 'selected' ? createState.branchScopeIds : []
})
}
onIdsChange={(value) => setCreateState({ ...createState, branchScopeIds: value })}
/>
setCreateState({
...createState,
productTypeScopeMode: value,
productTypeScopeIds: value === 'selected' ? createState.productTypeScopeIds : []
})
}
onIdsChange={(value) =>
setCreateState({ ...createState, productTypeScopeIds: value })
}
/>
setCreateState({ ...createState, isPrimary: value === true })
}
/>
Primary role
setCreateState({ ...createState, isActive: value === true })
}
/>
Active
void handleCreate()} isLoading={createMutation.isPending}>
Assign Role
Existing Assignments
Active and historical CRM role assignments in this organization.
{data.assignments.map((assignment) => (
setSelectedId(assignment.id)}
className={`w-full rounded-lg border p-3 text-left transition ${
assignment.id === selectedId
? 'border-primary bg-primary/5'
: 'hover:bg-muted/40'
}`}
>
{assignment.userName}
{assignment.isPrimary ? Primary : null}
{!assignment.isActive ? Inactive : null}
{assignment.userEmail}
{assignment.roleProfileName} ({assignment.roleProfileCode})
))}
Assignment Detail
Update scope, activation, and primary-role display for the selected CRM assignment.
{!selectedAssignment || !editorState ? (
Select an assignment to edit.
) : (
<>
User
{selectedAssignment.userName}
{selectedAssignment.userEmail}
Role Profile
{selectedAssignment.roleProfileName}
{selectedAssignment.roleProfileCode}
setEditorState({
...editorState,
branchScopeMode: value,
branchScopeIds: value === 'selected' ? editorState.branchScopeIds : []
})
}
onIdsChange={(value) =>
setEditorState({ ...editorState, branchScopeIds: value })
}
/>
setEditorState({
...editorState,
productTypeScopeMode: value,
productTypeScopeIds:
value === 'selected' ? editorState.productTypeScopeIds : []
})
}
onIdsChange={(value) =>
setEditorState({ ...editorState, productTypeScopeIds: value })
}
/>
setEditorState({ ...editorState, isPrimary: value === true })
}
/>
Primary role
setEditorState({ ...editorState, isActive: value === true })
}
/>
Active
void handleUpdate()} isLoading={updateMutation.isPending}>
Save Assignment
void deleteMutation.mutateAsync(selectedAssignment.id)}
isLoading={deleteMutation.isPending}
>
Remove Assignment
>
)}
);
}