Панель со списками заявок (#1)

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/1
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tnsu.kz>
Co-committed-by: gorbenko.pavel <gorbenko.pavel@tnsu.kz>
This commit was merged in pull request #1.
This commit is contained in:
2026-08-04 12:02:11 +00:00
committed by timur.gorodetskiy
parent ba4e3feee5
commit 0aa0a60e40
21 changed files with 1442 additions and 121 deletions
@@ -0,0 +1,83 @@
@import '../../../../styles/design-tokens';
// Выезжающая панель поверх страницы. position: fixed — панель должна перекрывать
// всю страницу SharePoint, а не только канву web part.
.overlay {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
justify-content: flex-end;
}
.backdrop {
position: absolute;
inset: 0;
background: rgba(10, 27, 48, .45);
animation: fade .2s ease-out;
}
.drawer {
position: relative;
display: flex;
flex-direction: column;
width: min(920px, 100%);
height: 100%;
background: $gray-50;
box-shadow: $sh-modal;
animation: slide .22s ease-out;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: $sp-4;
flex-shrink: 0;
padding: $sp-4 $sp-6;
background: $white;
border-bottom: 1px solid $gray-200;
}
.title {
font-family: $font-serif;
font-size: 18px;
color: $navy-900;
}
.close {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
padding: 0;
border: 1px solid $gray-300;
border-radius: $r-sm;
background: $white;
color: $navy-700;
cursor: pointer;
transition: .16s;
&:hover {
border-color: $red-500;
color: $red-500;
}
}
.body {
flex: 1;
overflow-y: auto;
padding: $sp-6;
}
@keyframes slide {
from { transform: translateX(24px); opacity: .6; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}
@@ -0,0 +1,39 @@
import * as React from 'react';
import styles from './Drawer.module.scss';
import { Icon } from '../../shared/ui/Icon/Icon';
interface IDrawerProps {
title: string;
onClose: () => void;
children: React.ReactNode;
}
// Выезжающая справа панель: открывается по клику на строку таблицы заявок,
// внутри — заявка в режиме просмотра/согласования. Закрывается крестиком,
// кликом по затемнению и клавишей Esc.
export const Drawer: React.FC<IDrawerProps> = ({ title, onClose, children }) => {
React.useEffect(() => {
const onKeyDown = (e: KeyboardEvent): void => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', onKeyDown);
return () => window.removeEventListener('keydown', onKeyDown);
}, [onClose]);
return (
<div className={styles.overlay}>
{/* Затемнение — не кнопка (внутри панели свои интерактивные элементы),
поэтому клик обрабатываем на этом слое напрямую. */}
<div className={styles.backdrop} onClick={onClose} aria-hidden="true" />
<aside className={styles.drawer} role="dialog" aria-modal="true" aria-label={title}>
<div className={styles.header}>
<span className={styles.title}>{title}</span>
<button type="button" className={styles.close} onClick={onClose} aria-label="Закрыть">
<Icon name="close" size="sm" />
</button>
</div>
<div className={styles.body}>{children}</div>
</aside>
</div>
);
};