feat: added app routing
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import styles from './RecruitingHireForm.module.scss';
|
||||
import { Button, Alert, FormCard, FormActions, Icon } from '../../shared/ui';
|
||||
import { Button, Alert, FormCard, FormActions, FileDropzone, CollapseSection, FormField, Icon } from '../../shared/ui';
|
||||
import {
|
||||
ApprovalMatrix,
|
||||
useMatrixEditor,
|
||||
@@ -9,9 +9,10 @@ import {
|
||||
REQUEST_STATUS,
|
||||
IApproverToWrite
|
||||
} from '../../shared/approval';
|
||||
import { uploadAttachments } from '../../shared/attachments/attachments.service';
|
||||
import { IRequestFormProps } from '../../models/types';
|
||||
import { IFormData, EMPTY_DATA, SetField } from './model';
|
||||
import { saveHireRequest, hireApprovalService, HIRE_REQUEST_TYPE_ID } from './submit';
|
||||
import { saveHireRequest, hireApprovalService, HIRE_REQUEST_TYPE_ID, DATA_LIST_TITLE } from './submit';
|
||||
import { ApplicationSection } from './sections/ApplicationSection';
|
||||
import { CandidateSection } from './sections/CandidateSection';
|
||||
import { PpeSection } from './sections/PpeSection';
|
||||
@@ -27,6 +28,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
||||
const [actionKind, setActionKind] = useState<ActionKind | undefined>(undefined);
|
||||
const [status, setStatus] = useState<string | undefined>(undefined);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
|
||||
const set: SetField = (key, value) => {
|
||||
setData((prev) => ({ ...prev, [key]: value }));
|
||||
@@ -60,7 +62,8 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
||||
setActionKind(kind);
|
||||
setActionStatus('saving');
|
||||
try {
|
||||
await saveHireRequest(data, matrixApprovers(), requestStatus);
|
||||
const id = await saveHireRequest(data, matrixApprovers(), requestStatus);
|
||||
await uploadAttachments(DATA_LIST_TITLE, id, files); // файлы — после создания элемента
|
||||
setStatus(requestStatus);
|
||||
setActionStatus('success');
|
||||
if (onSaved) {
|
||||
@@ -106,6 +109,12 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
||||
<ConditionsSection {...sectionProps} />
|
||||
<ResponsibleSection {...sectionProps} />
|
||||
|
||||
<CollapseSection title="Вложения" icon="upload">
|
||||
<FormField label="Файлы" full>
|
||||
<FileDropzone files={files} onChange={setFiles} />
|
||||
</FormField>
|
||||
</CollapseSection>
|
||||
|
||||
<ApprovalMatrix editor={matrix} search={(q) => approval.searchUsers(q)} />
|
||||
|
||||
<FormActions>
|
||||
|
||||
@@ -2,8 +2,9 @@ import * as React from 'react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import type { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import styles from './RecruitingHireView.module.scss';
|
||||
import { Alert, FormCard, FormModeProvider } from '../../shared/ui';
|
||||
import { Alert, FormCard, FormModeProvider, CollapseSection, FormField, AttachmentList } from '../../shared/ui';
|
||||
import { ApproverService, DecisionPanel } from '../../shared/approval';
|
||||
import { getAttachments, IAttachmentInfo } from '../../shared/attachments/attachments.service';
|
||||
import { IFormData, EMPTY_DATA, SetField } from './model';
|
||||
import { loadHireRequest } from './load';
|
||||
import { hireApprovalService, DATA_LIST_TITLE } from './submit';
|
||||
@@ -26,6 +27,7 @@ const NOOP_SET: SetField = () => undefined;
|
||||
// через <FormModeProvider readOnly> — поля сами становятся неизменяемыми.
|
||||
const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemId }) => {
|
||||
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
||||
const [attachments, setAttachments] = useState<IAttachmentInfo[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -38,10 +40,11 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
setLoading(true);
|
||||
loadHireRequest(itemId)
|
||||
.then((loaded) => {
|
||||
Promise.all([loadHireRequest(itemId), getAttachments(DATA_LIST_TITLE, itemId)])
|
||||
.then(([loaded, files]) => {
|
||||
if (alive) {
|
||||
setData(loaded);
|
||||
setAttachments(files);
|
||||
setError(null);
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -80,6 +83,12 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
|
||||
<ConditionsSection {...sectionProps} />
|
||||
<ResponsibleSection {...sectionProps} />
|
||||
</FormModeProvider>
|
||||
|
||||
<CollapseSection title="Вложения" icon="upload">
|
||||
<FormField label="Файлы" full>
|
||||
<AttachmentList items={attachments} />
|
||||
</FormField>
|
||||
</CollapseSection>
|
||||
</FormCard>
|
||||
|
||||
{/* Панель решения — ВНЕ FormModeProvider, чтобы комментарий был редактируемым. */}
|
||||
|
||||
@@ -62,10 +62,13 @@ export const EMPTY_DATA: IFormData = {
|
||||
SecuritySpecialist: ''
|
||||
};
|
||||
|
||||
/** Значение «Тип приёма», при котором показывается поле «Взамен кого». */
|
||||
export const HIRE_TYPE_REPLACEMENT = 'Вместо уволенного';
|
||||
|
||||
/** Варианты «Тип приёма» — совпадают с choices колонки HireType. */
|
||||
export const HIRE_TYPE_OPTIONS: ISelectOption[] = [
|
||||
{ value: 'Новый сотрудник', label: 'Новый сотрудник' },
|
||||
{ value: 'Вместо уволенного', label: 'Вместо уволенного' }
|
||||
{ value: HIRE_TYPE_REPLACEMENT, label: HIRE_TYPE_REPLACEMENT }
|
||||
];
|
||||
|
||||
/** Варианты «Вид оформления» — совпадают с choices колонки EmploymentForm. */
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
import * as React from 'react';
|
||||
import { CollapseSection, FormField, TextInput, Select } from '../../../shared/ui';
|
||||
import { ISectionProps, HIRE_TYPE_OPTIONS } from '../model';
|
||||
import { CollapseSection, FormField, TextInput, PhoneInput, Select } from '../../../shared/ui';
|
||||
import { ISectionProps, HIRE_TYPE_OPTIONS, HIRE_TYPE_REPLACEMENT } from '../model';
|
||||
|
||||
export const ApplicationSection: React.FC<ISectionProps> = ({ data, set }) => (
|
||||
<CollapseSection title="Заявка" icon="clipboard">
|
||||
<FormField label="Телефон" htmlFor="Phone">
|
||||
<TextInput id="Phone" value={data.Phone} onChange={(v) => set('Phone', v)} />
|
||||
</FormField>
|
||||
<FormField label="Тип приёма" htmlFor="HireType">
|
||||
<Select
|
||||
id="HireType"
|
||||
value={data.HireType}
|
||||
onChange={(v) => set('HireType', v)}
|
||||
options={HIRE_TYPE_OPTIONS}
|
||||
placeholder="— выберите —"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Взамен кого (Ф.И.О. уволенного)" htmlFor="ReplacedEmployee" full>
|
||||
<TextInput
|
||||
id="ReplacedEmployee"
|
||||
value={data.ReplacedEmployee}
|
||||
onChange={(v) => set('ReplacedEmployee', v)}
|
||||
/>
|
||||
</FormField>
|
||||
</CollapseSection>
|
||||
);
|
||||
export const ApplicationSection: React.FC<ISectionProps> = ({ data, set }) => {
|
||||
const isReplacement = data.HireType === HIRE_TYPE_REPLACEMENT;
|
||||
|
||||
const handleHireTypeChange = (value: string): void => {
|
||||
set('HireType', value);
|
||||
if (value !== HIRE_TYPE_REPLACEMENT) {
|
||||
set('ReplacedEmployee', ''); // при не-замене очищаем скрытое поле
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CollapseSection title="Заявка" icon="clipboard">
|
||||
<FormField label="Телефон" htmlFor="Phone">
|
||||
<PhoneInput id="Phone" value={data.Phone} onChange={(v) => set('Phone', v)} />
|
||||
</FormField>
|
||||
<FormField label="Тип приёма" htmlFor="HireType">
|
||||
<Select
|
||||
id="HireType"
|
||||
value={data.HireType}
|
||||
onChange={handleHireTypeChange}
|
||||
options={HIRE_TYPE_OPTIONS}
|
||||
placeholder="— выберите —"
|
||||
/>
|
||||
</FormField>
|
||||
{isReplacement && (
|
||||
<FormField label="Взамен кого (Ф.И.О. уволенного)" htmlFor="ReplacedEmployee" full>
|
||||
<TextInput
|
||||
id="ReplacedEmployee"
|
||||
value={data.ReplacedEmployee}
|
||||
onChange={(v) => set('ReplacedEmployee', v)}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
</CollapseSection>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import { CollapseSection, FormField, TextInput, Select } from '../../../shared/ui';
|
||||
import { CollapseSection, FormField, TextInput, MoneyInput, Select } from '../../../shared/ui';
|
||||
import { ISectionProps, EMPLOYMENT_FORM_OPTIONS } from '../model';
|
||||
|
||||
export const ConditionsSection: React.FC<ISectionProps> = ({ data, set }) => (
|
||||
<CollapseSection title="Условия оплаты и оформления" icon="calculator">
|
||||
<FormField label="Оклад при испытательном сроке" htmlFor="SalaryProbation">
|
||||
<TextInput
|
||||
<MoneyInput
|
||||
id="SalaryProbation"
|
||||
type="number"
|
||||
value={data.SalaryProbation}
|
||||
onChange={(v) => set('SalaryProbation', v)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Оклад по завершении ИС" htmlFor="SalaryAfterProbation">
|
||||
<TextInput
|
||||
<MoneyInput
|
||||
id="SalaryAfterProbation"
|
||||
type="number"
|
||||
value={data.SalaryAfterProbation}
|
||||
onChange={(v) => set('SalaryAfterProbation', v)}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CollapseSection, FormField, TextInput } from '../../../shared/ui';
|
||||
import { ISectionProps } from '../model';
|
||||
|
||||
export const PpeSection: React.FC<ISectionProps> = ({ data, set }) => (
|
||||
<CollapseSection title="СИЗ (спецодежда)" icon="folder">
|
||||
<CollapseSection title="СИЗ (спецодежда)" icon="helmet">
|
||||
<FormField label="Рост, см" htmlFor="PpeHeight">
|
||||
<TextInput id="PpeHeight" type="number" value={data.PpeHeight} onChange={(v) => set('PpeHeight', v)} />
|
||||
</FormField>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CollapseSection, FormField, PeopleField } from '../../../shared/ui';
|
||||
import { ISectionProps, PERSON_PLACEHOLDER } from '../model';
|
||||
|
||||
export const ResponsibleSection: React.FC<ISectionProps> = ({ data, set, context }) => (
|
||||
<CollapseSection title="Ответственные лица" icon="check">
|
||||
<CollapseSection title="Ответственные лица" icon="user-check">
|
||||
<FormField label="Руководитель участка" full>
|
||||
<PeopleField
|
||||
context={context}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import { CollapseSection, FormField, TextInput, TextArea, Toggle, PeopleField, PeopleFieldMulti } from '../../../shared/ui';
|
||||
import { CollapseSection, FormField, TextInput, TextArea, MoneyInput, Toggle, PeopleField, PeopleFieldMulti } from '../../../shared/ui';
|
||||
import { ISectionProps, PERSON_PLACEHOLDER } from '../model';
|
||||
|
||||
export const ConditionsSection: React.FC<ISectionProps> = ({ data, set, context }) => (
|
||||
<CollapseSection title="Условия работы" icon="chart">
|
||||
<FormField label="Ставка на ИС" htmlFor="RateProbation">
|
||||
<TextInput id="RateProbation" type="number" value={data.RateProbation} onChange={(v) => set('RateProbation', v)} />
|
||||
<MoneyInput id="RateProbation" value={data.RateProbation} onChange={(v) => set('RateProbation', v)} />
|
||||
</FormField>
|
||||
<FormField label="Ставка после ИС" htmlFor="RateAfterProbation">
|
||||
<TextInput
|
||||
<MoneyInput
|
||||
id="RateAfterProbation"
|
||||
type="number"
|
||||
value={data.RateAfterProbation}
|
||||
onChange={(v) => set('RateAfterProbation', v)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user