feat(resources): one tabbed page per resource

In the resource-first model a resource is the unit of capacity, so its
working hours, holidays, services, skills and categories belong to it — not
scattered across a list page's modals plus a separate calendar page.

/admin/resources/{uuid} now carries six tabs and the active tab lives in the
query string, so back and refresh land on the same view. The old
/calendar URL redirects to ?tab=hours instead of 404ing.

The skills and services modal bodies became panels the tab renders directly;
the modals are now thin wrappers, so the list page keeps working unchanged
and there is still one implementation of each editor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-02 13:06:06 +03:30
co-authored by Claude Opus 5
parent 03d8d7d68a
commit e08876f7b4
15 changed files with 1126 additions and 705 deletions
+13 -1
View File
@@ -70,10 +70,22 @@ export function useResources(filters: ResourceFilters = {}) {
onError: (e) => fail(e, 'ذخیرهٔ مهارت‌ها ناموفق بود'),
});
/** دستهٔ منبع از کاتالوگ سراسری می‌آید؛ ساختنش فقط در «تنظیمات ← دسته‌بندی‌ها» ممکن است. */
const setCategories = useMutation({
mutationFn: ({ uuid, categoryUuids }: { uuid: string; categoryUuids: string[] }) =>
api.put<ApiResponse<ClinicResource>>(`/api/v1/resource/${uuid}/categories`, { category_uuids: categoryUuids }),
onSuccess: () => {
toast.success('دسته‌بندی‌های منبع ذخیره شد');
invalidate();
qc.invalidateQueries({ queryKey: ['resource-detail'] });
},
onError: (e) => fail(e, 'ذخیرهٔ دسته‌بندی‌ها ناموفق بود'),
});
return {
resources: query.data?.data ?? [],
loading: query.isLoading,
create, update, remove, setSkills,
create, update, remove, setSkills, setCategories,
};
}