feat: weather conditions check from stroy-bot-main

Ports the weather check added to stroy-bot-main on 2026-10-16: daily
station weather, report positions with weather per shift and the AI
assessment of working conditions. Opens as ?page=weather&start=&end=
instead of the #weathercheck hash, and hub dates survive the way back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-20 09:47:02 +05:00
parent b18cf50efd
commit d606f8371c
33 changed files with 1365 additions and 12 deletions
+26 -4
View File
@@ -2,6 +2,9 @@ import * as React from 'react';
import { Alert, Badge, BadgeVariant, Button, Icon, Spinner, TextInput } from '@tnsu/ui-kit';
import { IAccessibleProject } from '../../app/project/projectTypes';
import { isExportAvailable } from '../../app/project/projectRules';
import { useRouter } from '../../app/routing/RouterContext';
import { InlineIcon } from '../../shared/components/InlineIcon';
import { WEATHER_TEXTS } from '../weather/texts';
import { getReportType, isInvalidRange, ReportKind } from './exportReport';
import { useReportExport } from './useReportExport';
import { ExportInfoButton } from './ExportInfoButton';
@@ -20,17 +23,22 @@ interface Props {
project: IAccessibleProject | undefined;
}
/** Карточка «Выгрузить за период». Поля дат видны только при выбранном проекте. */
/**
* Карточка «Выгрузить за период» и кнопка «Сверка погодных условий». Поля дат видны только при выбранном проекте.
* Даты берутся из адреса, если вернулись со сверки погоды, — чтобы не выбирать период заново (как в main).
*/
export const ExportCard: React.FC<Props> = ({ project }) => {
const [dateFrom, setDateFrom] = React.useState<string>('');
const [dateTo, setDateTo] = React.useState<string>('');
const { route, navigate } = useRouter();
const [dateFrom, setDateFrom] = React.useState<string>(route.start ?? '');
const [dateTo, setDateTo] = React.useState<string>(route.end ?? '');
const { isExporting, errorMessage, exportReport } = useReportExport();
const isEnabled = !!project && isExportAvailable(project);
const hasBothDates = !!dateFrom && !!dateTo;
const isRangeInvalid = isInvalidRange(dateFrom, dateTo);
const reportType = getReportType(dateFrom, dateTo);
const canExport = isEnabled && hasBothDates && !isRangeInvalid && !isExporting;
const isPeriodReady = isEnabled && hasBothDates && !isRangeInvalid;
const canExport = isPeriodReady && !isExporting;
const onExport = (): void => {
if (project && canExport) {
@@ -38,6 +46,14 @@ export const ExportCard: React.FC<Props> = ({ project }) => {
}
};
const onOpenWeather = (): void => {
if (!project || !isPeriodReady) return;
const projectId = String(project.id);
// Сначала запоминаем даты в адресе главной: «назад» в браузере вернёт их в поля.
navigate({ page: 'hub', projectId, start: dateFrom, end: dateTo }, { replace: true });
navigate({ page: 'weather', projectId, start: dateFrom, end: dateTo });
};
return (
<div className={isEnabled ? styles.exportCard : `${styles.exportCard} ${styles.cardDisabled}`}>
<span className={styles.cardIcon} aria-hidden="true">
@@ -64,6 +80,12 @@ export const ExportCard: React.FC<Props> = ({ project }) => {
<Button variant="accent" onClick={onExport} disabled={!canExport}>
{isExporting ? <Spinner size="xSmall" label={TEXTS.buttonLoading} /> : TEXTS.button}
</Button>
<Button variant="secondary" onClick={onOpenWeather} disabled={!isPeriodReady}>
<span className={styles.buttonContent}>
<InlineIcon name="cloud" />
{WEATHER_TEXTS.openButton}
</span>
</Button>
</div>
)}
@@ -136,6 +136,12 @@ $card-min-width: 260px;
color: $gray-700;
}
.buttonContent {
display: inline-flex;
align-items: center;
gap: $sp-2;
}
.dateError {
font-size: $fs-sm;
color: $red-500;