refactor(frontend): remove redundant department panels

This commit is contained in:
lizerk 2026-04-13 23:44:18 +03:00
parent 64eafeea46
commit 969554bf3f
3 changed files with 6 additions and 171 deletions

View File

@ -232,10 +232,10 @@
Использовать как референс для:
- detail screen, который должен уметь переключаться между `showcase` и `management` без смены route и без ощущения двух разных страниц
- page-level controller для domain actions вокруг одного объекта: overview, members, head management, roles
- richer detail layout, где есть сильный header, один основной roster-блок и один secondary-блок ролей, без раздувания служебных summary-panels
- page-level controller для domain actions вокруг одного объекта: overview, members и inline management actions
- richer detail layout, где есть сильный header и один основной roster/workspace-блок, без раздувания служебных summary-panels и вторичных management-panels
- member roster с role assignment и person-chip подачей вместо сырых строк участников
- role management на уровне названий ролей и назначений участникам, без фронтового permission-editor
- role management на уровне названий ролей и назначений участникам, встроенный в roster, без фронтового permission-editor
С оговоркой:

View File

@ -34,7 +34,7 @@
| `users/directory` | `mixed` | `users/page.tsx` и `employees/page.tsx` уже живут в app-surface language, сценарии user/employee navigation выровнены и читаются как один cluster | список людей всё ещё проще и беднее по interaction density, чем основные рабочие модули, поэтому это не полноценный stylistic reference | считать baseline-pass завершённым и трогать дальше только при появлении richer directory requirements |
| `home/feed` | `mixed` | сильный app-shell fit, хорошая operational density, уже много app primitives | page очень большой, комментарии и post-compose logic перегружены, встречаются legacy `rounded-md` fragments | использовать как reference по feed density, но разрезать modal/comments/post flows и дочистить geometry |
| `notifications` | `mixed` | близок к operational app-style, фильтры и list hierarchy уже читаются системно | page-level logic пока монолитная, нет выделенного feature controller, есть место для уплотнения и симметрии с messages/requests | после people cluster сделать короткий structural pass без крупного редизайна |
| `departments` | `mixed` | detail screen уже работает как dual-mode page: showcase-first для обычного просмотра и встроенный management-layer для управленцев, page-level controller вынесен в hook | список отделов всё ещё проще detail screen, а сам модуль ещё не проверен длинным visual-pass и живым backend-сценарием | считать `departments/[id]` baseline reference для object-detail pages с mixed audience и затем при желании подтянуть list screen до той же richness |
| `departments` | `mixed` | detail screen уже работает как dual-mode page: showcase-first для обычного просмотра и встроенный inline management-layer для управленцев, page-level controller вынесен в hook | список отделов всё ещё проще detail screen, а сам модуль ещё не проверен длинным visual-pass и живым backend-сценарием | считать `departments/[id]` baseline reference для object-detail pages с mixed audience и затем при желании подтянуть list screen до той же richness |
| `auth/legal` | `mixed` | изолированные flows уже отделены от app-shell, общая подача аккуратная | `rounded-[2rem]` и локальная hero-card геометрия образуют мини-систему отдельно от app shell | не смешивать с operational refactor; сделать отдельный compact pass по auth/legal geometry |
| `documents/dashboard` | `legacy-heavy` | есть готовые data blocks | заметны старые rounded patterns и мелкие utility-решения | включить в documents refactor pass |
@ -178,7 +178,7 @@
- у `settings` после controller extraction резать section-level UI и только затем geometry
- у `departments` держать detail screen в логике `showcase + management layer`, а не возвращать его в mini-admin panel
- detail screen `departments/[id]` уже не считать пустой зоной; дальнейшие работы здесь должны быть про polishing, а не про возврат permission-editor и служебных панелей
- detail screen `departments/[id]` уже не считать пустой зоной; дальнейшие работы здесь должны быть про polishing, а не про возврат permission-editor, служебных панелей и отдельных role/head blocks
- у `auth/legal` выровнять isolated geometry отдельно от app-shell жанра
### Волна 7. Calendar special audit

View File

