feat(freight:backoffice): added position type

This commit is contained in:
Michael Abebe
2026-05-26 13:35:49 +03:00
parent b487d47055
commit 54b122bb3a

View File

@@ -12,6 +12,11 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@edr/ui-common";
import {
Building2,
@@ -53,9 +58,16 @@ interface PositionRecord {
key: string;
name: LocaleText;
parentPositionId?: string | null;
positionTypeId?: string | null;
rank?: number;
}
interface PositionTypeRecord {
id: string;
key: string;
name: LocaleText;
}
interface EmployeeUserRecord {
id: string;
name?: LocaleText;
@@ -104,6 +116,7 @@ interface PositionFormState {
nameAm: string;
key: string;
parentPositionId: string;
positionTypeId: string;
}
interface UnitFormState {
@@ -147,6 +160,7 @@ const emptyPositionForm: PositionFormState = {
nameAm: "",
key: "",
parentPositionId: "",
positionTypeId: "",
};
const inputClassName =
@@ -434,6 +448,7 @@ const UserManagementPage = () => {
const [organizations, setOrganizations] = useState<OrganizationRecord[]>([]);
const [units, setUnits] = useState<UnitRecord[]>([]);
const [positions, setPositions] = useState<PositionRecord[]>([]);
const [positionTypes, setPositionTypes] = useState<PositionTypeRecord[]>([]);
const [orgEmployees, setOrgEmployees] = useState<EmployeeRecord[]>([]);
const [positionMembers, setPositionMembers] = useState<EmployeeRecord[]>([]);
const [selectedOrgConfiguration, setSelectedOrgConfiguration] =
@@ -447,9 +462,11 @@ const UserManagementPage = () => {
const [loading, setLoading] = useState(true);
const [membersLoading, setMembersLoading] = useState(false);
const [orgEmployeesLoading, setOrgEmployeesLoading] = useState(false);
const [positionTypesLoading, setPositionTypesLoading] = useState(false);
const [actionError, setActionError] = useState<string | null>(null);
const [actionSuccess, setActionSuccess] = useState<string | null>(null);
const [loadError, setLoadError] = useState<string | null>(null);
const [positionTypesError, setPositionTypesError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
const [unitSearch, setUnitSearch] = useState("");
const [positionSearch, setPositionSearch] = useState("");
@@ -664,6 +681,25 @@ const UserManagementPage = () => {
}
}, []);
const loadPositionTypes = useCallback(async (unitId: string) => {
setPositionTypesLoading(true);
setPositionTypesError(null);
try {
const response = await api.get<ListResponse<PositionTypeRecord>>(
`/position-types/list-with-commons/${unitId}?take=1000&skip=0&orderBy=createdAt:Desc`,
);
setPositionTypes(getItems(response.data));
} catch (error) {
const message = getErrorMessage(error, "Failed to load position types.");
setPositionTypes([]);
setPositionTypesError(message);
throw error;
} finally {
setPositionTypesLoading(false);
}
}, []);
const loadPositionMembers = useCallback(async (positionId: string | null) => {
if (!positionId) {
setPositionMembers([]);
@@ -748,6 +784,8 @@ const UserManagementPage = () => {
if (!selectedOrgId) {
setUnits([]);
setPositions([]);
setPositionTypes([]);
setPositionTypesError(null);
setSelectedUnitId(null);
setSelectedPositionId(null);
setExpandedDepartmentIds(new Set());
@@ -772,6 +810,8 @@ const UserManagementPage = () => {
useEffect(() => {
if (!selectedUnitId) {
setPositions([]);
setPositionTypes([]);
setPositionTypesError(null);
setSelectedPositionId(null);
setExpandedDepartmentIds(new Set());
setPositionMembers([]);
@@ -865,16 +905,27 @@ const UserManagementPage = () => {
});
};
const openCreateSubDepartmentDialog = (parent: PositionRecord) => {
setSelectedPositionId(parent.id);
setExpandedDepartmentIds((current) => new Set(current).add(parent.id));
setCreatePositionMode("create-child");
setPositionForm({
...emptyPositionForm,
parentPositionId: parent.id,
});
const openCreateSubDepartmentDialog = async (parent: PositionRecord) => {
if (!selectedUnit) {
setActionError("Select a unit before creating a department.");
return;
}
resetMessages();
setIsCreatePositionOpen(true);
try {
await loadPositionTypes(selectedUnit.id);
setSelectedPositionId(parent.id);
setExpandedDepartmentIds((current) => new Set(current).add(parent.id));
setCreatePositionMode("create-child");
setPositionForm({
...emptyPositionForm,
parentPositionId: parent.id,
});
setIsCreatePositionOpen(true);
} catch (error) {
setActionError(getErrorMessage(error, "Failed to load position types."));
}
};
const openInviteForDepartment = (position: PositionRecord) => {
@@ -905,26 +956,52 @@ const UserManagementPage = () => {
setIsEditUnitOpen(true);
};
const openCreatePositionDialog = (mode: "create-root" | "create-child") => {
setCreatePositionMode(mode);
setPositionForm({
...emptyPositionForm,
parentPositionId: mode === "create-child" && selectedPosition ? selectedPosition.id : "",
});
const openCreatePositionDialog = async (
mode: "create-root" | "create-child",
unitId = selectedUnit?.id,
) => {
if (!unitId) {
setActionError("Select a unit before creating a department.");
return;
}
resetMessages();
setIsCreatePositionOpen(true);
try {
await loadPositionTypes(unitId);
setCreatePositionMode(mode);
setPositionForm({
...emptyPositionForm,
parentPositionId: mode === "create-child" && selectedPosition ? selectedPosition.id : "",
});
setIsCreatePositionOpen(true);
} catch (error) {
setActionError(getErrorMessage(error, "Failed to load position types."));
}
};
const openEditPositionDialog = (position: PositionRecord) => {
setPositionForm({
id: position.id,
nameEn: position.name.en ?? "",
nameAm: position.name.am ?? "",
key: position.key,
parentPositionId: position.parentPositionId ?? "",
});
const openEditPositionDialog = async (position: PositionRecord) => {
if (!selectedUnit) {
setActionError("Select a unit before editing a department.");
return;
}
resetMessages();
setIsEditPositionOpen(true);
try {
await loadPositionTypes(selectedUnit.id);
setPositionForm({
id: position.id,
nameEn: position.name.en ?? "",
nameAm: position.name.am ?? "",
key: position.key,
parentPositionId: position.parentPositionId ?? "",
positionTypeId: position.positionTypeId ?? "",
});
setIsEditPositionOpen(true);
} catch (error) {
setActionError(getErrorMessage(error, "Failed to load position types."));
}
};
const handleCreateUnit = async (event: React.FormEvent<HTMLFormElement>) => {
@@ -1007,6 +1084,11 @@ const UserManagementPage = () => {
return;
}
if (!positionForm.positionTypeId) {
setActionError("Select a position type before saving the department.");
return;
}
setSubmitting(true);
resetMessages();
@@ -1018,6 +1100,7 @@ const UserManagementPage = () => {
unitId: selectedUnit.id,
organizationId: selectedOrganization.id,
parentPositionId: positionForm.parentPositionId || null,
positionTypeId: positionForm.positionTypeId,
rank: 0,
};
@@ -1419,8 +1502,10 @@ const UserManagementPage = () => {
<DropdownMenuItem
onSelect={(event) => {
event.preventDefault();
void handleSelectUnit(item.id);
openCreatePositionDialog("create-root");
void (async () => {
await handleSelectUnit(item.id);
await openCreatePositionDialog("create-root", item.id);
})();
}}
>
Add Department
@@ -1495,7 +1580,7 @@ const UserManagementPage = () => {
onAddSubDepartment={openCreateSubDepartmentDialog}
onEdit={(position) => {
setSelectedPositionId(position.id);
openEditPositionDialog(position);
void openEditPositionDialog(position);
}}
onDelete={(position) => {
setSelectedPositionId(position.id);
@@ -1804,6 +1889,31 @@ const UserManagementPage = () => {
</button>
</div>
</Field>
<Field label="Position type">
<Select
value={positionForm.positionTypeId || undefined}
onValueChange={(value) =>
setPositionForm((current) => ({ ...current, positionTypeId: value }))
}
disabled={positionTypesLoading || submitting}
>
<SelectTrigger className="w-full rounded-xl">
<SelectValue
placeholder={positionTypesLoading ? "Loading position types..." : "Select position type"}
/>
</SelectTrigger>
<SelectContent>
{positionTypes.map((positionType) => (
<SelectItem key={positionType.id} value={positionType.id}>
{getLocaleLabel(positionType.name, positionType.key)}
</SelectItem>
))}
</SelectContent>
</Select>
{positionTypesError ? (
<p className="mt-2 text-xs text-destructive">{positionTypesError}</p>
) : null}
</Field>
<Field label="Parent position">
<select
className={inputClassName}
@@ -1867,6 +1977,31 @@ const UserManagementPage = () => {
onChange={(event) => setPositionForm((current) => ({ ...current, key: event.target.value }))}
/>
</Field>
<Field label="Position type">
<Select
value={positionForm.positionTypeId || undefined}
onValueChange={(value) =>
setPositionForm((current) => ({ ...current, positionTypeId: value }))
}
disabled={positionTypesLoading || submitting}
>
<SelectTrigger className="w-full rounded-xl">
<SelectValue
placeholder={positionTypesLoading ? "Loading position types..." : "Select position type"}
/>
</SelectTrigger>
<SelectContent>
{positionTypes.map((positionType) => (
<SelectItem key={positionType.id} value={positionType.id}>
{getLocaleLabel(positionType.name, positionType.key)}
</SelectItem>
))}
</SelectContent>
</Select>
{positionTypesError ? (
<p className="mt-2 text-xs text-destructive">{positionTypesError}</p>
) : null}
</Field>
<Field label="Parent position">
<select
className={inputClassName}