feat(roles): permissions matrix grouped by section + clone-from-template flow
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 41s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Successful in 54s
Docker Web / Build + push Web (push) Successful in 29s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 11s
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 41s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Successful in 54s
Docker Web / Build + push Web (push) Successful in 29s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 11s
RolePermissions расширен с 21 до 32 флагов: добавлены UnitsManage, DemandsView/Edit/Post (отгрузка контрагенту, не путать с RetailSales), CounterpartiesDelete, InventoryEdit, LossEdit, EnterEdit (склад-операции), ReportsFinanceView, ReportsStockView (тонкие отчётные права), CashRegistersManage и IntegrationsManage (отдельно от OrgSettingsManage). UI EmployeeRolesPage: - 7 групп вместо 6: Каталог / Закупки / Продажи / Контрагенты / Склад-Остатки / Отчёты / Настройки. Все секции аккордеоном внутри модалки (как было — flex-col), но с правильным грануляр-списком. - Системные роли — чекбоксы disabled (только просмотр; имя/описание редактируются). - При [+ Добавить роль] — сначала открывается модалка выбора шаблона: Пустой / Копия Администратора / Копия любой существующей. Дальше открывается основная модалка с предзаполненной матрицей. allPerms() помощник на фронте — зеркало RolePermissions.All() с бэка, для шаблона «Копия Администратора» в clone-flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dd3bf5e20f
commit
080564f2b2
|
|
@ -10,6 +10,7 @@ public class RolePermissions
|
||||||
public bool ProductsDelete { get; set; }
|
public bool ProductsDelete { get; set; }
|
||||||
public bool ProductGroupsManage { get; set; }
|
public bool ProductGroupsManage { get; set; }
|
||||||
public bool PriceTypesManage { get; set; }
|
public bool PriceTypesManage { get; set; }
|
||||||
|
public bool UnitsManage { get; set; }
|
||||||
|
|
||||||
// Закупки
|
// Закупки
|
||||||
public bool SuppliesView { get; set; }
|
public bool SuppliesView { get; set; }
|
||||||
|
|
@ -17,17 +18,28 @@ public class RolePermissions
|
||||||
public bool SuppliesPost { get; set; }
|
public bool SuppliesPost { get; set; }
|
||||||
public bool SuppliesDelete { get; set; }
|
public bool SuppliesDelete { get; set; }
|
||||||
|
|
||||||
// Продажи (POS)
|
// Продажи (отгрузка контрагенту + POS)
|
||||||
|
public bool DemandsView { get; set; }
|
||||||
|
public bool DemandsEdit { get; set; }
|
||||||
|
public bool DemandsPost { get; set; }
|
||||||
public bool RetailSalesOperate { get; set; }
|
public bool RetailSalesOperate { get; set; }
|
||||||
public bool RetailSalesRefund { get; set; }
|
public bool RetailSalesRefund { get; set; }
|
||||||
|
|
||||||
// Контрагенты
|
// Контрагенты
|
||||||
public bool CounterpartiesView { get; set; }
|
public bool CounterpartiesView { get; set; }
|
||||||
public bool CounterpartiesEdit { get; set; }
|
public bool CounterpartiesEdit { get; set; }
|
||||||
|
public bool CounterpartiesDelete { get; set; }
|
||||||
|
|
||||||
// Отчёты и остатки
|
// Склад / Остатки
|
||||||
public bool ReportsView { get; set; }
|
|
||||||
public bool StocksView { get; set; }
|
public bool StocksView { get; set; }
|
||||||
|
public bool InventoryEdit { get; set; }
|
||||||
|
public bool LossEdit { get; set; }
|
||||||
|
public bool EnterEdit { get; set; }
|
||||||
|
|
||||||
|
// Отчёты
|
||||||
|
public bool ReportsView { get; set; }
|
||||||
|
public bool ReportsFinanceView { get; set; }
|
||||||
|
public bool ReportsStockView { get; set; }
|
||||||
|
|
||||||
// Настройки организации
|
// Настройки организации
|
||||||
public bool OrgSettingsManage { get; set; }
|
public bool OrgSettingsManage { get; set; }
|
||||||
|
|
@ -35,17 +47,22 @@ public class RolePermissions
|
||||||
public bool RolesManage { get; set; }
|
public bool RolesManage { get; set; }
|
||||||
public bool StoresManage { get; set; }
|
public bool StoresManage { get; set; }
|
||||||
public bool RetailPointsManage { get; set; }
|
public bool RetailPointsManage { get; set; }
|
||||||
|
public bool CashRegistersManage { get; set; }
|
||||||
|
public bool IntegrationsManage { get; set; }
|
||||||
|
|
||||||
/// <summary>Полный набор всех true — для системной роли «Администратор».</summary>
|
/// <summary>Полный набор всех true — для системной роли «Администратор».</summary>
|
||||||
public static RolePermissions All() => new()
|
public static RolePermissions All() => new()
|
||||||
{
|
{
|
||||||
ProductsView = true, ProductsEdit = true, ProductsDelete = true,
|
ProductsView = true, ProductsEdit = true, ProductsDelete = true,
|
||||||
ProductGroupsManage = true, PriceTypesManage = true,
|
ProductGroupsManage = true, PriceTypesManage = true, UnitsManage = true,
|
||||||
SuppliesView = true, SuppliesEdit = true, SuppliesPost = true, SuppliesDelete = true,
|
SuppliesView = true, SuppliesEdit = true, SuppliesPost = true, SuppliesDelete = true,
|
||||||
|
DemandsView = true, DemandsEdit = true, DemandsPost = true,
|
||||||
RetailSalesOperate = true, RetailSalesRefund = true,
|
RetailSalesOperate = true, RetailSalesRefund = true,
|
||||||
CounterpartiesView = true, CounterpartiesEdit = true,
|
CounterpartiesView = true, CounterpartiesEdit = true, CounterpartiesDelete = true,
|
||||||
ReportsView = true, StocksView = true,
|
StocksView = true, InventoryEdit = true, LossEdit = true, EnterEdit = true,
|
||||||
|
ReportsView = true, ReportsFinanceView = true, ReportsStockView = true,
|
||||||
OrgSettingsManage = true, EmployeesManage = true, RolesManage = true,
|
OrgSettingsManage = true, EmployeesManage = true, RolesManage = true,
|
||||||
StoresManage = true, RetailPointsManage = true,
|
StoresManage = true, RetailPointsManage = true,
|
||||||
|
CashRegistersManage = true, IntegrationsManage = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1914,21 +1914,33 @@ protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
o.Property<bool>("ProductsDelete").HasColumnType("boolean");
|
o.Property<bool>("ProductsDelete").HasColumnType("boolean");
|
||||||
o.Property<bool>("ProductGroupsManage").HasColumnType("boolean");
|
o.Property<bool>("ProductGroupsManage").HasColumnType("boolean");
|
||||||
o.Property<bool>("PriceTypesManage").HasColumnType("boolean");
|
o.Property<bool>("PriceTypesManage").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("UnitsManage").HasColumnType("boolean");
|
||||||
o.Property<bool>("SuppliesView").HasColumnType("boolean");
|
o.Property<bool>("SuppliesView").HasColumnType("boolean");
|
||||||
o.Property<bool>("SuppliesEdit").HasColumnType("boolean");
|
o.Property<bool>("SuppliesEdit").HasColumnType("boolean");
|
||||||
o.Property<bool>("SuppliesPost").HasColumnType("boolean");
|
o.Property<bool>("SuppliesPost").HasColumnType("boolean");
|
||||||
o.Property<bool>("SuppliesDelete").HasColumnType("boolean");
|
o.Property<bool>("SuppliesDelete").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("DemandsView").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("DemandsEdit").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("DemandsPost").HasColumnType("boolean");
|
||||||
o.Property<bool>("RetailSalesOperate").HasColumnType("boolean");
|
o.Property<bool>("RetailSalesOperate").HasColumnType("boolean");
|
||||||
o.Property<bool>("RetailSalesRefund").HasColumnType("boolean");
|
o.Property<bool>("RetailSalesRefund").HasColumnType("boolean");
|
||||||
o.Property<bool>("CounterpartiesView").HasColumnType("boolean");
|
o.Property<bool>("CounterpartiesView").HasColumnType("boolean");
|
||||||
o.Property<bool>("CounterpartiesEdit").HasColumnType("boolean");
|
o.Property<bool>("CounterpartiesEdit").HasColumnType("boolean");
|
||||||
o.Property<bool>("ReportsView").HasColumnType("boolean");
|
o.Property<bool>("CounterpartiesDelete").HasColumnType("boolean");
|
||||||
o.Property<bool>("StocksView").HasColumnType("boolean");
|
o.Property<bool>("StocksView").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("InventoryEdit").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("LossEdit").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("EnterEdit").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("ReportsView").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("ReportsFinanceView").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("ReportsStockView").HasColumnType("boolean");
|
||||||
o.Property<bool>("OrgSettingsManage").HasColumnType("boolean");
|
o.Property<bool>("OrgSettingsManage").HasColumnType("boolean");
|
||||||
o.Property<bool>("EmployeesManage").HasColumnType("boolean");
|
o.Property<bool>("EmployeesManage").HasColumnType("boolean");
|
||||||
o.Property<bool>("RolesManage").HasColumnType("boolean");
|
o.Property<bool>("RolesManage").HasColumnType("boolean");
|
||||||
o.Property<bool>("StoresManage").HasColumnType("boolean");
|
o.Property<bool>("StoresManage").HasColumnType("boolean");
|
||||||
o.Property<bool>("RetailPointsManage").HasColumnType("boolean");
|
o.Property<bool>("RetailPointsManage").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("CashRegistersManage").HasColumnType("boolean");
|
||||||
|
o.Property<bool>("IntegrationsManage").HasColumnType("boolean");
|
||||||
o.HasKey("EmployeeRoleId");
|
o.HasKey("EmployeeRoleId");
|
||||||
o.ToJson("permissions");
|
o.ToJson("permissions");
|
||||||
o.WithOwner().HasForeignKey("EmployeeRoleId");
|
o.WithOwner().HasForeignKey("EmployeeRoleId");
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,16 @@ const URL = '/api/organization/employee-roles'
|
||||||
|
|
||||||
export interface RolePermissions {
|
export interface RolePermissions {
|
||||||
productsView: boolean; productsEdit: boolean; productsDelete: boolean
|
productsView: boolean; productsEdit: boolean; productsDelete: boolean
|
||||||
productGroupsManage: boolean; priceTypesManage: boolean
|
productGroupsManage: boolean; priceTypesManage: boolean; unitsManage: boolean
|
||||||
suppliesView: boolean; suppliesEdit: boolean; suppliesPost: boolean; suppliesDelete: boolean
|
suppliesView: boolean; suppliesEdit: boolean; suppliesPost: boolean; suppliesDelete: boolean
|
||||||
|
demandsView: boolean; demandsEdit: boolean; demandsPost: boolean
|
||||||
retailSalesOperate: boolean; retailSalesRefund: boolean
|
retailSalesOperate: boolean; retailSalesRefund: boolean
|
||||||
counterpartiesView: boolean; counterpartiesEdit: boolean
|
counterpartiesView: boolean; counterpartiesEdit: boolean; counterpartiesDelete: boolean
|
||||||
reportsView: boolean; stocksView: boolean
|
stocksView: boolean; inventoryEdit: boolean; lossEdit: boolean; enterEdit: boolean
|
||||||
|
reportsView: boolean; reportsFinanceView: boolean; reportsStockView: boolean
|
||||||
orgSettingsManage: boolean; employeesManage: boolean; rolesManage: boolean
|
orgSettingsManage: boolean; employeesManage: boolean; rolesManage: boolean
|
||||||
storesManage: boolean; retailPointsManage: boolean
|
storesManage: boolean; retailPointsManage: boolean; cashRegistersManage: boolean
|
||||||
|
integrationsManage: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmployeeRoleDto {
|
export interface EmployeeRoleDto {
|
||||||
|
|
@ -37,24 +40,42 @@ interface Form {
|
||||||
|
|
||||||
const blankPerms = (): RolePermissions => ({
|
const blankPerms = (): RolePermissions => ({
|
||||||
productsView: false, productsEdit: false, productsDelete: false,
|
productsView: false, productsEdit: false, productsDelete: false,
|
||||||
productGroupsManage: false, priceTypesManage: false,
|
productGroupsManage: false, priceTypesManage: false, unitsManage: false,
|
||||||
suppliesView: false, suppliesEdit: false, suppliesPost: false, suppliesDelete: false,
|
suppliesView: false, suppliesEdit: false, suppliesPost: false, suppliesDelete: false,
|
||||||
|
demandsView: false, demandsEdit: false, demandsPost: false,
|
||||||
retailSalesOperate: false, retailSalesRefund: false,
|
retailSalesOperate: false, retailSalesRefund: false,
|
||||||
counterpartiesView: false, counterpartiesEdit: false,
|
counterpartiesView: false, counterpartiesEdit: false, counterpartiesDelete: false,
|
||||||
reportsView: false, stocksView: false,
|
stocksView: false, inventoryEdit: false, lossEdit: false, enterEdit: false,
|
||||||
|
reportsView: false, reportsFinanceView: false, reportsStockView: false,
|
||||||
orgSettingsManage: false, employeesManage: false, rolesManage: false,
|
orgSettingsManage: false, employeesManage: false, rolesManage: false,
|
||||||
storesManage: false, retailPointsManage: false,
|
storesManage: false, retailPointsManage: false, cashRegistersManage: false,
|
||||||
|
integrationsManage: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const allPerms = (): RolePermissions => ({
|
||||||
|
productsView: true, productsEdit: true, productsDelete: true,
|
||||||
|
productGroupsManage: true, priceTypesManage: true, unitsManage: true,
|
||||||
|
suppliesView: true, suppliesEdit: true, suppliesPost: true, suppliesDelete: true,
|
||||||
|
demandsView: true, demandsEdit: true, demandsPost: true,
|
||||||
|
retailSalesOperate: true, retailSalesRefund: true,
|
||||||
|
counterpartiesView: true, counterpartiesEdit: true, counterpartiesDelete: true,
|
||||||
|
stocksView: true, inventoryEdit: true, lossEdit: true, enterEdit: true,
|
||||||
|
reportsView: true, reportsFinanceView: true, reportsStockView: true,
|
||||||
|
orgSettingsManage: true, employeesManage: true, rolesManage: true,
|
||||||
|
storesManage: true, retailPointsManage: true, cashRegistersManage: true,
|
||||||
|
integrationsManage: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const blankForm = (): Form => ({ name: '', description: '', isSystem: false, permissions: blankPerms() })
|
const blankForm = (): Form => ({ name: '', description: '', isSystem: false, permissions: blankPerms() })
|
||||||
|
|
||||||
const PERM_GROUPS: { title: string; perms: { key: keyof RolePermissions; label: string }[] }[] = [
|
export const PERM_GROUPS: { title: string; perms: { key: keyof RolePermissions; label: string }[] }[] = [
|
||||||
{ title: 'Каталог', perms: [
|
{ title: 'Каталог', perms: [
|
||||||
{ key: 'productsView', label: 'Просмотр товаров' },
|
{ key: 'productsView', label: 'Просмотр товаров' },
|
||||||
{ key: 'productsEdit', label: 'Редактирование товаров' },
|
{ key: 'productsEdit', label: 'Редактирование товаров' },
|
||||||
{ key: 'productsDelete', label: 'Удаление товаров' },
|
{ key: 'productsDelete', label: 'Удаление товаров' },
|
||||||
{ key: 'productGroupsManage', label: 'Управление группами товаров' },
|
{ key: 'productGroupsManage', label: 'Управление группами' },
|
||||||
{ key: 'priceTypesManage', label: 'Управление типами цен' },
|
{ key: 'priceTypesManage', label: 'Управление типами цен' },
|
||||||
|
{ key: 'unitsManage', label: 'Единицы измерения' },
|
||||||
]},
|
]},
|
||||||
{ title: 'Закупки', perms: [
|
{ title: 'Закупки', perms: [
|
||||||
{ key: 'suppliesView', label: 'Просмотр приёмок' },
|
{ key: 'suppliesView', label: 'Просмотр приёмок' },
|
||||||
|
|
@ -63,16 +84,27 @@ const PERM_GROUPS: { title: string; perms: { key: keyof RolePermissions; label:
|
||||||
{ key: 'suppliesDelete', label: 'Удаление приёмок' },
|
{ key: 'suppliesDelete', label: 'Удаление приёмок' },
|
||||||
]},
|
]},
|
||||||
{ title: 'Продажи', perms: [
|
{ title: 'Продажи', perms: [
|
||||||
|
{ key: 'demandsView', label: 'Просмотр отгрузок' },
|
||||||
|
{ key: 'demandsEdit', label: 'Редактирование отгрузок' },
|
||||||
|
{ key: 'demandsPost', label: 'Проведение отгрузок' },
|
||||||
{ key: 'retailSalesOperate', label: 'Работа на кассе' },
|
{ key: 'retailSalesOperate', label: 'Работа на кассе' },
|
||||||
{ key: 'retailSalesRefund', label: 'Возвраты' },
|
{ key: 'retailSalesRefund', label: 'Возвраты на кассе' },
|
||||||
]},
|
]},
|
||||||
{ title: 'Контрагенты', perms: [
|
{ title: 'Контрагенты', perms: [
|
||||||
{ key: 'counterpartiesView', label: 'Просмотр' },
|
{ key: 'counterpartiesView', label: 'Просмотр' },
|
||||||
{ key: 'counterpartiesEdit', label: 'Редактирование' },
|
{ key: 'counterpartiesEdit', label: 'Редактирование' },
|
||||||
|
{ key: 'counterpartiesDelete', label: 'Удаление' },
|
||||||
]},
|
]},
|
||||||
{ title: 'Отчёты и остатки', perms: [
|
{ title: 'Склад / Остатки', perms: [
|
||||||
{ key: 'reportsView', label: 'Просмотр отчётов' },
|
|
||||||
{ key: 'stocksView', label: 'Просмотр остатков' },
|
{ key: 'stocksView', label: 'Просмотр остатков' },
|
||||||
|
{ key: 'inventoryEdit', label: 'Инвентаризация' },
|
||||||
|
{ key: 'lossEdit', label: 'Списание' },
|
||||||
|
{ key: 'enterEdit', label: 'Оприходование' },
|
||||||
|
]},
|
||||||
|
{ title: 'Отчёты', perms: [
|
||||||
|
{ key: 'reportsView', label: 'Сводные отчёты' },
|
||||||
|
{ key: 'reportsFinanceView', label: 'Финансы / касса' },
|
||||||
|
{ key: 'reportsStockView', label: 'Остатки и обороты' },
|
||||||
]},
|
]},
|
||||||
{ title: 'Настройки организации', perms: [
|
{ title: 'Настройки организации', perms: [
|
||||||
{ key: 'orgSettingsManage', label: 'Общие настройки' },
|
{ key: 'orgSettingsManage', label: 'Общие настройки' },
|
||||||
|
|
@ -80,6 +112,8 @@ const PERM_GROUPS: { title: string; perms: { key: keyof RolePermissions; label:
|
||||||
{ key: 'rolesManage', label: 'Роли' },
|
{ key: 'rolesManage', label: 'Роли' },
|
||||||
{ key: 'storesManage', label: 'Склады' },
|
{ key: 'storesManage', label: 'Склады' },
|
||||||
{ key: 'retailPointsManage', label: 'Кассы' },
|
{ key: 'retailPointsManage', label: 'Кассы' },
|
||||||
|
{ key: 'cashRegistersManage', label: 'Кассовые аппараты (ККМ)' },
|
||||||
|
{ key: 'integrationsManage', label: 'Интеграции (МойСклад и т.п.)' },
|
||||||
]},
|
]},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -87,6 +121,9 @@ export function EmployeeRolesPage() {
|
||||||
const { data, isLoading, page, setPage, search, setSearch, sortKey, sortOrder, setSort } = useCatalogList<EmployeeRoleDto>(URL)
|
const { data, isLoading, page, setPage, search, setSearch, sortKey, sortOrder, setSort } = useCatalogList<EmployeeRoleDto>(URL)
|
||||||
const { create, update, remove } = useCatalogMutations(URL, URL)
|
const { create, update, remove } = useCatalogMutations(URL, URL)
|
||||||
const [form, setForm] = useState<Form | null>(null)
|
const [form, setForm] = useState<Form | null>(null)
|
||||||
|
// Шаг выбора шаблона: показывается ПЕРЕД редактированием матрицы при добавлении новой роли.
|
||||||
|
const [pickTemplate, setPickTemplate] = useState(false)
|
||||||
|
const [templateId, setTemplateId] = useState<string>('blank')
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
if (!form) return
|
if (!form) return
|
||||||
|
|
@ -104,7 +141,7 @@ export function EmployeeRolesPage() {
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<SearchBar value={search} onChange={setSearch} />
|
<SearchBar value={search} onChange={setSearch} />
|
||||||
<Button onClick={() => setForm(blankForm())}>
|
<Button onClick={() => { setPickTemplate(true); setTemplateId('blank') }}>
|
||||||
<Plus className="w-4 h-4" /> Добавить роль
|
<Plus className="w-4 h-4" /> Добавить роль
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|
@ -142,6 +179,53 @@ export function EmployeeRolesPage() {
|
||||||
/>
|
/>
|
||||||
</ListPageShell>
|
</ListPageShell>
|
||||||
|
|
||||||
|
{/* Шаг выбора шаблона — открывается до основной модалки. */}
|
||||||
|
<Modal
|
||||||
|
open={pickTemplate}
|
||||||
|
onClose={() => setPickTemplate(false)}
|
||||||
|
title="Новая роль — выберите шаблон"
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
<Button variant="secondary" onClick={() => setPickTemplate(false)}>Отмена</Button>
|
||||||
|
<Button onClick={() => {
|
||||||
|
let perms = blankPerms()
|
||||||
|
if (templateId === 'all') perms = allPerms()
|
||||||
|
else if (templateId !== 'blank') {
|
||||||
|
const src = data?.items.find((r) => r.id === templateId)
|
||||||
|
if (src) perms = { ...blankPerms(), ...src.permissions }
|
||||||
|
}
|
||||||
|
setPickTemplate(false)
|
||||||
|
setForm({ ...blankForm(), permissions: perms })
|
||||||
|
}}>Продолжить</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm text-slate-500 mb-3">
|
||||||
|
Стартовый набор прав. После создания роли сможешь дополнительно настроить
|
||||||
|
каждый пункт через матрицу прав.
|
||||||
|
</p>
|
||||||
|
<label className="flex items-start gap-2 p-2 rounded hover:bg-slate-50 dark:hover:bg-slate-800 cursor-pointer">
|
||||||
|
<input type="radio" name="tpl" checked={templateId === 'blank'} onChange={() => setTemplateId('blank')} className="mt-1" />
|
||||||
|
<span><span className="font-medium">Пустой</span><br /><span className="text-xs text-slate-500">Все права отключены, нужно ставить галки самому.</span></span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-start gap-2 p-2 rounded hover:bg-slate-50 dark:hover:bg-slate-800 cursor-pointer">
|
||||||
|
<input type="radio" name="tpl" checked={templateId === 'all'} onChange={() => setTemplateId('all')} className="mt-1" />
|
||||||
|
<span><span className="font-medium">Копия Администратора</span><br /><span className="text-xs text-slate-500">Все права включены — потом убери ненужные.</span></span>
|
||||||
|
</label>
|
||||||
|
{data?.items.map((r) => (
|
||||||
|
<label key={r.id} className="flex items-start gap-2 p-2 rounded hover:bg-slate-50 dark:hover:bg-slate-800 cursor-pointer">
|
||||||
|
<input type="radio" name="tpl" checked={templateId === r.id} onChange={() => setTemplateId(r.id)} className="mt-1" />
|
||||||
|
<span>
|
||||||
|
<span className="font-medium">Копия «{r.name}»</span>
|
||||||
|
{r.isSystem && <span className="ml-2 text-xs px-1.5 py-0.5 rounded bg-slate-100 dark:bg-slate-800">системная</span>}
|
||||||
|
{r.description && <><br /><span className="text-xs text-slate-500">{r.description}</span></>}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={!!form}
|
open={!!form}
|
||||||
onClose={() => setForm(null)}
|
onClose={() => setForm(null)}
|
||||||
|
|
@ -183,6 +267,7 @@ export function EmployeeRolesPage() {
|
||||||
key={p.key}
|
key={p.key}
|
||||||
label={p.label}
|
label={p.label}
|
||||||
checked={form.permissions[p.key]}
|
checked={form.permissions[p.key]}
|
||||||
|
disabled={form.isSystem}
|
||||||
onChange={(v) => setForm({ ...form, permissions: { ...form.permissions, [p.key]: v } })}
|
onChange={(v) => setForm({ ...form, permissions: { ...form.permissions, [p.key]: v } })}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue