"use client"; import { useEffect, useState } from "react"; import { formatTime, useSchedules, useStartCitySweep, useUpdateSchedule, utcToMsk, mskToUtc, type ScheduleSourceKey, type ScraperSource, type SweepParamConfig, } from "@/app/scrapers/_components/ScraperPage"; // ── ScheduleControl ──────────────────────────────────────────────────────── interface ScheduleControlProps { source: ScraperSource; scheduleSource: ScheduleSourceKey; paramConfig: SweepParamConfig; } export function ScheduleControl({ source, scheduleSource, paramConfig, }: ScheduleControlProps) { const schedulesQ = useSchedules(); const updateScheduleMut = useUpdateSchedule(); const sweepMut = useStartCitySweep(source); const [schEnabled, setSchEnabled] = useState(true); const [schMskStart, setSchMskStart] = useState(5); const [schMskEnd, setSchMskEnd] = useState(8); const [schPages, setSchPages] = useState( paramConfig.defaults.pages_per_anchor, ); const [schTopN, setSchTopN] = useState( paramConfig.defaults.detail_top_n ?? 0, ); const [schDelay, setSchDelay] = useState( paramConfig.defaults.request_delay_sec, ); const [schRadius, setSchRadius] = useState(paramConfig.defaults.radius_m); const [schEnrichHouses, setSchEnrichHouses] = useState( paramConfig.defaults.enrich_houses ?? false, ); const [schEnrichAddress, setSchEnrichAddress] = useState( paramConfig.defaults.enrich_address ?? false, ); useEffect(() => { const sweep = schedulesQ.data?.find((s) => s.source === scheduleSource); if (!sweep) return; setSchEnabled(sweep.enabled); setSchMskStart(utcToMsk(sweep.window_start_hour)); setSchMskEnd(utcToMsk(sweep.window_end_hour)); const p = sweep.default_params ?? {}; if (typeof p.pages_per_anchor === "number") setSchPages(p.pages_per_anchor); if (typeof p.detail_top_n === "number") setSchTopN(p.detail_top_n); if (typeof p.request_delay_sec === "number") setSchDelay(p.request_delay_sec); if (typeof p.radius_m === "number") setSchRadius(p.radius_m); if (typeof p.enrich_houses === "boolean") setSchEnrichHouses(p.enrich_houses); if (typeof p.enrich_address === "boolean") setSchEnrichAddress(p.enrich_address); }, [schedulesQ.data, scheduleSource]); const currentSweep = schedulesQ.data?.find( (s) => s.source === scheduleSource, ); function handleSweepTrigger() { const body: Record = { pages_per_anchor: schPages, request_delay_sec: schDelay, radius_m: schRadius, }; if (paramConfig.hasDetailParams) { body.detail_top_n = schTopN; body.enrich_houses = schEnrichHouses; } if (paramConfig.hasEnrichAddress) { body.enrich_address = schEnrichAddress; } sweepMut.mutate(body); } return (

Расписание и ручной запуск

{schedulesQ.isPending && (

Загрузка расписания…

)} {schedulesQ.isError && (

Ошибка загрузки: {schedulesQ.error.message}

)} {/* Current status */} {currentSweep && (
Статус: {currentSweep.enabled ? "включено" : "отключено"}
Окно (МСК): {String(utcToMsk(currentSweep.window_start_hour)).padStart( 2, "0", )} :00– {String(utcToMsk(currentSweep.window_end_hour)).padStart( 2, "0", )} :00
Следующий: {formatTime(currentSweep.next_run_at)}
Последний: {currentSweep.last_run_at ? formatTime(currentSweep.last_run_at) : "—"} {currentSweep.last_run_id && ` (run #${currentSweep.last_run_id})`}
)} {/* Schedule settings form */}
{ e.preventDefault(); const defaultParams: Record = { pages_per_anchor: schPages, request_delay_sec: schDelay, radius_m: schRadius, }; if (paramConfig.hasDetailParams) { defaultParams.detail_top_n = schTopN; defaultParams.enrich_houses = schEnrichHouses; } if (paramConfig.hasEnrichAddress) { defaultParams.enrich_address = schEnrichAddress; } updateScheduleMut.mutate({ source: scheduleSource, payload: { enabled: schEnabled, window_start_hour: mskToUtc(schMskStart), window_end_hour: mskToUtc(schMskEnd), default_params: defaultParams, }, }); }} > {paramConfig.hasDetailParams && ( )} {paramConfig.hasDetailParams && ( )} {paramConfig.hasEnrichAddress && ( )} {updateScheduleMut.isError && (
Ошибка: {updateScheduleMut.error.message}
)} {updateScheduleMut.isSuccess && updateScheduleMut.data && (
Расписание обновлено. Next run:{" "} {formatTime(updateScheduleMut.data.next_run_at)}
)} {/* Manual trigger */}

Ручной запуск city sweep с параметрами выше:

{sweepMut.isError && (
Ошибка: {sweepMut.error.message}
)} {sweepMut.isSuccess && sweepMut.data && (
              {JSON.stringify(sweepMut.data, null, 2)}
            
)}
); }