feat: request portal — bespoke forms + approval mechanism

This commit is contained in:
Тимур Городецкий
2026-08-03 09:35:14 +05:00
commit bc91c9861d
110 changed files with 30562 additions and 0 deletions
@@ -0,0 +1,105 @@
@import '../../../../styles/design-tokens';
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
gap: 14px;
margin-top: $sp-6;
}
.tile {
position: relative;
display: flex;
align-items: center;
gap: 16px;
width: 100%;
padding: 22px 24px;
text-align: left;
background: $white;
border: 1px solid $gray-200;
border-radius: $r-md;
box-shadow: $sh-1;
cursor: pointer;
overflow: hidden;
font-family: $font-sans;
transition: box-shadow .22s, transform .22s, border-color .22s;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: $grad-gold-v;
opacity: 0;
transition: opacity .22s;
}
&:hover {
box-shadow: $sh-3;
transform: translateY(-2px);
border-color: transparent;
}
&:hover::before {
opacity: 1;
}
}
.tileIcon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: $r-md;
background: $navy-50;
border: 1px solid $navy-100;
color: $navy-700;
transition: background .22s, color .22s, border-color .22s;
.tile:hover & {
background: $gold-50;
border-color: $gold-300;
color: $gold-600;
}
}
.tileBody {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.tileTitle {
font-size: 14.5px;
font-weight: 600;
letter-spacing: .1px;
color: $navy-900;
}
.tileDesc {
font-size: 12px;
color: $gray-400;
}
.tileArrow {
display: flex;
margin-left: auto;
color: $gray-300;
transition: color .22s, transform .22s;
.tile:hover & {
color: $gold-500;
transform: translateX(3px);
}
}
.empty {
margin-top: $sp-6;
color: $gray-500;
font-size: $fs-sm;
}
@@ -0,0 +1,39 @@
import * as React from 'react';
import styles from './RequestGrid.module.scss';
import { Icon } from '../../shared/ui/Icon/Icon';
import { IRequestType } from '../../models/types';
interface IRequestGridProps {
requests: IRequestType[];
onSelect: (request: IRequestType) => void;
}
export const RequestGrid: React.FC<IRequestGridProps> = ({ requests, onSelect }) => {
if (requests.length === 0) {
return <p className={styles.empty}>В этой категории пока нет заявок.</p>;
}
return (
<div className={styles.grid}>
{requests.map((request) => (
<button
key={request.id}
type="button"
className={styles.tile}
onClick={() => onSelect(request)}
>
<span className={styles.tileIcon}>
<Icon name={request.icon} />
</span>
<span className={styles.tileBody}>
<span className={styles.tileTitle}>{request.title}</span>
{request.description && <span className={styles.tileDesc}>{request.description}</span>}
</span>
<span className={styles.tileArrow}>
<Icon name="chevron-right" size="sm" />
</span>
</button>
))}
</div>
);
};