From 58691216ed28b64edda692142ecc60ff810ae15a Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Fri, 25 Sep 2026 14:33:10 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D1=83=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=83=D0=BF=D0=BE=D0=BC=20=D0=BA=20=D1=82=D0=B8=D0=BF?= =?UTF-8?q?=D0=B0=D0=BC=20=D0=B7=D0=B0=D1=8F=D0=B2=D0=BE=D0=BA=20=D0=B2=20?= =?UTF-8?q?approvalAdmin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новая вкладка «Доступ к заявкам»: выдать/отозвать доступ сотруднику видеть все заявки выбранного типа — по образцу экрана «Типы заявок». Co-Authored-By: Claude Sonnet 5 --- .../approvalAdmin/components/app/AdminApp.tsx | 3 + .../approvalAdmin/components/app/types.ts | 10 +- .../screens/RequestTypeAccessScreen.tsx | 151 ++++++++++++++++++ .../hooks/useRequestTypeAccess.ts | 10 ++ .../webparts/approvalAdmin/models/index.ts | 1 + .../approvalAdmin/models/requestTypeAccess.ts | 12 ++ .../src/webparts/approvalAdmin/routing.ts | 2 +- .../webparts/approvalAdmin/services/api.ts | 17 ++ frontend/src/webparts/approvalAdmin/texts.ts | 21 +++ 9 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 frontend/src/webparts/approvalAdmin/components/screens/RequestTypeAccessScreen.tsx create mode 100644 frontend/src/webparts/approvalAdmin/hooks/useRequestTypeAccess.ts create mode 100644 frontend/src/webparts/approvalAdmin/models/requestTypeAccess.ts diff --git a/frontend/src/webparts/approvalAdmin/components/app/AdminApp.tsx b/frontend/src/webparts/approvalAdmin/components/app/AdminApp.tsx index 0db5ee1..1fb17ab 100644 --- a/frontend/src/webparts/approvalAdmin/components/app/AdminApp.tsx +++ b/frontend/src/webparts/approvalAdmin/components/app/AdminApp.tsx @@ -14,6 +14,7 @@ import { texts } from '../../texts'; import { useLocalStorageState } from '../../hooks/useLocalStorageState'; import { DashboardScreen } from '../screens/DashboardScreen'; import { RequestTypesScreen } from '../screens/RequestTypesScreen'; +import { RequestTypeAccessScreen } from '../screens/RequestTypeAccessScreen'; import { RoutesScreen } from '../screens/RoutesScreen'; import { RouteStepsScreen } from '../screens/RouteStepsScreen'; import { ResolveScreen } from '../screens/ResolveScreen'; @@ -27,6 +28,7 @@ import { ManageByUserScreen } from '../screens/ManageByUserScreen'; const NAV_ITEMS: ISidebarItem[] = [ { id: 'dashboard', title: texts.nav.dashboard, icon: 'chart' }, { id: 'requestTypes', title: texts.nav.requestTypes, icon: 'document' }, + { id: 'requestTypeAccess', title: texts.nav.requestTypeAccess, icon: 'people' }, { id: 'routes', title: texts.nav.routes, icon: 'send' }, { id: 'resolve', title: texts.nav.resolve, icon: 'search' }, { id: 'manageRequests', title: texts.nav.manageRequests, icon: 'clipboard' }, @@ -147,6 +149,7 @@ export const AdminApp: React.FC = ({ api, dictionary, sharePoint
{route.tab === 'dashboard' && } {route.tab === 'requestTypes' && } + {route.tab === 'requestTypeAccess' && } {route.tab === 'routes' && !route.routeId && ( = ({ api, dictionary }) => { + const requestTypes = useRequestTypes(api); + const access = useRequestTypeAccess(api); + const employees = usePortalEmployees(dictionary); + + const [showForm, setShowForm] = React.useState(false); + const [requestTypeId, setRequestTypeId] = React.useState(''); + const [email, setEmail] = React.useState(''); + const submit = useAsyncSubmit(); + + const deleteTarget = useDeleteTarget(item => api.requestTypeAccess.remove(item.id), access.reload); + + const requestTypeName = (id: number): string => requestTypes.items.find(rt => rt.id === id)?.name ?? `#${id}`; + + const openCreate = (): void => { + setRequestTypeId(''); + setEmail(''); + setShowForm(true); + }; + + const submitForm = (): void => { + if (!requestTypeId) { + submit.fail(texts.requestTypeAccess.validationRequestType); + return; + } + if (!email.trim()) { + submit.fail(texts.requestTypeAccess.validationEmail); + return; + } + + submit.run(() => + api.requestTypeAccess.create({ requestTypeId: Number(requestTypeId), email: email.trim() }).then(() => { + setShowForm(false); + access.reload(); + }) + ); + }; + + return ( +
+ + {texts.requestTypeAccess.add}} + /> + + {access.error && {access.error}} + {deleteTarget.error && {deleteTarget.error}} + + {requestTypeName(r.requestTypeId)} }, + { key: 'email', header: texts.requestTypeAccess.columnEmail, render: r => r.email }, + { + key: 'actions', + header: '', + width: '100px', + align: 'right', + sticky: true, + render: r => ( + + deleteTarget.ask(r)} /> + + ) + } + ]} + rows={access.items} + keyOf={r => r.id} + emptyMessage={access.isLoading ? texts.common.loading : texts.requestTypeAccess.emptyList} + /> + + {showForm && ( + setShowForm(false)} + > + + +