mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
feat(freight:backoffice): added position type
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user