fix(frontend): surface department employee loading errors

This commit is contained in:
lizerk 2026-04-14 00:14:55 +03:00
parent d1a7eedcee
commit edc770dc83
2 changed files with 42 additions and 4 deletions

View File

@ -398,6 +398,7 @@ function DepartmentMembersEmptyState() {
function AddMemberModal({
canAssignRoles,
directoryError,
isOpen,
items,
loading,
@ -405,6 +406,7 @@ function AddMemberModal({
optionsLoading,
onClose,
onCreateRole,
onRetryEmployees,
onSave,
onSelect,
onSelectRole,
@ -413,6 +415,7 @@ function AddMemberModal({
selectedRoleId,
}: {
canAssignRoles: boolean;
directoryError: string | null;
isOpen: boolean;
items: Array<{ id: number; name: string }>;
loading: boolean;
@ -420,6 +423,7 @@ function AddMemberModal({
optionsLoading: boolean;
onClose: () => void;
onCreateRole: () => void;
onRetryEmployees: () => Promise<void>;
onSave: () => Promise<void>;
onSelect: (id: number | null) => void;
onSelectRole: (id: number | null) => void;
@ -432,7 +436,7 @@ function AddMemberModal({
optionsLoading,
items.length,
);
const selectDisabled = optionsLoading || items.length === 0;
const selectDisabled = optionsLoading || !!directoryError || items.length === 0;
const submitDisabled = isDepartmentMemberModalSubmitDisabled({
loading,
mode,
@ -477,7 +481,20 @@ function AddMemberModal({
disabled={selectDisabled}
/>
{canAssignRoles ? (
{directoryError ? (
<div className="app-feedback-danger rounded-xl p-3 text-sm">
<p>{directoryError}</p>
<button
type="button"
onClick={() => void onRetryEmployees()}
className="app-action-secondary mt-3 inline-flex rounded-lg px-3 py-2 text-sm"
>
Повторить загрузку
</button>
</div>
) : null}
{canAssignRoles && !directoryError ? (
<div className="space-y-3">
<SearchableSelectSingle
label={mode === "assignRole" ? "Роль *" : "Роль"}
@ -509,7 +526,7 @@ function AddMemberModal({
</div>
) : null}
{helperText ? (
{!directoryError && helperText ? (
<p className="app-text-muted mt-3 text-sm">
{helperText}
</p>
@ -855,6 +872,7 @@ export default function DepartmentDetailPage() {
<AddMemberModal
canAssignRoles={h.userPerms.can_assign_roles}
directoryError={h.employeesDirectoryError}
isOpen={h.addMemberOpen}
items={getDepartmentMemberModalItems(
h.memberModalMode,
@ -869,6 +887,7 @@ export default function DepartmentDetailPage() {
h.closeAddMember();
h.openCreateRole();
}}
onRetryEmployees={h.reloadEmployeesDirectory}
onSave={h.submitAddMember}
onSelect={h.setSelectedMemberId}
onSelectRole={h.setSelectedRoleId}

View File

@ -41,6 +41,7 @@ export type DepartmentPageController = {
departmentDraft: { name: string; description: string };
departmentId: number;
editDepartmentOpen: boolean;
employeesDirectoryError: string | null;
error: string | null;
filteredMembers: DepartmentMemberLink[];
headCandidates: Array<{ id: number; name: string }>;
@ -66,6 +67,7 @@ export type DepartmentPageController = {
openCreateRole: () => void;
openDepartmentEditor: () => void;
openEditRole: (role: DepartmentRole) => void;
reloadEmployeesDirectory: () => Promise<void>;
refreshPage: () => Promise<void>;
saveDepartment: () => Promise<void>;
saveRole: () => Promise<void>;
@ -136,6 +138,7 @@ export function useDepartmentPage(departmentId: number): DepartmentPageControlle
const [userPerms, setUserPerms] = useState<DepartmentUserPermissions | null>(null);
const [allEmployees, setAllEmployees] = useState<User[]>([]);
const [employeesDirectoryLoading, setEmployeesDirectoryLoading] = useState(false);
const [employeesDirectoryError, setEmployeesDirectoryError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [pendingKey, setPendingKey] = useState<SavingKey | null>(null);
@ -166,9 +169,14 @@ export function useDepartmentPage(departmentId: number): DepartmentPageControlle
const loadEmployeesDirectory = useCallback(async () => {
setEmployeesDirectoryLoading(true);
setEmployeesDirectoryError(null);
try {
const employees = await loadAllPages<User>((params) => apiClient.getEmployees(params));
setAllEmployees(sortEmployeesByName(employees.filter((employee) => employee.is_active)));
} catch (loadError) {
const message = getErrorMessage(loadError, "Не удалось загрузить сотрудников");
setEmployeesDirectoryError(message);
throw loadError;
} finally {
setEmployeesDirectoryLoading(false);
}
@ -232,7 +240,12 @@ export function useDepartmentPage(departmentId: number): DepartmentPageControlle
}, [loadCore]);
useEffect(() => {
if (!userPerms || (!userPerms.can_manage && !userPerms.can_change_head)) {
if (
!userPerms ||
(!userPerms.can_manage &&
!userPerms.can_change_head &&
!userPerms.can_assign_roles)
) {
return;
}
if (allEmployees.length > 0) {
@ -292,6 +305,10 @@ export function useDepartmentPage(departmentId: number): DepartmentPageControlle
await loadCore();
}, [loadCore]);
const reloadEmployeesDirectory = useCallback(async () => {
await loadEmployeesDirectory();
}, [loadEmployeesDirectory]);
const updateDepartmentDraft = useCallback((patch: Partial<{ name: string; description: string }>) => {
setDepartmentDraft((current) => ({ ...current, ...patch }));
}, []);
@ -606,6 +623,7 @@ export function useDepartmentPage(departmentId: number): DepartmentPageControlle
departmentDraft,
departmentId,
editDepartmentOpen,
employeesDirectoryError,
employeesDirectoryLoading,
error,
filteredMembers,
@ -631,6 +649,7 @@ export function useDepartmentPage(departmentId: number): DepartmentPageControlle
openCreateRole,
openDepartmentEditor,
openEditRole,
reloadEmployeesDirectory,
refreshPage,
saveDepartment,
saveRole,