раздел «Отчёты» в боковом меню — скачивание отчёта по вакансиям (#17)
Tests / frontend-test (push) Successful in 1m15s
Tests / frontend-test (push) Successful in 1m15s
Пункт меню виден только заданному списку почт (Тришина, Городецкий, Горбенко) — группы HR в SharePoint пока нет, добавили TODO завести проверку по ней отдельно (reportsAccess.ts). Скрыт из меню совсем, а не показан неактивным, и прямой переход по ?rpnav=reports у остальных редиректит на «Новую заявку». Раздел — форма периода (С/По) и кнопка «Скачать xlsx», дергает GET api/reports/vacancies через тот же AadHttpClient, что и остальные вызовы approval-route API. Имя файла собирается на фронте по формату бэкенда (VacancyReportWorkbook.FileName), а не читается из Content-Disposition — approval-route не объявляет заголовок в Access-Control-Expose-Headers, браузер бы его не отдал. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/17
This commit was merged in pull request #17.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
@import '../../../../styles/design-tokens';
|
||||
|
||||
.view {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $sp-3;
|
||||
margin-top: $sp-4;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: $sp-2;
|
||||
font-size: $fs-sm;
|
||||
color: $gray-500;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import * as React from 'react';
|
||||
import type { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import { Alert, Button, FormCard, FormField, FormGrid, Icon, TextInput } from '@tnsu/ui-kit';
|
||||
import { downloadVacancyReport } from '../../shared/reports/reports.service';
|
||||
import styles from './ReportsFeature.module.scss';
|
||||
|
||||
// Раздел «Отчёты» — пока один отчёт (по вакансиям), поэтому без списка
|
||||
// карточек: сразу форма периода + кнопка. Появится второй отчёт — тут
|
||||
// понадобится список/переключатель, но городить его заранее незачем.
|
||||
|
||||
function todayIso(): string {
|
||||
const now = new Date();
|
||||
const mm = `${now.getMonth() + 1}`.padStart(2, '0');
|
||||
const dd = `${now.getDate()}`.padStart(2, '0');
|
||||
return `${now.getFullYear()}-${mm}-${dd}`;
|
||||
}
|
||||
|
||||
/** Первое число текущего месяца — разумный дефолт периода отчёта. */
|
||||
function firstDayOfMonthIso(): string {
|
||||
const now = new Date();
|
||||
const mm = `${now.getMonth() + 1}`.padStart(2, '0');
|
||||
return `${now.getFullYear()}-${mm}-01`;
|
||||
}
|
||||
|
||||
export interface IReportsFeatureProps {
|
||||
context: WebPartContext;
|
||||
}
|
||||
|
||||
export const ReportsFeature: React.FC<IReportsFeatureProps> = ({ context }) => {
|
||||
const today = todayIso();
|
||||
const [from, setFrom] = React.useState<string>(firstDayOfMonthIso());
|
||||
const [to, setTo] = React.useState<string>(today);
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
const [error, setError] = React.useState<string>('');
|
||||
|
||||
const periodInvalid = !!from && !!to && from > to;
|
||||
|
||||
const download = (): void => {
|
||||
if (!from || !to || periodInvalid) return;
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
downloadVacancyReport(context, from, to)
|
||||
.catch((e: Error) => setError(e.message || 'Не удалось сформировать отчёт'))
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.view}>
|
||||
<FormCard title="Отчёт по вакансиям" subtitle="Заявки на подбор и на приём за выбранный период">
|
||||
<FormGrid>
|
||||
<FormField label="С" htmlFor="reportFrom" required>
|
||||
<TextInput id="reportFrom" type="date" value={from} onChange={setFrom} max={to || undefined} />
|
||||
</FormField>
|
||||
<FormField label="По" htmlFor="reportTo" required>
|
||||
<TextInput id="reportTo" type="date" value={to} onChange={setTo} min={from || undefined} max={today} />
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
|
||||
{periodInvalid && <Alert variant="warn">Конец периода раньше его начала.</Alert>}
|
||||
{error && <Alert variant="err">{error}</Alert>}
|
||||
|
||||
<div className={styles.actions}>
|
||||
<Button
|
||||
variant="accent"
|
||||
onClick={download}
|
||||
disabled={loading || !from || !to || periodInvalid}
|
||||
>
|
||||
<Icon name="download" size="sm" /> {loading ? 'Формируется…' : 'Скачать xlsx'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className={styles.hint}>
|
||||
Период отбирает заявки по дате подачи заявки на подбор. Заявки на приём в отчёт
|
||||
попадают независимо от периода — по факту закрытия вакансии.
|
||||
</p>
|
||||
</FormCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user