// ============================================
// ソルマーレ長崎FC 情報共有アプリ v2
// React SPA + PWA
// ============================================
const { useState, useEffect, useMemo, useCallback } = React;
// ===== SVG Icons =====
const I = {
Home:()=>,
Calendar:()=>,
Users:()=>,
User:()=>,
Book:()=>,
Settings:()=>,
Clock:()=>,
MapPin:()=>,
Flag:()=>,
Pin:()=>,
Play:()=>,
Plus:()=>,
Car:()=>,
Star:()=>,
ChevL:()=>,
ChevR:()=>,
AlertTri:()=>,
File:()=>,
Bell:()=>,
RefreshCw:()=>,
};
// ===== Data =====
const DOW = ['日','月','火','水','木','金','土'];
const CAT_LABELS = {training:'トレーニング',trm:'TRM',cup:'カップ戦',official:'公式戦',other:'その他'};
const SKILL_LABELS = {shoot:'シュート',pass:'パス',dribble:'ドリブル',tactics:'戦術理解',physical:'フィジカル',gk:'GK',other:'その他'};
const LOC_LABELS = {indoor:'室内',outdoor:'屋外'};
const DEMO_SCHEDULES = [
{id:1,category:'training',title:'通常トレーニング',event_date:'2026-04-04',start_time:'17:00',end_time:'19:00',location:'南部グラウンド',description:'基礎練習とミニゲーム',attendance:{attend:14,absent:2,undecided:4}},
{id:2,category:'trm',title:'TRM vs 長崎南FC',event_date:'2026-04-05',start_time:'09:00',end_time:'12:00',location:'諫早市中央運動公園',gather_time:'08:00',gather_place:'南部グラウンド駐車場',opponent:'長崎南FC',age_group:'U-8',kickoff_time:'09:30',parking_limited:true,parking_limit_num:5,parking_note:'台数制限あり。乗り合わせにご協力ください。',description:'8人制。ユニフォーム上下、水筒、着替え持参。',attendance:{attend:16,absent:1,undecided:3}},
{id:3,category:'training',title:'通常トレーニング',event_date:'2026-04-08',start_time:'17:00',end_time:'19:00',location:'南部グラウンド',attendance:{attend:10,absent:3,undecided:7}},
{id:4,category:'official',title:'長崎市少年サッカー大会',event_date:'2026-04-12',start_time:'08:30',end_time:'16:00',location:'長崎市総合運動公園',gather_time:'07:30',gather_place:'南部グラウンド駐車場',age_group:'U-8',kickoff_time:'09:00',parking_limited:true,parking_limit_num:8,description:'トーナメント戦。弁当持参。',attendance:{attend:18,absent:0,undecided:2}},
{id:5,category:'cup',title:'春季ジュニアカップ',event_date:'2026-04-19',start_time:'08:00',end_time:'15:00',location:'大村市陸上競技場',gather_time:'06:30',gather_place:'南部グラウンド駐車場',opponent:'県内8チーム',age_group:'U-8',kickoff_time:'08:30',description:'グループリーグ+決勝T',attendance:{attend:15,absent:1,undecided:4}},
{id:6,category:'training',title:'通常トレーニング',event_date:'2026-04-11',start_time:'17:00',end_time:'19:00',location:'南部グラウンド',attendance:{attend:8,absent:2,undecided:10}},
];
const DEMO_NEWS = [
{id:1,title:'4月の月会費について',category:'info',content:'4月分の月会費(3,000円)の引き落としは4月10日です。\n口座残高のご確認をお願いいたします。',is_pinned:true,created_by:'山田コーチ',created_at:'2026-04-01'},
{id:2,title:'【結果】3/29 春季交流戦',category:'result',content:'第1試合 ソルマーレ 3-1 長崎北FC\n第2試合 ソルマーレ 2-2 時津少年SC\n第3試合 ソルマーレ 4-0 大村キッカーズ\n\n3戦2勝1分で優勝!',is_pinned:false,created_by:'永野コーチ',created_at:'2026-03-30'},
{id:3,title:'4/12 長崎市大会のご案内',category:'event',content:'4月12日(日)長崎市総合運動公園にて開催。\n集合:7:30 南部グラウンド駐車場\n持ち物:ユニフォーム上下、水筒、弁当、着替え\n\n車出しの協力をお願いできる方は4/8までにご連絡ください。',is_pinned:true,created_by:'永野コーチ',created_at:'2026-03-28'},
{id:4,title:'【緊急】4/5 TRM 集合時間変更',category:'urgent',content:'4月5日のTRMの集合時間が変更になりました。\n\n変更前:8:30\n変更後:8:00\n\nお間違えのないようお願いします。',is_pinned:false,created_by:'永野コーチ',created_at:'2026-04-02'},
];
const DEMO_PLAYERS = [
{id:1,last_name:'田中',first_name:'翔太',grade:'小1',birth_date:'2019-05-12',jersey_number:10},
{id:2,last_name:'山本',first_name:'蓮',grade:'小1',birth_date:'2019-07-03',jersey_number:7},
{id:3,last_name:'佐藤',first_name:'大翔',grade:'小1',birth_date:'2019-04-22',jersey_number:4},
{id:4,last_name:'鈴木',first_name:'陽斗',grade:'小1',birth_date:'2019-09-15',jersey_number:1},
{id:5,last_name:'高橋',first_name:'悠真',grade:'小1',birth_date:'2019-11-08',jersey_number:9},
{id:6,last_name:'伊藤',first_name:'湊',grade:'小1',birth_date:'2019-06-20',jersey_number:8},
{id:7,last_name:'渡辺',first_name:'颯',grade:'小1',birth_date:'2019-03-14',jersey_number:3},
{id:8,last_name:'中村',first_name:'樹',grade:'小1',birth_date:'2019-08-30',jersey_number:6},
{id:9,last_name:'永野',first_name:'○○',grade:'小1',birth_date:'2019-12-01',jersey_number:11},
{id:10,last_name:'小林',first_name:'蒼',grade:'小1',birth_date:'2019-02-18',jersey_number:2},
{id:11,last_name:'加藤',first_name:'陸',grade:'年長',birth_date:'2020-01-25',jersey_number:5},
{id:12,last_name:'吉田',first_name:'凛太朗',grade:'年長',birth_date:'2020-06-11',jersey_number:14},
{id:13,last_name:'松本',first_name:'瑛太',grade:'小1',birth_date:'2019-10-07',jersey_number:13},
{id:14,last_name:'井上',first_name:'律',grade:'年長',birth_date:'2020-04-02',jersey_number:15},
{id:15,last_name:'木村',first_name:'朝陽',grade:'小1',birth_date:'2019-07-19',jersey_number:12},
{id:16,last_name:'林',first_name:'結翔',grade:'小1',birth_date:'2019-05-28',jersey_number:16},
{id:17,last_name:'清水',first_name:'蒼空',grade:'年長',birth_date:'2020-09-05',jersey_number:17},
{id:18,last_name:'山口',first_name:'晴',grade:'小1',birth_date:'2019-04-16',jersey_number:18},
{id:19,last_name:'阿部',first_name:'湊斗',grade:'年長',birth_date:'2020-03-21',jersey_number:19},
{id:20,last_name:'石田',first_name:'碧',grade:'小1',birth_date:'2019-08-08',jersey_number:20},
];
const DEMO_STAFF = [
{id:1,last_name:'田中',first_name:'健一',position_title:'監督',qualifications:'JFA公認C級コーチ / 4級審判'},
{id:2,last_name:'永野',first_name:'',position_title:'アシスタントコーチ',qualifications:'JFA公認D級コーチ'},
{id:3,last_name:'山田',first_name:'太郎',position_title:'コーチ / 会計',qualifications:'JFA公認D級コーチ'},
];
const DEMO_MEMORY = {
player_id:9, shoe_brand:'アシックス DSライト', shoe_size:'20.0cm',
favorite_snack:'inゼリー(マスカット味)', first_goal_date:'2026-03-29',
first_goal_note:'春季交流戦の第3試合、大村キッカーズ戦で左足シュート!', favorite_player:'三笘薫',
personal_best:'50m走 10.2秒', notes:'練習前にストレッチを入念にやると調子がいい'
};
const DEMO_LIBRARY = [
{id:1,url:'https://youtube.com/watch?v=example1',title:'【U-8向け】インサイドキックの基本',location_type:'outdoor',skill_category:'pass',added_by:'永野コーチ'},
{id:2,url:'https://youtube.com/watch?v=example2',title:'室内でできるボールタッチ練習30選',location_type:'indoor',skill_category:'dribble',added_by:'田中監督'},
{id:3,url:'https://tiktok.com/@example/video3',title:'シュートの蹴り方 3つのコツ',location_type:'outdoor',skill_category:'shoot',added_by:'山田コーチ'},
{id:4,url:'https://youtube.com/watch?v=example4',title:'ポジショニングの考え方【ジュニア向け】',location_type:'outdoor',skill_category:'tactics',added_by:'永野コーチ'},
{id:5,url:'https://youtube.com/watch?v=example5',title:'GK基礎:キャッチングとポジション',location_type:'outdoor',skill_category:'gk',added_by:'田中監督'},
{id:6,url:'https://youtube.com/watch?v=example6',title:'雨の日にできる体幹トレーニング',location_type:'indoor',skill_category:'physical',added_by:'永野コーチ'},
];
// ===== Helpers =====
function fmtDate(s){const d=new Date(s+'T00:00:00');return{m:d.getMonth()+1,d:d.getDate(),dow:DOW[d.getDay()],dayOfWeek:d.getDay()};}
function fmtDateFull(s){const{m,d,dow}=fmtDate(s);return `${m}月${d}日(${dow})`;}
function timeAgo(s){const now=new Date();const d=new Date(s+'T00:00:00');const diff=Math.floor((now-d)/(1000*60*60*24));if(diff===0)return'今日';if(diff===1)return'昨日';if(diff<7)return`${diff}日前`;return fmtDateFull(s);}
function greeting(){const h=new Date().getHours();return h<12?'おはようございます':h<18?'こんにちは':'こんばんは';}
// ===== Calendar Helper =====
function getCalendarDays(year, month) {
const first = new Date(year, month, 1);
const last = new Date(year, month + 1, 0);
const days = [];
const startDow = first.getDay();
// Previous month
for (let i = startDow - 1; i >= 0; i--) {
const d = new Date(year, month, -i);
days.push({ date: d, otherMonth: true });
}
// Current month
for (let i = 1; i <= last.getDate(); i++) {
days.push({ date: new Date(year, month, i), otherMonth: false });
}
// Next month fill
const rem = 7 - (days.length % 7);
if (rem < 7) {
for (let i = 1; i <= rem; i++) {
days.push({ date: new Date(year, month + 1, i), otherMonth: true });
}
}
return days;
}
function dateToStr(d) {
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
}
// ===== Components =====
function Header({ onSettings }) {
return (
この期間の予定はありません
お子さまの情報が登録されていません
{mem.shoe_brand}
{mem.shoe_size}
{mem.favorite_snack}
{fmtDateFull(mem.first_goal_date)}
{mem.favorite_player}
{mem.first_goal_note}
{mem.personal_best}
{mem.notes}
該当する動画がありません