119 lines
4.0 KiB
TypeScript
119 lines
4.0 KiB
TypeScript
import { useState, useEffect } from "react";
|
||
import { AS } from "../storageControl";
|
||
import { STORAGE_KEYS } from "@/constants";
|
||
import { API_ENDPOINTS } from "@/constants";
|
||
import type { ElesiteResponse, ElesiteData } from "@/types/unyohub";
|
||
|
||
type ElesiteHook = {
|
||
/** えれサイト使用設定 */
|
||
useElesite: boolean;
|
||
/** えれサイトデータ */
|
||
elesiteData: ElesiteResponse;
|
||
/** 指定した列番の運用情報を文字列で取得 */
|
||
getElesiteByTrainNumber: (trainNumber: string) => string | null;
|
||
/** 指定した列番に紐づくエントリの配列を取得 */
|
||
getElesiteEntriesByTrainNumber: (trainNumber: string) => ElesiteData[];
|
||
/** えれサイト使用設定を更新 */
|
||
setUseElesite: (value: boolean) => void;
|
||
};
|
||
|
||
export const useElesite = (): ElesiteHook => {
|
||
const [useElesite, setUseElesiteState] = useState(false);
|
||
const [elesiteData, setElesiteData] = useState<ElesiteResponse>([]);
|
||
|
||
// 初期読み込み
|
||
useEffect(() => {
|
||
AS.getItem(STORAGE_KEYS.USE_ELESITE).then((value) => {
|
||
setUseElesiteState(value === true || value === "true");
|
||
});
|
||
|
||
AS.getItem(STORAGE_KEYS.ELESITE_DATA).then((value) => {
|
||
if (value) {
|
||
try {
|
||
setElesiteData(JSON.parse(value as string));
|
||
} catch (e) {
|
||
console.error("Failed to parse elesite data", e);
|
||
}
|
||
}
|
||
});
|
||
}, []);
|
||
|
||
// データ更新処理
|
||
useEffect(() => {
|
||
if (!useElesite) return;
|
||
|
||
const fetchElesiteData = async () => {
|
||
try {
|
||
const cacheBuster = '?_=' + Date.now();
|
||
const response = await fetch(API_ENDPOINTS.ELESITE_DATA + cacheBuster);
|
||
const data = await response.json();
|
||
setElesiteData(data);
|
||
await AS.setItem(STORAGE_KEYS.ELESITE_DATA, JSON.stringify(data));
|
||
} catch (error) {
|
||
console.error("Failed to fetch elesite data", error);
|
||
}
|
||
};
|
||
|
||
fetchElesiteData();
|
||
|
||
// 10分ごとにデータを更新
|
||
const interval = setInterval(fetchElesiteData, 10 * 60 * 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, [useElesite]);
|
||
|
||
// 列番から編成名を取得(formation_config.units 優先)
|
||
const getElesiteByTrainNumber = (trainNumber: string): string | null => {
|
||
if (!useElesite || elesiteData.length === 0) return null;
|
||
|
||
const results: string[] = [];
|
||
|
||
// 高松(left_station)側のユニットを先に表示
|
||
// (heading_to === "left") === is_leading が true → 高松(left)端のユニット
|
||
const sortedEntries = [...elesiteData].sort((a, b) => {
|
||
const aNav = a.trains?.find(t => t.train_number === trainNumber)?.nav;
|
||
const bNav = b.trains?.find(t => t.train_number === trainNumber)?.nav;
|
||
const aIsLeft = (aNav?.heading_to === "left") === (aNav?.is_leading === true);
|
||
const bIsLeft = (bNav?.heading_to === "left") === (bNav?.is_leading === true);
|
||
if (aIsLeft === bIsLeft) return 0;
|
||
return aIsLeft ? -1 : 1;
|
||
});
|
||
|
||
for (const entry of sortedEntries) {
|
||
if (!entry.trains) continue;
|
||
const found = entry.trains.find(train => train.train_number === trainNumber);
|
||
if (!found) continue;
|
||
// units が1件以上ある場合のみ編成名を返す(空 units は報告なし扱い)
|
||
const units = entry.formation_config?.units;
|
||
const formText = units?.length
|
||
? units.map(u => u.formation).join('+')
|
||
: null;
|
||
if (formText) results.push(formText);
|
||
}
|
||
|
||
return results.length > 0 ? results.join(', ') : null;
|
||
};
|
||
|
||
// 列番に紐づくエントリをすべて取得
|
||
const getElesiteEntriesByTrainNumber = (trainNumber: string): ElesiteData[] => {
|
||
if (!useElesite || elesiteData.length === 0) return [];
|
||
return elesiteData.filter(
|
||
(unyo) => unyo.trains?.some((t) => t.train_number === trainNumber)
|
||
);
|
||
};
|
||
|
||
// 設定を更新
|
||
const setUseElesite = (value: boolean) => {
|
||
setUseElesiteState(value);
|
||
AS.setItem(STORAGE_KEYS.USE_ELESITE, value.toString());
|
||
};
|
||
|
||
return {
|
||
useElesite,
|
||
elesiteData,
|
||
getElesiteByTrainNumber,
|
||
getElesiteEntriesByTrainNumber,
|
||
setUseElesite,
|
||
};
|
||
};
|