@ -18,13 +18,12 @@ import {
} from "lucide-react";
import { AppShell } from "@/components/AppShell";
import { DepartmentPersonChip } from "@/components/departments/DepartmentPersonChip";
import { RequestAvatar } from "@/components/requests/RequestAvatar";
import { SearchableSelectSingle } from "@/components/shared/SearchableSelect";
import { Modal } from "@/components/ui/Modal";
import { useDepartmentPage } from "@/hooks/useDepartmentPage";
import { displayUserName, userProfileLink } from "@/lib/shared";
import type { DepartmentMemberLink, DepartmentRole } from "@/types/api";
import type { DepartmentMemberLink } from "@/types/api";
function MetaChip({
children,
@ -297,68 +296,6 @@ function DepartmentMemberRow({
);
}
function DepartmentRoleCard({
canManageRoles,
pendingKey,
role,
usageCount,
onDelete,
onEdit,
}: {
canManageRoles: boolean;
pendingKey: string | null;
role: DepartmentRole;
usageCount: number;
onDelete: (role: DepartmentRole) => Promise<void>;
onEdit: (role: DepartmentRole) => void;
}) {
const isDeleting = pendingKey === `role-delete-${role.id}`;
return (
<article className="app-surface-muted rounded-xl p-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<h3 className="text-sm font-semibold text-[var(--foreground)]">
{role.name}
</h3>
<span className="app-badge inline-flex rounded-full px-2.5 py-1 text-xs font-medium">
{usageCount} участников
</span>
</div>
<p className="app-text-muted mt-2 text-sm">
{usageCount
? "Используется в составе отдела."
: "Пока не назначена участникам отдела."}
</p>
</div>
{canManageRoles ? (
<div className="flex shrink-0 flex-wrap gap-2">
<button
type="button"
onClick={() => onEdit(role)}
className="app-action-secondary inline-flex items-center gap-2 rounded-lg px-3 py-2 text-sm"
>
<PencilLine size={14} />
Переименовать
</button>
<button
type="button"
onClick={() => void onDelete(role)}
disabled={isDeleting}
className="app-action-danger inline-flex items-center gap-2 rounded-lg px-3 py-2 text-sm disabled:opacity-50"
>
<Trash2 size={14} />
Удалить
</button>
</div>
) : null}
</div>
</article>
);
}
function DepartmentEditorModal({
draft,
isOpen,
@ -811,108 +748,6 @@ export default function DepartmentDetailPage() {
)}
</div>
{isManagementMode && h.userPerms.can_change_head ? (
<div className="app-surface-muted rounded-xl p-4">
<div className="mb-3 flex flex-wrap items-center gap-2">
<p className="app-card-caption">Руководитель отдела</p>
{h.department.head ? (
<DepartmentPersonChip
currentUserId={h.currentUserId}
person={h.department.head}
subtitle={
h.department.head.position?.name ||
h.department.head.email
}
/>
) : (
<span className="app-text-muted text-sm">
Руководитель пока не назначен
</span>
)}
</div>
<div className="space-y-3">
<SearchableSelectSingle
label="Назначить руководителя"
items={h.headCandidates}
selectedId={h.selectedHeadId}
onSelect={h.setSelectedHeadId}
placeholder="Выберите сотрудника"
disabled={!h.headCandidates.length}
/>
<div className="flex flex-wrap gap-2">
<button
type="button"
onClick={() => void h.submitHeadChange()}
disabled={h.pendingKey === "head"}
className="app-action-primary rounded-lg px-4 py-2 text-sm disabled:opacity-50"
>
Сохранить
</button>
{h.department.head ? (
<button
type="button"
onClick={() => void h.submitHeadRemoval()}
disabled={h.pendingKey === "head"}
className="app-action-secondary rounded-lg px-4 py-2 text-sm disabled:opacity-50"
>
Снять
</button>
) : null}
</div>
</div>
</div>
) : null}
</section>
<section className="app-surface rounded-2xl p-5">
<div className="mb-4 flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<h2 className="app-card-caption">Роли отдела</h2>
<p className="app-text-muted mt-2 text-sm">
{isManagementMode
? "Именованные роли внутри отдела: их можно создавать, переименовывать и назначать участникам."
: "Справочный список ролей, которые используются внутри отдела."}
</p>
</div>
{isManagementMode && h.userPerms.can_assign_roles ? (
<button
type="button"
onClick={h.openCreateRole}
className="app-action-secondary inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm"
>
<Plus size={14} />
Создать роль
</button>
) : null}
</div>
<div className="space-y-3">
{h.roles.length ? (
h.roles.map((role) => (
<DepartmentRoleCard
key={role.id}
canManageRoles={isManagementMode && h.userPerms.can_assign_roles}
pendingKey={h.pendingKey}
role={role}
usageCount={h.roleUsage[role.id] || 0}
onDelete={h.submitRoleDelete}
onEdit={h.openEditRole}
/>
))
) : (
<div className="app-surface-muted rounded-xl p-8 text-center">
<p className="text-sm font-medium text-[var(--foreground)]">
Роли ещё не созданы
</p>
<p className="app-text-muted mt-2 text-sm">
{isManagementMode
? "Создай первую роль, если внутри отдела нужно разделить обязанности."
: "В этом отделе пока не заведены отдельные внутренние роли."}
</p>
</div>
)}
</div>
</section>
</>
) : null}