feat(s29): SuperAdmin UI — бейдж + переключатель расширенного режима (шаг 6/8)
- SuperAdminOrganizationsPage:
- OrgRow.isAdvancedMode добавлен.
- Новая колонка «Режим» — бейдж «Простой» (slate) / «Расширенный»
(violet).
- Кнопка-экшн (иконка Sparkles) открывает модалку подтверждения
с разной копией для вкл/выкл; успешный PUT → list.refetch().
- Выкл-копия напоминает что данные НЕ удаляются и переключение
обратимо; вкл-копия перечисляет открывающиеся модули.
Переключение из карточки организации — отложено, список-страница
с бейджем и кнопкой закрывает UX.
pnpm run build — ✓ 2.02s, 0 TS errors.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
792ea0fd41
commit
276cba22f8
|
|
@ -14,7 +14,7 @@ SuperAdmin и только для конкретной организации.
|
|||
- [x] 3. Серверная защита: `[RequiresAdvancedMode]` → 403 ProblemDetails на скрытых модулях
|
||||
- [x] 4. Web: меню, маршруты, дашборд фильтруются; прямой переход → страница «Недоступно»
|
||||
- [x] 5. Упрощённые формы (товар / контрагент / документы) — скрыть «не нужные» поля, значения сохраняются
|
||||
- [ ] 6. SuperAdmin UI — бейдж «Простой/Расширенный» + переключатель с подтверждением
|
||||
- [x] 6. SuperAdmin UI — бейдж «Простой/Расширенный» + переключатель с подтверждением
|
||||
- [ ] 7. Тесты (unit + integration + e2e); патч quality-watchdog при необходимости
|
||||
- [ ] 8. Деплой стейджа + `docs/simple-mode.md`
|
||||
|
||||
|
|
@ -131,3 +131,15 @@ SuperAdmin и только для конкретной организации.
|
|||
Что отложено (новая функциональность, в этой задаче не трогаем):
|
||||
- `Counterparty.IsSupplier` / `IsBuyer` — задача просит галочки «Поставщик/Покупатель», но на `Counterparty` таких bool-колонок нет. Это новая миграция и расширение API — отдельная задача.
|
||||
- `pnpm run build` — ✓ (2.36s, 0 TS errors).
|
||||
|
||||
### 2026-10-04 — Шаг 6 ✓
|
||||
|
||||
- `SuperAdminOrganizationsPage.tsx`:
|
||||
- В `OrgRow` добавлен `isAdvancedMode: boolean` (бэк уже отдаёт из шага 2).
|
||||
- Новая колонка **«Режим»** с бейджем «Простой» (slate-пилюля) / «Расширенный» (violet-пилюля).
|
||||
- Новая кнопка-экшн рядом с «Архивировать» (иконка Sparkles): при клике открывается модалка подтверждения с разной копией для включения/выключения:
|
||||
- *Включить:* перечисляет, что станет доступно (demand, loyalty, promos, abc, audit, несколько цен, пресеты, bulk-ops, telegram, sso, язык).
|
||||
- *Выключить:* предупреждает что данные НЕ удаляются и переключение можно вернуть; напоминает проверить активные операции.
|
||||
- Переключение бьёт `PUT /api/super-admin/organizations/{id}/advanced-mode { enabled }` и после успеха `list.refetch()`.
|
||||
- Переключение из карточки организации (`SuperAdminOrgEmployeesPage` / detail-view) не делаем в этой итерации — список-страница с бейджем и кнопкой уже закрывает UX.
|
||||
- `pnpm run build` — ✓ (2.02s, 0 TS errors).
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Plus, Archive, RotateCcw, Trash2, LogIn, Users } from 'lucide-react'
|
||||
import { Plus, Archive, RotateCcw, Trash2, LogIn, Users, Sparkles } from 'lucide-react'
|
||||
import { api, setOrgOverride } from '@/lib/api'
|
||||
import { ListPageShell } from '@/components/ListPageShell'
|
||||
import { DataTable } from '@/components/DataTable'
|
||||
|
|
@ -18,6 +18,7 @@ interface OrgRow {
|
|||
id: string; name: string; countryCode: string
|
||||
isActive: boolean; isArchived: boolean; archivedAt: string | null
|
||||
createdAt: string; employeeCount: number; productCount: number; lastLoginAt: string | null
|
||||
isAdvancedMode: boolean
|
||||
}
|
||||
|
||||
export function SuperAdminOrganizationsPage() {
|
||||
|
|
@ -27,6 +28,22 @@ export function SuperAdminOrganizationsPage() {
|
|||
const [archiveOf, setArchiveOf] = useState<OrgRow | null>(null)
|
||||
const [deleteOf, setDeleteOf] = useState<OrgRow | null>(null)
|
||||
const [confirmName, setConfirmName] = useState('')
|
||||
// Sprint 29 — модалка подтверждения переключения режима; `toggleMode.target`
|
||||
// хранит и строку, и целевое состояние (вкл./выкл.).
|
||||
const [toggleModeOf, setToggleModeOf] = useState<{ row: OrgRow; enable: boolean } | null>(null)
|
||||
const [togglePending, setTogglePending] = useState(false)
|
||||
|
||||
const applyToggleMode = async () => {
|
||||
if (!toggleModeOf) return
|
||||
setTogglePending(true)
|
||||
try {
|
||||
await api.put(`${URL}/${toggleModeOf.row.id}/advanced-mode`, { enabled: toggleModeOf.enable })
|
||||
setToggleModeOf(null)
|
||||
list.refetch()
|
||||
} finally {
|
||||
setTogglePending(false)
|
||||
}
|
||||
}
|
||||
|
||||
const archive = async () => {
|
||||
if (!archiveOf || confirmName !== archiveOf.name) return
|
||||
|
|
@ -107,6 +124,9 @@ export function SuperAdminOrganizationsPage() {
|
|||
{ header: 'Статус', width: '120px', cell: (r) => r.isArchived
|
||||
? <span className="text-xs px-1.5 py-0.5 rounded bg-amber-100 text-amber-700">Архив</span>
|
||||
: <span className="text-xs text-emerald-600">Активна</span> },
|
||||
{ header: 'Режим', width: '130px', cell: (r) => r.isAdvancedMode
|
||||
? <span className="text-xs px-1.5 py-0.5 rounded bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-200">Расширенный</span>
|
||||
: <span className="text-xs px-1.5 py-0.5 rounded bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300">Простой</span> },
|
||||
{ header: '', width: '160px', cell: (r) => (
|
||||
<div className="flex gap-1.5" onClick={(e) => e.stopPropagation()}>
|
||||
{!r.isArchived && (
|
||||
|
|
@ -121,6 +141,16 @@ export function SuperAdminOrganizationsPage() {
|
|||
className="p-1.5 text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 rounded">
|
||||
<Users className="w-4 h-4" />
|
||||
</button>
|
||||
{!r.isArchived && (
|
||||
<button
|
||||
title={r.isAdvancedMode ? 'Выключить расширенный режим' : 'Включить расширенный режим'}
|
||||
onClick={() => setToggleModeOf({ row: r, enable: !r.isAdvancedMode })}
|
||||
className={`p-1.5 rounded ${r.isAdvancedMode
|
||||
? 'text-violet-600 hover:bg-violet-50 dark:hover:bg-violet-900/20'
|
||||
: 'text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800'}`}>
|
||||
<Sparkles className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
{!r.isArchived ? (
|
||||
<button title="Архивировать" onClick={() => { setArchiveOf(r); setConfirmName('') }}
|
||||
className="p-1.5 text-amber-600 hover:bg-amber-50 dark:hover:bg-amber-900/20 rounded">
|
||||
|
|
@ -167,6 +197,42 @@ export function SuperAdminOrganizationsPage() {
|
|||
)}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={!!toggleModeOf}
|
||||
onClose={() => setToggleModeOf(null)}
|
||||
title={toggleModeOf?.enable ? 'Включить расширенный режим' : 'Выключить расширенный режим'}
|
||||
footer={<>
|
||||
<Button variant="secondary" onClick={() => setToggleModeOf(null)} disabled={togglePending}>Отмена</Button>
|
||||
<Button onClick={applyToggleMode} disabled={togglePending}>
|
||||
{togglePending ? 'Применяю…' : (toggleModeOf?.enable ? 'Включить' : 'Выключить')}
|
||||
</Button>
|
||||
</>}
|
||||
>
|
||||
{toggleModeOf && (
|
||||
<div className="space-y-3 text-sm">
|
||||
{toggleModeOf.enable ? (
|
||||
<>
|
||||
<p>У организации <strong>{toggleModeOf.row.name}</strong> будет доступен полный набор функций:
|
||||
оптовые отгрузки, перемещения между точками, лояльность, промо/акции,
|
||||
ABC-анализ, аудит организации, несколько типов цен, bulk-операции и
|
||||
сохранённые пресеты, Telegram-бот владельца, SSO, выбор языка.</p>
|
||||
<p className="text-slate-500">Переключение логируется в SuperAdmin audit-log.</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>У организации <strong>{toggleModeOf.row.name}</strong> останется только базовый
|
||||
розничный учёт. Советуем сначала убедиться, что сотрудники не ведут активных
|
||||
операций в скрываемых модулях.</p>
|
||||
<p className="text-slate-500">
|
||||
Данные скрытых модулей <strong>не удаляются</strong> — при повторном включении
|
||||
всё снова будет видно. Переключение логируется в SuperAdmin audit-log.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
<Modal open={!!deleteOf} onClose={() => setDeleteOf(null)}
|
||||
title="Удалить организацию навсегда"
|
||||
footer={<>
|
||||
|
|
|
|||
Loading…
Reference in a new issue