feat: add date formatting and stale check for Unyohub entries in TrainDataSources

This commit is contained in:
harukin-expo-dev-env
2026-03-23 01:10:59 +00:00
parent ecc9ee313e
commit 814de31418
3 changed files with 118 additions and 6 deletions

View File

@@ -59,6 +59,24 @@ const formatHHMM = (iso: string): string => {
}
};
/**
* "YYYY-MM-DD HH:MM:SS" 形式の文字列を "M/D HH:MM" 形式にフォーマット
* ISO 8601 文字列にも対応
*/
const formatDateHHMM = (datetime: string): string => {
try {
// "YYYY-MM-DD HH:MM:SS" → space を T に置換して安全にパース
const d = new Date(datetime.replace(" ", "T"));
const mo = d.getMonth() + 1;
const day = d.getDate();
const h = d.getHours().toString().padStart(2, "0");
const m = d.getMinutes().toString().padStart(2, "0");
return `${mo}/${day} ${h}:${m}`;
} catch {
return "";
}
};
export const TrainDataSources: FC<{ payload?: TrainDataSourcesPayload }> = ({
payload,
}) => {
@@ -189,6 +207,37 @@ export const TrainDataSources: FC<{ payload?: TrainDataSourcesPayload }> = ({
(e) => !!e.formations && e.formations.trim() !== "",
);
// 最終投稿日時(エントリ中の最新値)
const unyohubLastPostedDatetime =
unyohubEntries
.map((e) => e.last_posted_datetime)
.filter((d): d is string => !!d)
.sort()
.at(-1) ?? null;
// 投稿日時が今日でない場合はカードを薄く表示("YYYY-MM-DD HH:MM:SS" 形式)
const todayDateStr = new Date().toLocaleDateString("sv"); // "YYYY-MM-DD"
const isUnyohubStale =
unyohubLastPostedDatetime == null ||
!unyohubLastPostedDatetime.startsWith(todayDateStr);
// 運用グループ名重複除去して最大3件
const unyohubGroupNames = [
...new Set(
nonEmptyFormationEntries
.map((e) => e.operation_group_name)
.filter((n) => !!n && n.trim() !== ""),
),
]
.slice(0, 3)
.join(" / ");
// 新フィールドのチップ表示フラグ
const hubHasIsQuotation = unyohubEntries.some((e) => e.is_quotation === true);
const hubHasFromBeginner = unyohubEntries.some((e) => e.from_beginner === true);
const hubHasCommentExists = unyohubEntries.some((e) => e.comment_exists === true);
const hubHasHiddenByDefault = unyohubEntries.some((e) => e.hidden_by_default === true);
// 先頭エントリで direction を取得し、表示順を決定:
// outbound → position_forward 昇順 (pos=1 が宇和島/南端側)
// inbound → position_forward 降順 (pos=MAX が宇和島/南端側)
@@ -218,7 +267,38 @@ export const TrainDataSources: FC<{ payload?: TrainDataSourcesPayload }> = ({
const formationDetail = (
<View style={styles.operationDetailBlock}>
{hasNonEmptyFormations && (
<Text style={[styles.unitIdText, { color: colors.textAccent }]}>{formationNames}</Text>
<Text style={[styles.unitIdText, { color: colors.textAccent, opacity: isUnyohubStale ? 0.4 : 1 }]}>{formationNames}</Text>
)}
{unyohubGroupNames !== "" && (
<Text style={[styles.subText, { color: colors.textSecondary }]}>{unyohubGroupNames}</Text>
)}
{(hubHasIsQuotation || hubHasFromBeginner || hubHasCommentExists || hubHasHiddenByDefault) && (
<View style={styles.metaChipRow}>
{hubHasIsQuotation && (
<View style={styles.metaChip}>
<MaterialCommunityIcons name="content-copy" size={11} color="#888" />
<Text style={styles.metaChipText}></Text>
</View>
)}
{hubHasFromBeginner && (
<View style={[styles.metaChip, styles.metaChipOrange]}>
<MaterialCommunityIcons name="account-school" size={11} color="#f57c00" />
<Text style={[styles.metaChipText, { color: "#f57c00" }]}>稿</Text>
</View>
)}
{hubHasCommentExists && (
<View style={styles.metaChip}>
<MaterialCommunityIcons name="comment-text-outline" size={11} color="#0077aa" />
<Text style={[styles.metaChipText, { color: "#0077aa" }]}></Text>
</View>
)}
{hubHasHiddenByDefault && (
<View style={[styles.metaChip, styles.metaChipOrange]}>
<MaterialCommunityIcons name="eye-off-outline" size={11} color="#f57c00" />
<Text style={[styles.metaChipText, { color: "#f57c00" }]}></Text>
</View>
)}
</View>
)}
<RefDirectionBanner
rows={[{ leftLabel: "宇和島/宿毛/阿波海南" }]}
@@ -363,9 +443,11 @@ export const TrainDataSources: FC<{ payload?: TrainDataSourcesPayload }> = ({
color={colors.textSecondary}
title="鉄道運用Hub"
label="外部コミュニティデータ"
sub={
sub={
hasNonEmptyFormations
? ""
? unyohubLastPostedDatetime
? `最終投稿: ${formatDateHHMM(unyohubLastPostedDatetime)}`
: ""
: unyoCount > 0
? "数日の運用報告なし"
: "この列車の運用データはありません"

View File

@@ -366,6 +366,22 @@ export const injectJavascriptData = ({
return foundUnyos.map(u => u.formations + '(' + u.position_forward + '-' + u.position_rear + ')').join(', ');
};
/** 今日付の投稿があるか判定("YYYY-MM-DD ..." 形式の last_posted_datetime */
const isUnyohubStale = (trainNumber) => {
if (!unyohubData || unyohubData.length === 0) return true;
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
const todayStr = yyyy + '-' + mm + '-' + dd;
const matchedEntries = unyohubData.filter(
u => u.trains && u.trains.some(t => t.train_number === trainNumber)
&& u.formations && u.formations.trim() !== ""
);
if (matchedEntries.length === 0) return true;
return !matchedEntries.some(u => u.last_posted_datetime && u.last_posted_datetime.startsWith(todayStr));
};
const getElesiteFormation = (trainNumber) => {
if (!${useElesite === "true"} || !elesiteData || elesiteData.length === 0) {
return null;
@@ -739,8 +755,9 @@ export const injectJavascriptData = ({
const optionalTextColor = optionalText.includes("最終") ? "red" : "black";
const unyohubFormation = getUnyohubFormation(列番データ);
const hasUnyohub = unyohubFormation !== null;
const unyohubStale = hasUnyohub && isUnyohubStale(列番データ);
if(hasUnyohub) {
console.log('[UnyoHub] Badge shown for', 列番データ, ':', unyohubFormation);
console.log('[UnyoHub] Badge shown for', 列番データ, ':', unyohubFormation, unyohubStale ? '(stale)' : '');
}
const elesiteFormation = getElesiteFormation(列番データ);
const hasElesite = elesiteFormation !== null;
@@ -769,7 +786,8 @@ export const injectJavascriptData = ({
if(hasUnyohub) {
const unyoOffsetPx = _blueOffset;
const offsetStyle = badgeVerticalPos + ":" + unyoOffsetPx + "px;";
badgeHtml += "<div style='position:absolute;" + badgePosition + ":0;" + offsetStyle + "background-color:#ffcc00;border-radius:50%;border:1px solid #000000;width:19px;height:19px;box-sizing:border-box;overflow:hidden;display:flex;align-items:center;justify-content:center;'><img src='https://fossil.2pd.jp/unyohub/raw/5cb01771a1ae8cb73b7bc2048b88f7878ab180aef46247c3901d7cba50d6b71f' style='width:24px;height:24px;margin-left:5px;'/></div>";
const unyoOpacity = unyohubStale ? "opacity:0.35;" : "";
badgeHtml += "<div style='position:absolute;" + badgePosition + ":0;" + offsetStyle + unyoOpacity + "background-color:#ffcc00;border-radius:50%;border:1px solid #000000;width:19px;height:19px;box-sizing:border-box;overflow:hidden;display:flex;align-items:center;justify-content:center;'><img src='https://fossil.2pd.jp/unyohub/raw/5cb01771a1ae8cb73b7bc2048b88f7878ab180aef46247c3901d7cba50d6b71f' style='width:24px;height:24px;margin-left:5px;'/></div>";
}
// えれサイトバッジ(緑)

View File

@@ -33,9 +33,11 @@ export type ElesiteTrain = {
/** 鉄道運用Hub APIデータ型 */
export type UnyohubData = {
operation_id?: string;
/** 運用グループ名 */
operation_group_name: string;
formations: string;
posts_count: number;
from_beginner: boolean;
from_beginner?: boolean;
trains: UnyohubTrain[];
starting_location: string;
starting_track: string;
@@ -47,7 +49,17 @@ export type UnyohubData = {
min_car_count: number;
max_car_count: number;
main_color: string;
/** デフォルトアイコン識別子 */
default_icon?: string;
comment: string | null;
/** コメントが存在するか */
comment_exists?: boolean;
/** 引用データか */
is_quotation?: boolean;
/** 最終投稿日時 */
last_posted_datetime?: string;
/** デフォルトで非表示か */
hidden_by_default?: boolean;
};
export type UnyohubResponse = UnyohubData[];