feat: added status next in que and changed forms state machine

This commit is contained in:
Тимур Городецкий
2026-08-05 13:53:26 +05:00
parent 5bc80df11c
commit 571614c031
18 changed files with 373 additions and 68 deletions
@@ -1,9 +1,16 @@
import * as React from 'react';
import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect, useMemo, useCallback } from 'react';
import type { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './RecruitingHireView.module.scss';
import { Alert, Button, FormCard, FormModeProvider, CollapseSection, FormField, AttachmentList, Icon } from '../../shared/ui';
import { ApproverService, DecisionPanel, StatusBadge } from '../../shared/approval';
import { Alert, Button, FormCard, FormModeProvider, CollapseSection, FormField, AttachmentList, Icon, PrintButton } from '../../shared/ui';
import {
ApproverService,
DecisionPanel,
StatusBadge,
ApprovalMatrix,
useMatrixEditor,
APPROVAL_PHASE
} from '../../shared/approval';
import { navigate } from '../../shared/routing';
import { getAttachments, IAttachmentInfo } from '../../shared/attachments/attachments.service';
import { IFormData, EMPTY_DATA, SetField } from './model';
@@ -42,6 +49,29 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
[]
);
// Матрица маршрута из фактических строк заявки (read-only), разбитая по фазам.
const approval = useMemo(() => hireApprovalService(), []);
const loadApprovers = useCallback(
() =>
approval
.getMatrixForRequest(itemId)
.then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution)),
[approval, itemId]
);
const approvers = useMatrixEditor(loadApprovers, `a-${itemId}`, false, true, (l) =>
approval.getJobTitle(l)
);
const loadExecutors = useCallback(
() =>
approval
.getMatrixForRequest(itemId)
.then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution)),
[approval, itemId]
);
const executors = useMatrixEditor(loadExecutors, `e-${itemId}`, false, true, (l) =>
approval.getJobTitle(l)
);
useEffect(() => {
let alive = true;
setLoading(true);
@@ -81,7 +111,7 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
return (
<div>
<div className={styles.toolbar}>
<div className={`${styles.toolbar} rp-no-print`}>
<Button
variant="secondary"
onClick={() =>
@@ -91,29 +121,48 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
<Icon name="copy" size="sm" />
Организация рабочего места на основе этой
</Button>
<PrintButton />
</div>
<FormCard
title="Заявка на прием"
subtitle={`№${itemId} · режим согласования`}
headerRight={status ? <StatusBadge status={status} /> : undefined}
>
<FormModeProvider readOnly>
<ApplicationSection {...sectionProps} />
<CandidateSection {...sectionProps} />
<PpeSection {...sectionProps} />
<ConditionsSection {...sectionProps} />
</FormModeProvider>
<div className="rp-print-root">
<FormCard
title="Заявка на прием"
subtitle={`№${itemId}`}
headerRight={status ? <StatusBadge status={status} /> : undefined}
>
<FormModeProvider readOnly>
<ApplicationSection {...sectionProps} />
<CandidateSection {...sectionProps} />
<PpeSection {...sectionProps} />
<ConditionsSection {...sectionProps} />
</FormModeProvider>
<CollapseSection title="Вложения" icon="upload">
<FormField label="Файлы" full>
<AttachmentList items={attachments} />
</FormField>
</CollapseSection>
</FormCard>
<div className="rp-no-print">
<CollapseSection title="Вложения" icon="upload">
<FormField label="Файлы" full>
<AttachmentList items={attachments} />
</FormField>
</CollapseSection>
</div>
</FormCard>
{/* Панель решения — ВНЕ FormModeProvider, чтобы комментарий был редактируемым. */}
<DecisionPanel service={approverService} requestId={itemId} />
<ApprovalMatrix editor={approvers} search={(q) => approval.searchUsers(q)} />
{!executors.loading && executors.rows.length > 0 && (
<ApprovalMatrix
editor={executors}
search={(q) => approval.searchUsers(q)}
title="Исполнение"
roleLabel="Исполнитель"
emptyText="Исполнители не назначены."
/>
)}
</div>
{/* Панель решения — ВНЕ печати и FormModeProvider (комментарий редактируемый). */}
<div className="rp-no-print">
<DecisionPanel service={approverService} requestId={itemId} />
</div>
</div>
);
};
@@ -2,7 +2,7 @@
"dataListTitle": "Прием на работу — согласование",
"columns": [
{ "internalName": "Approver", "title": "Согласующий", "type": "User", "userSelectionMode": "PeopleOnly" },
{ "internalName": "Status", "title": "Статус", "type": "Choice", "choices": ["Черновик", "На согласовании", "Согласовано", "Отклонено", "Возвращена на доработку", "Выполнение", "Закрыта"] },
{ "internalName": "Status", "title": "Статус", "type": "Choice", "choices": ["Черновик", "На согласовании", "Согласовано", "Отклонено", "Возвращена на доработку", "Ждёт очереди", "На исполнении", "Выполнено", "Выполнение", "Закрыта"] },
{ "internalName": "Ticket", "title": "Заявка", "type": "Lookup", "lookupList": "Прием на работу — данные", "lookupShowField": "Title" },
{ "internalName": "Date", "title": "Дата", "type": "DateTime" },
{ "internalName": "Comment", "title": "Комментарий", "type": "Note" },
@@ -6,3 +6,9 @@
font-size: $fs-sm;
text-align: center;
}
.toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: $sp-4;
}
@@ -1,9 +1,16 @@
import * as React from 'react';
import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect, useMemo, useCallback } from 'react';
import type { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './WorkplaceSetupView.module.scss';
import { Alert, FormCard, FormModeProvider, CollapseSection, FormField, AttachmentList } from '../../shared/ui';
import { ApproverService, DecisionPanel, StatusBadge } from '../../shared/approval';
import { Alert, FormCard, FormModeProvider, CollapseSection, FormField, AttachmentList, PrintButton } from '../../shared/ui';
import {
ApproverService,
DecisionPanel,
StatusBadge,
ApprovalMatrix,
useMatrixEditor,
APPROVAL_PHASE
} from '../../shared/approval';
import { getAttachments, IAttachmentInfo } from '../../shared/attachments/attachments.service';
import { IFormData, EMPTY_DATA, SetField } from './model';
import { loadWorkplaceRequest } from './load';
@@ -31,6 +38,29 @@ const WorkplaceSetupView: React.FC<IWorkplaceSetupViewProps> = ({ context, itemI
[]
);
// Матрица маршрута из фактических строк заявки (read-only), разбитая по фазам.
const approval = useMemo(() => workplaceApprovalService(), []);
const loadApprovers = useCallback(
() =>
approval
.getMatrixForRequest(itemId)
.then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution)),
[approval, itemId]
);
const approvers = useMatrixEditor(loadApprovers, `a-${itemId}`, false, true, (l) =>
approval.getJobTitle(l)
);
const loadExecutors = useCallback(
() =>
approval
.getMatrixForRequest(itemId)
.then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution)),
[approval, itemId]
);
const executors = useMatrixEditor(loadExecutors, `e-${itemId}`, false, true, (l) =>
approval.getJobTitle(l)
);
useEffect(() => {
let alive = true;
setLoading(true);
@@ -70,24 +100,46 @@ const WorkplaceSetupView: React.FC<IWorkplaceSetupViewProps> = ({ context, itemI
return (
<div>
<FormCard
title="Организация рабочего места"
subtitle={`№${itemId} · режим согласования`}
headerRight={status ? <StatusBadge status={status} /> : undefined}
>
<FormModeProvider readOnly>
<EmployeeSection {...sectionProps} />
<EquipmentSection {...sectionProps} />
</FormModeProvider>
<div className={`${styles.toolbar} rp-no-print`}>
<PrintButton />
</div>
<CollapseSection title="Вложения" icon="upload">
<FormField label="Файлы" full>
<AttachmentList items={attachments} />
</FormField>
</CollapseSection>
</FormCard>
<div className="rp-print-root">
<FormCard
title="Организация рабочего места"
subtitle={`№${itemId}`}
headerRight={status ? <StatusBadge status={status} /> : undefined}
>
<FormModeProvider readOnly>
<EmployeeSection {...sectionProps} />
<EquipmentSection {...sectionProps} />
</FormModeProvider>
<DecisionPanel service={approverService} requestId={itemId} />
<div className="rp-no-print">
<CollapseSection title="Вложения" icon="upload">
<FormField label="Файлы" full>
<AttachmentList items={attachments} />
</FormField>
</CollapseSection>
</div>
</FormCard>
<ApprovalMatrix editor={approvers} search={(q) => approval.searchUsers(q)} />
{!executors.loading && executors.rows.length > 0 && (
<ApprovalMatrix
editor={executors}
search={(q) => approval.searchUsers(q)}
title="Исполнение"
roleLabel="Исполнитель"
emptyText="Исполнители не назначены."
/>
)}
</div>
<div className="rp-no-print">
<DecisionPanel service={approverService} requestId={itemId} />
</div>
</div>
);
};
@@ -2,7 +2,7 @@
"dataListTitle": "Организация рабочего места — согласование",
"columns": [
{ "internalName": "Approver", "title": "Согласующий", "type": "User", "userSelectionMode": "PeopleOnly" },
{ "internalName": "Status", "title": "Статус", "type": "Choice", "choices": ["Черновик", "На согласовании", "Согласовано", "Отклонено", "Возвращена на доработку", "Выполнение", "Закрыта"] },
{ "internalName": "Status", "title": "Статус", "type": "Choice", "choices": ["Черновик", "На согласовании", "Согласовано", "Отклонено", "Возвращена на доработку", "Ждёт очереди", "На исполнении", "Выполнено", "Выполнение", "Закрыта"] },
{ "internalName": "Ticket", "title": "Заявка", "type": "Lookup", "lookupList": "Организация рабочего места — данные", "lookupShowField": "Title" },
{ "internalName": "Date", "title": "Дата", "type": "DateTime" },
{ "internalName": "Comment", "title": "Комментарий", "type": "Note" },