feat: port stroy-bot-projects-binder as binder page
Add the "binder" section (?page=binder&projectId=): links a SharePoint project to the TNSU API project catalogue by writing UniqueId0/UniqueName to the "Проекты" list. The class component is rewritten as function components with hooks while keeping the legacy behaviour and texts: the same project GET with odata=nometadata and empty odata-version, MERGE with IF-MATCH * for bind/unbind (no confirmation), catalogue search with 400 ms debounce and 20-per-page paging, card select/deselect, status colours, ru-RU dates and HTML-entity decoding of the address. Fluent UI is replaced with ui-kit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
@import '../../../../styles/design-tokens';
|
||||
|
||||
// Толщина акцентных полос карточек — как в binder.
|
||||
$accent-bar-width: 3px;
|
||||
$accent-line-height: 2px;
|
||||
// На самых узких экранах карточки компактнее — точка перелома из binder (между $bp-xs и $bp-sm).
|
||||
$compact-card-bp: 480px;
|
||||
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $sp-4;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dismissibleAlert {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: $sp-2;
|
||||
}
|
||||
|
||||
.dismissibleAlertBody {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hairline {
|
||||
@include hairline;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// ---- Карточка проекта SharePoint ----
|
||||
|
||||
.projectCard {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: $sp-6 $sp-7;
|
||||
background: $white;
|
||||
border: 1px solid $gray-200;
|
||||
border-radius: $r-lg;
|
||||
box-shadow: $sh-2;
|
||||
|
||||
// Navy-линия сверху.
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: $accent-line-height;
|
||||
background: linear-gradient(90deg, $navy-800, $navy-600 60%, transparent);
|
||||
}
|
||||
|
||||
// Золотая полоса слева.
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: $accent-bar-width;
|
||||
background: $grad-gold-v;
|
||||
}
|
||||
}
|
||||
|
||||
.projectTitle {
|
||||
margin: 0;
|
||||
font-family: $font-serif;
|
||||
font-size: $fs-form-title;
|
||||
font-weight: 600;
|
||||
color: $navy-900;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.projectFields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: $sp-1 $sp-4;
|
||||
margin-top: $sp-3;
|
||||
}
|
||||
|
||||
.fieldLine {
|
||||
min-width: 0;
|
||||
font-size: $fs-label;
|
||||
color: $gray-500;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.fieldLabel {
|
||||
font-weight: 600;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
.bindingRow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: $sp-2;
|
||||
margin-top: $sp-3;
|
||||
padding-top: $sp-3;
|
||||
border-top: 1px solid $gray-200;
|
||||
}
|
||||
|
||||
.boundInfo {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: $fs-label;
|
||||
color: $gray-500;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.unbindAction {
|
||||
margin-top: $sp-2;
|
||||
}
|
||||
|
||||
// ---- Поиск по справочнику ----
|
||||
|
||||
.sectionTitle {
|
||||
margin: 0;
|
||||
font-family: $font-serif;
|
||||
font-size: $fs-section-title;
|
||||
font-weight: 600;
|
||||
color: $navy-900;
|
||||
}
|
||||
|
||||
.searchBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $sp-1;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.emptyResults {
|
||||
display: block;
|
||||
padding: $sp-4 0;
|
||||
font-size: $fs-label;
|
||||
text-align: center;
|
||||
color: $gray-400;
|
||||
}
|
||||
|
||||
.results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $sp-2;
|
||||
}
|
||||
|
||||
.resultItem {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $sp-1;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: $sp-3 $sp-4;
|
||||
background: $white;
|
||||
border: 1px solid $gray-200;
|
||||
border-radius: $r-md;
|
||||
box-shadow: $sh-1;
|
||||
cursor: pointer;
|
||||
transition: 0.22s;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: $accent-bar-width;
|
||||
background: $grad-gold-v;
|
||||
opacity: 0;
|
||||
transition: 0.22s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: $sh-3;
|
||||
transform: translateY(-2px);
|
||||
border-color: transparent;
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resultItemSelected {
|
||||
background: $gold-50;
|
||||
border-color: $gold-400;
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $gold-50;
|
||||
border-color: $gold-400;
|
||||
}
|
||||
}
|
||||
|
||||
.resultName {
|
||||
font-size: $fs-body;
|
||||
font-weight: 600;
|
||||
color: $navy-900;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.bindAction {
|
||||
margin-top: $sp-1;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: $sp-2;
|
||||
}
|
||||
|
||||
.pageInfo {
|
||||
font-size: $fs-label;
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
@include below($bp-sm) {
|
||||
.projectFields {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@include below($compact-card-bp) {
|
||||
.projectCard {
|
||||
padding: $sp-3 $sp-4;
|
||||
}
|
||||
|
||||
.resultItem {
|
||||
padding: $sp-2 $sp-3;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user