Files
duty-teller/webapp-next/src/components/admin/hooks/use-admin-reassign.ts
Nikolay Tatarinov fa22976e75 feat: enhance Mini App design guidelines and refactor layout components
- Updated Mini App design guidelines to include detailed instructions on UI changes, accessibility rules, and verification processes.
- Refactored multiple components to utilize `MiniAppScreen` and `MiniAppScreenContent` for consistent layout structure across the application.
- Improved error handling in `GlobalError` and `NotFound` components by integrating new layout components for better user experience.
- Introduced new hooks for admin functionality, streamlining access checks and data loading processes.
- Enhanced documentation to reflect changes in design policies and component usage, ensuring clarity for future development.
2026-03-06 17:51:33 +03:00

117 lines
3.6 KiB
TypeScript

"use client";
import { useCallback, useEffect, useState, type Dispatch, type SetStateAction } from "react";
import { AccessDeniedError, patchAdminDuty, type UserForAdmin } from "@/lib/api";
import { triggerHapticLight } from "@/lib/telegram-haptic";
import type { DutyWithUser } from "@/types";
export interface UseAdminReassignOptions {
initDataRaw: string | undefined;
lang: "ru" | "en";
users: UserForAdmin[];
setDuties: Dispatch<SetStateAction<DutyWithUser[]>>;
t: (key: string, params?: Record<string, string>) => string;
}
export function useAdminReassign({
initDataRaw,
lang,
users,
setDuties,
t,
}: UseAdminReassignOptions) {
const [selectedDuty, setSelectedDuty] = useState<DutyWithUser | null>(null);
const [selectedUserId, setSelectedUserId] = useState<number | "">("");
const [saving, setSaving] = useState(false);
const [reassignErrorKey, setReassignErrorKey] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [sheetExiting, setSheetExiting] = useState(false);
const closeReassign = useCallback(() => {
setSelectedDuty(null);
setSelectedUserId("");
setReassignErrorKey(null);
setSheetExiting(false);
}, []);
useEffect(() => {
if (!sheetExiting) return;
const fallback = window.setTimeout(() => {
closeReassign();
}, 320);
return () => window.clearTimeout(fallback);
}, [sheetExiting, closeReassign]);
const openReassign = useCallback((duty: DutyWithUser) => {
setSelectedDuty(duty);
setSelectedUserId(duty.user_id);
setReassignErrorKey(null);
}, []);
const requestCloseSheet = useCallback(() => {
setSheetExiting(true);
}, []);
const handleReassign = useCallback(() => {
if (!selectedDuty || selectedUserId === "" || !initDataRaw) return;
if (selectedUserId === selectedDuty.user_id) {
closeReassign();
return;
}
setSaving(true);
setReassignErrorKey(null);
patchAdminDuty(selectedDuty.id, selectedUserId, initDataRaw, lang)
.then((updated) => {
setDuties((prev) =>
prev.map((d) =>
d.id === updated.id
? {
...d,
user_id: updated.user_id,
full_name:
users.find((u) => u.id === updated.user_id)?.full_name ?? d.full_name,
}
: d
)
);
setSuccessMessage(t("admin.reassign_success"));
try {
triggerHapticLight();
} catch {
// Haptic not available (e.g. non-Telegram).
}
requestCloseSheet();
setTimeout(() => setSuccessMessage(null), 3000);
})
.catch((e) => {
if (e instanceof AccessDeniedError) {
setReassignErrorKey("admin.reassign_error_denied");
} else if (e instanceof Error && /not found|не найден/i.test(e.message)) {
setReassignErrorKey("admin.reassign_error_not_found");
} else if (
e instanceof TypeError ||
(e instanceof Error && (e.message === "Failed to fetch" || e.message === "Load failed"))
) {
setReassignErrorKey("admin.reassign_error_network");
} else {
setReassignErrorKey("admin.reassign_error_generic");
}
})
.finally(() => setSaving(false));
}, [selectedDuty, selectedUserId, initDataRaw, lang, users, closeReassign, requestCloseSheet, t, setDuties]);
return {
selectedDuty,
selectedUserId,
setSelectedUserId,
saving,
reassignErrorKey,
successMessage,
sheetExiting,
openReassign,
requestCloseSheet,
handleReassign,
closeReassign,
};
}