feat(platform): harden access scoping and delivery baseline
This commit is contained in:
@@ -23,8 +23,9 @@ export function BulkEditModal({ selectedIds, fieldDefs, onClose, onSuccess }: Pr
|
||||
|
||||
const utils = trpc.useUtils();
|
||||
const mutation = trpc.resource.batchUpdateCustomFields.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.resource.list.invalidate();
|
||||
onSuccess: () => {
|
||||
void utils.resource.directory.invalidate();
|
||||
void utils.resource.listStaff.invalidate();
|
||||
onSuccess();
|
||||
onClose();
|
||||
},
|
||||
|
||||
@@ -12,8 +12,11 @@ interface RoleAssignment {
|
||||
isPrimary: boolean;
|
||||
}
|
||||
|
||||
type CountryWithCities = { id: string; metroCities: { id: string; name: string }[] };
|
||||
type ManagementGroupWithLevels = { id: string; levels: { id: string; name: string }[] };
|
||||
type RoleOption = { id: string; name: string; color?: string | null };
|
||||
type CountryOption = { id: string; name: string; metroCities: { id: string; name: string }[] };
|
||||
type OrgUnitOption = { id: string; name: string; level: number; isActive: boolean };
|
||||
type ClientOption = { id: string; name: string };
|
||||
type ManagementGroupOption = { id: string; name: string; levels: { id: string; name: string }[] };
|
||||
|
||||
interface SkillRow {
|
||||
skill: string;
|
||||
@@ -206,35 +209,22 @@ export function ResourceModal({ mode, resource, onClose }: ResourceModalProps) {
|
||||
const { data: mgmtGroups } = trpc.managementLevel.listGroups.useQuery(undefined, { staleTime: 60_000 });
|
||||
const { data: clients } = trpc.clientEntity.list.useQuery(undefined, { staleTime: 60_000 });
|
||||
|
||||
const roleOptions = (availableRoles ?? []) as unknown as RoleOption[];
|
||||
const countryOptions = (countries ?? []) as unknown as CountryOption[];
|
||||
const orgUnitOptions = (orgUnits ?? []) as unknown as OrgUnitOption[];
|
||||
const managementGroupOptions = (mgmtGroups ?? []) as unknown as ManagementGroupOption[];
|
||||
const clientOptions = (clients ?? []) as unknown as ClientOption[];
|
||||
|
||||
// Derive metro cities from selected country
|
||||
const countryRows = (countries ?? []) as unknown as CountryWithCities[];
|
||||
const selectedCountry = countryRows.find((c) => c.id === form.countryId);
|
||||
const selectedCountry = countryOptions.find((c) => c.id === form.countryId);
|
||||
const metroCities = selectedCountry?.metroCities ?? [];
|
||||
|
||||
// Derive levels from selected group
|
||||
const managementGroups = (mgmtGroups ?? []) as unknown as ManagementGroupWithLevels[];
|
||||
const selectedGroup = managementGroups.find((g) => g.id === form.managementLevelGroupId);
|
||||
const selectedGroup = managementGroupOptions.find((g) => g.id === form.managementLevelGroupId);
|
||||
const mgmtLevels = selectedGroup?.levels ?? [];
|
||||
|
||||
const createMutation = trpc.resource.create.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.resource.list.invalidate();
|
||||
onClose();
|
||||
},
|
||||
onError: (err) => {
|
||||
setErrorMsg(err.message ?? "An error occurred while saving.");
|
||||
},
|
||||
});
|
||||
|
||||
const updateMutation = trpc.resource.update.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.resource.list.invalidate();
|
||||
onClose();
|
||||
},
|
||||
onError: (err) => {
|
||||
setErrorMsg(err.message ?? "An error occurred while saving.");
|
||||
},
|
||||
});
|
||||
const createMutation = trpc.resource.create.useMutation();
|
||||
const updateMutation = trpc.resource.update.useMutation();
|
||||
|
||||
const isMutating = createMutation.isPending || updateMutation.isPending;
|
||||
|
||||
@@ -314,16 +304,25 @@ export function ResourceModal({ mode, resource, onClose }: ResourceModalProps) {
|
||||
};
|
||||
}
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setErrorMsg(null);
|
||||
|
||||
const payload = buildPayload();
|
||||
|
||||
if (mode === "create") {
|
||||
createMutation.mutate(payload);
|
||||
} else if (resource) {
|
||||
updateMutation.mutate({ id: resource.id, data: payload });
|
||||
try {
|
||||
if (mode === "create") {
|
||||
await createMutation.mutateAsync(payload);
|
||||
} else if (resource) {
|
||||
await updateMutation.mutateAsync({ id: resource.id, data: payload });
|
||||
}
|
||||
|
||||
void utils.resource.directory.invalidate();
|
||||
void utils.resource.listStaff.invalidate();
|
||||
onClose();
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "An error occurred while saving.";
|
||||
setErrorMsg(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,7 +453,7 @@ export function ResourceModal({ mode, resource, onClose }: ResourceModalProps) {
|
||||
onChange={(e) => setField("roleId", e.target.value)}
|
||||
>
|
||||
<option value="">— Not specified —</option>
|
||||
{(availableRoles ?? []).map((r) => (
|
||||
{roleOptions.map((r) => (
|
||||
<option key={r.id} value={r.id}>{r.name}</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -552,8 +551,8 @@ export function ResourceModal({ mode, resource, onClose }: ResourceModalProps) {
|
||||
}}
|
||||
>
|
||||
<option value="">— Not specified —</option>
|
||||
{(countries ?? []).map((c) => (
|
||||
<option key={c.id} value={c.id}>{(c as unknown as { name: string }).name}</option>
|
||||
{countryOptions.map((c) => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -584,10 +583,10 @@ export function ResourceModal({ mode, resource, onClose }: ResourceModalProps) {
|
||||
onChange={(e) => setField("orgUnitId", e.target.value)}
|
||||
>
|
||||
<option value="">— Not specified —</option>
|
||||
{(orgUnits ?? [])
|
||||
.filter((u) => (u as unknown as { level: number }).level === 7 && (u as unknown as { isActive: boolean }).isActive)
|
||||
{orgUnitOptions
|
||||
.filter((u) => u.level === 7 && u.isActive)
|
||||
.map((u) => (
|
||||
<option key={u.id} value={u.id}>{(u as unknown as { name: string }).name}</option>
|
||||
<option key={u.id} value={u.id}>{u.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -600,8 +599,8 @@ export function ResourceModal({ mode, resource, onClose }: ResourceModalProps) {
|
||||
onChange={(e) => setField("clientUnitId", e.target.value)}
|
||||
>
|
||||
<option value="">— Not specified —</option>
|
||||
{(clients ?? []).map((c) => (
|
||||
<option key={c.id} value={c.id}>{(c as unknown as { name: string }).name}</option>
|
||||
{clientOptions.map((c) => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -620,8 +619,8 @@ export function ResourceModal({ mode, resource, onClose }: ResourceModalProps) {
|
||||
}}
|
||||
>
|
||||
<option value="">— Not specified —</option>
|
||||
{(mgmtGroups ?? []).map((g) => (
|
||||
<option key={g.id} value={g.id}>{(g as unknown as { name: string }).name}</option>
|
||||
{managementGroupOptions.map((g) => (
|
||||
<option key={g.id} value={g.id}>{g.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -895,7 +894,7 @@ export function ResourceModal({ mode, resource, onClose }: ResourceModalProps) {
|
||||
<p className={SECTION_HEADER_CLASS}>Roles</p>
|
||||
|
||||
<div className="space-y-2">
|
||||
{(availableRoles ?? []).map((role) => {
|
||||
{roleOptions.map((role) => {
|
||||
const assignment = form.roles.find((r) => r.roleId === role.id);
|
||||
const isChecked = Boolean(assignment);
|
||||
|
||||
@@ -942,7 +941,7 @@ export function ResourceModal({ mode, resource, onClose }: ResourceModalProps) {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{(availableRoles ?? []).length === 0 && (
|
||||
{roleOptions.length === 0 && (
|
||||
<p className="text-sm text-gray-400 italic">No roles defined yet. Create roles on the Roles page.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user