お店の経営ツール「Mise Manager」

【更新履歴】

 ・2026/2/20 バージョン1.0公開。
 ・2026/2/20 バージョン1.1公開。(原料在庫を追加)

画像
画像
画像
画像
画像

・ダウンロードされる方はこちら。↓

https://drive.google.com/drive/folders/1YXZyfO9mndQnj0MMkmiM1nsdGJEGvlGB?ths=true

・ソースコードはこちら。↓

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MISE Manager 1.1 — 在庫・経理管理</title>
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&family=M+PLUS+Rounded+1c:wght@400;500;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
:root{
  --bg:#f4f7ff;--bg2:#eaf0ff;--surface:#ffffff;
  --navy:#1a2f6e;--blue:#3b6ef5;--blue-l:#dce8ff;
  --mint:#22c55e;--mint-l:#dcfce7;
  --coral:#ef4444;--coral-l:#fee2e2;
  --amber:#f59e0b;--amber-l:#fef3c7;
  --purple:#8b5cf6;--purple-l:#ede9fe;
  --sky:#0ea5e9;--sky-l:#e0f2fe;
  --text:#1e293b;--text2:#64748b;--text3:#94a3b8;
  --border:#e2e8f0;--shadow:0 4px 20px rgba(30,60,140,.08);
  --r:16px;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html,body{height:100%;overflow:hidden;}
body{background:var(--bg);color:var(--text);font-family:'M PLUS Rounded 1c',sans-serif;font-size:14px;display:flex;flex-direction:column;}

/* ── Header ── */
.mgr-header{background:linear-gradient(135deg,var(--navy) 0%,#2d4a9e 100%);color:#fff;padding:0 24px;height:56px;display:flex;align-items:center;gap:16px;flex-shrink:0;box-shadow:0 3px 16px rgba(26,47,110,.35);}
.mgr-logo{font-family:'Nunito',sans-serif;font-size:20px;font-weight:900;letter-spacing:.03em;display:flex;align-items:center;gap:8px;}
.mgr-logo .badge{font-size:11px;font-weight:700;background:var(--amber);color:var(--navy);padding:2px 8px;border-radius:20px;}
.hdr-clock{font-family:'Nunito',monospace;font-size:15px;font-weight:800;opacity:.8;margin-left:auto;}
.hdr-actions{display:flex;gap:8px;}
.hdr-btn{background:rgba(255,255,255,.15);border:1.5px solid rgba(255,255,255,.3);color:#fff;border-radius:10px;padding:6px 14px;font-size:12px;font-family:inherit;font-weight:700;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:5px;}
.hdr-btn:hover{background:rgba(255,255,255,.28);}
.hdr-btn.primary{background:var(--blue);border-color:var(--blue);}

/* ── Layout ── */
.mgr-wrap{display:flex;flex:1;overflow:hidden;}
.sidebar{width:200px;background:var(--surface);border-right:2px solid var(--border);display:flex;flex-direction:column;flex-shrink:0;overflow:hidden;}
.main-content{flex:1;overflow-y:auto;overflow-x:hidden;}
.main-content::-webkit-scrollbar{width:5px;}
.main-content::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px;}

/* ── Sidebar Nav ── */
.nav-section{padding:12px 10px 4px;font-size:10px;font-weight:800;color:var(--text3);letter-spacing:.08em;}
.nav-item{display:flex;align-items:center;gap:10px;padding:10px 14px;margin:2px 8px;border-radius:12px;cursor:pointer;font-size:13px;font-weight:700;color:var(--text2);transition:all .15s;border:none;background:none;width:calc(100% - 16px);text-align:left;font-family:inherit;}
.nav-item:hover{background:var(--bg2);color:var(--navy);}
.nav-item.active{background:var(--blue-l);color:var(--blue);}
.nav-item .nav-icon{font-size:18px;flex-shrink:0;}
.nav-item .nav-badge{margin-left:auto;background:var(--coral);color:#fff;font-size:10px;font-weight:800;padding:1px 6px;border-radius:10px;min-width:18px;text-align:center;}
.nav-footer{margin-top:auto;padding:12px;border-top:2px solid var(--border);}
.nav-store{font-size:11px;font-weight:700;color:var(--text3);text-align:center;}

/* ── Panel ── */
.panel{display:none;padding:24px;min-height:100%;}
.panel.active{display:block;}
.panel-title{font-size:20px;font-weight:800;color:var(--navy);margin-bottom:4px;display:flex;align-items:center;gap:8px;}
.panel-sub{font-size:13px;color:var(--text2);margin-bottom:20px;}

/* ── KPI Cards ── */
.kpi-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;}
.kpi-card{background:var(--surface);border-radius:var(--r);padding:18px 20px;box-shadow:var(--shadow);border-bottom:4px solid transparent;position:relative;overflow:hidden;}
.kpi-card::before{content:'';position:absolute;top:-20px;right:-20px;width:80px;height:80px;border-radius:50%;opacity:.08;}
.kpi-card.kpi-blue{border-color:var(--blue);}
.kpi-card.kpi-blue::before{background:var(--blue);}
.kpi-card.kpi-mint{border-color:var(--mint);}
.kpi-card.kpi-mint::before{background:var(--mint);}
.kpi-card.kpi-amber{border-color:var(--amber);}
.kpi-card.kpi-amber::before{background:var(--amber);}
.kpi-card.kpi-coral{border-color:var(--coral);}
.kpi-card.kpi-coral::before{background:var(--coral);}
.kpi-card.kpi-purple{border-color:var(--purple);}
.kpi-card.kpi-purple::before{background:var(--purple);}
.kpi-label{font-size:11px;font-weight:800;color:var(--text2);margin-bottom:6px;letter-spacing:.04em;}
.kpi-val{font-family:'Nunito',sans-serif;font-size:26px;font-weight:900;color:var(--text);line-height:1;}
.kpi-val small{font-size:14px;font-weight:700;}
.kpi-diff{font-size:11px;font-weight:700;margin-top:5px;display:flex;align-items:center;gap:3px;}
.kpi-diff.up{color:var(--mint);}
.kpi-diff.down{color:var(--coral);}
.kpi-diff.flat{color:var(--text3);}
.kpi-icon{position:absolute;top:14px;right:16px;font-size:24px;opacity:.6;}

/* ── Section Card ── */
.s-card{background:var(--surface);border-radius:var(--r);padding:20px;box-shadow:var(--shadow);margin-bottom:16px;}
.s-card-title{font-size:14px;font-weight:800;color:var(--navy);margin-bottom:14px;display:flex;align-items:center;gap:6px;justify-content:space-between;}
.s-card-title span{font-size:12px;font-weight:700;color:var(--text2);}

/* ── Grid layouts ── */
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;}
.grid-auto{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px;}

/* ── Charts ── */
.chart-wrap{position:relative;height:240px;}
.chart-wrap.tall{height:320px;}

/* ── Table ── */
.tbl{width:100%;border-collapse:collapse;font-size:13px;}
.tbl th{background:var(--bg2);padding:8px 12px;text-align:left;font-size:11px;font-weight:800;color:var(--text2);border-bottom:2px solid var(--border);}
.tbl td{padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:middle;}
.tbl tr:last-child td{border-bottom:none;}
.tbl tr:hover td{background:var(--bg);}
.tbl .num{text-align:right;font-family:'Nunito',sans-serif;font-weight:700;}
.tbl .good{color:var(--mint);}
.tbl .warn{color:var(--amber);}
.tbl .bad{color:var(--coral);}
.tbl-scroll{overflow-x:auto;overflow-y:auto;max-height:320px;}
.tbl-scroll::-webkit-scrollbar{height:4px;width:4px;}
.tbl-scroll::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px;}

/* ── Badge/Chip ── */
.chip{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;}
.chip-blue{background:var(--blue-l);color:var(--blue);}
.chip-mint{background:var(--mint-l);color:#16a34a;}
.chip-coral{background:var(--coral-l);color:var(--coral);}
.chip-amber{background:var(--amber-l);color:#92400e;}
.chip-purple{background:var(--purple-l);color:var(--purple);}
.chip-gray{background:var(--bg2);color:var(--text2);}

/* ── Progress bar ── */
.prog-bar{height:8px;background:var(--bg2);border-radius:4px;overflow:hidden;margin-top:4px;}
.prog-fill{height:100%;border-radius:4px;transition:width .4s;}

/* ── Alert ── */
.alert{display:flex;align-items:flex-start;gap:10px;padding:12px 16px;border-radius:12px;margin-bottom:10px;font-size:13px;}
.alert-warn{background:var(--amber-l);border-left:4px solid var(--amber);}
.alert-bad{background:var(--coral-l);border-left:4px solid var(--coral);}
.alert-good{background:var(--mint-l);border-left:4px solid var(--mint);}
.alert-info{background:var(--blue-l);border-left:4px solid var(--blue);}
.alert-icon{font-size:18px;flex-shrink:0;}
.alert-body{flex:1;}
.alert-title{font-weight:800;margin-bottom:2px;}
.alert-text{color:var(--text2);font-size:12px;line-height:1.5;}

/* ── Form controls ── */
.f-row{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin-bottom:14px;}
.f-group{display:flex;flex-direction:column;gap:4px;}
.f-group label{font-size:11px;font-weight:800;color:var(--text2);}
.f-input{border:2px solid var(--border);border-radius:10px;padding:8px 12px;font-size:13px;font-family:inherit;font-weight:600;color:var(--text);background:var(--surface);outline:none;transition:border-color .15s;}
.f-input:focus{border-color:var(--blue);}
.f-input[type="number"]{width:120px;}
.f-btn{border:none;border-radius:10px;padding:9px 18px;font-size:13px;font-weight:800;cursor:pointer;font-family:inherit;transition:all .15s;display:flex;align-items:center;gap:6px;white-space:nowrap;}
.f-btn-blue{background:var(--blue);color:#fff;}
.f-btn-blue:hover{background:#2d5ee8;}
.f-btn-mint{background:var(--mint);color:#fff;}
.f-btn-mint:hover{background:#16a34a;}
.f-btn-coral{background:var(--coral);color:#fff;}
.f-btn-coral:hover{background:#dc2626;}
.f-btn-ghost{background:var(--bg2);color:var(--text2);}
.f-btn-ghost:hover{background:var(--border);}
.f-btn-amber{background:var(--amber);color:#fff;}
.f-btn-amber:hover{background:#d97706;}
select.f-input{cursor:pointer;}

/* ── Stock card ── */
.stock-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;}
.stock-card{background:var(--surface);border:2px solid var(--border);border-radius:14px;padding:14px;position:relative;transition:all .15s;}
.stock-card:hover{box-shadow:var(--shadow);border-color:var(--blue-l);}
.stock-card.warn-low{border-color:var(--amber);background:var(--amber-l);}
.stock-card.warn-out{border-color:var(--coral);background:var(--coral-l);}
.stock-icon{font-size:28px;margin-bottom:6px;display:block;}
.stock-name{font-size:13px;font-weight:800;margin-bottom:4px;line-height:1.2;}
.stock-cat{font-size:10px;color:var(--text2);font-weight:700;margin-bottom:8px;}
.stock-qty{font-family:'Nunito',sans-serif;font-size:28px;font-weight:900;line-height:1;}
.stock-qty small{font-size:12px;font-weight:700;color:var(--text2);}
.stock-meta{display:flex;gap:6px;margin-top:6px;flex-wrap:wrap;}
.stock-alert-badge{position:absolute;top:10px;right:10px;font-size:16px;}
.stock-edit-btn{background:var(--bg2);border:none;border-radius:8px;padding:4px 8px;font-size:11px;font-weight:700;cursor:pointer;font-family:inherit;color:var(--text2);transition:all .15s;}
.stock-edit-btn:hover{background:var(--blue-l);color:var(--blue);}

/* ── P&L table ── */
.pl-section{margin-bottom:16px;}
.pl-row{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-radius:10px;margin-bottom:3px;}
.pl-row.header{background:var(--bg2);font-size:11px;font-weight:800;color:var(--text2);}
.pl-row.item{font-size:13px;}
.pl-row.item:hover{background:var(--bg);}
.pl-row.subtotal{background:var(--bg2);font-weight:800;font-size:13px;}
.pl-row.total{background:var(--navy);color:#fff;font-size:16px;font-weight:800;border-radius:12px;}
.pl-row.total .num{color:var(--amber);}
.pl-row .num{font-family:'Nunito',sans-serif;font-weight:800;}
.pl-row .add-btn{background:var(--blue-l);border:none;border-radius:6px;padding:2px 8px;font-size:11px;font-weight:700;cursor:pointer;color:var(--blue);font-family:inherit;}

/* ── Purchase log ── */
.purchase-item{display:flex;align-items:center;gap:12px;padding:10px 14px;background:var(--surface);border:2px solid var(--border);border-radius:12px;margin-bottom:8px;transition:all .15s;}
.purchase-item:hover{border-color:var(--blue-l);box-shadow:var(--shadow);}
.purchase-icon{font-size:22px;flex-shrink:0;}
.purchase-info{flex:1;}
.purchase-name{font-size:13px;font-weight:800;}
.purchase-meta{font-size:11px;color:var(--text2);margin-top:2px;}
.purchase-amt{font-family:'Nunito',sans-serif;font-size:16px;font-weight:900;color:var(--blue);}
.purchase-del{border:none;background:none;color:var(--text3);cursor:pointer;font-size:16px;padding:2px;transition:color .15s;}
.purchase-del:hover{color:var(--coral);}

/* ── Analysis ── */
.rank-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:10px;margin-bottom:6px;background:var(--bg);}
.rank-no{font-family:'Nunito',sans-serif;font-size:18px;font-weight:900;color:var(--text3);min-width:28px;text-align:center;}
.rank-no.top1{color:var(--amber);}
.rank-no.top2{color:var(--text2);}
.rank-no.top3{color:#cd7f32;}
.rank-bar-wrap{flex:1;}
.rank-name{font-size:13px;font-weight:700;margin-bottom:2px;}
.rank-bar-track{height:6px;background:var(--border);border-radius:4px;overflow:hidden;}
.rank-bar-fill{height:100%;border-radius:4px;background:var(--blue);transition:width .5s;}
.rank-val{font-family:'Nunito',sans-serif;font-size:14px;font-weight:800;color:var(--text);text-align:right;min-width:80px;}

/* ── Period filter ── */
.period-bar{display:flex;gap:6px;margin-bottom:16px;flex-wrap:wrap;align-items:center;}
.period-btn{border:2px solid var(--border);background:var(--surface);border-radius:10px;padding:6px 14px;font-size:12px;font-weight:800;cursor:pointer;font-family:inherit;color:var(--text2);transition:all .15s;}
.period-btn.active{background:var(--navy);border-color:var(--navy);color:#fff;}
.period-btn:hover:not(.active){border-color:var(--blue);color:var(--blue);}

/* ── Toast ── */
.toast{position:fixed;top:70px;left:50%;transform:translateX(-50%);background:var(--navy);color:#fff;padding:10px 22px;border-radius:20px;font-size:13px;font-weight:700;z-index:9999;opacity:0;transition:opacity .25s;pointer-events:none;white-space:nowrap;}
.toast.show{opacity:1;}

/* ── Modal ── */
.modal{position:fixed;inset:0;background:rgba(0,20,60,.45);backdrop-filter:blur(4px);z-index:5000;display:flex;align-items:center;justify-content:center;animation:fadeIn .2s;}
.modal.hidden{display:none;}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
.modal-box{background:var(--surface);border-radius:24px;padding:28px;max-width:500px;width:92%;box-shadow:0 20px 60px rgba(0,0,0,.25);animation:slideUp .25s;}
@keyframes slideUp{from{transform:translateY(24px);opacity:0}to{transform:translateY(0);opacity:1}}
.modal-title{font-size:18px;font-weight:800;margin-bottom:16px;color:var(--navy);}
.modal-btns{display:flex;gap:10px;margin-top:20px;}
.modal-btns button{flex:1;border:none;border-radius:12px;padding:11px;font-size:14px;font-weight:800;cursor:pointer;font-family:inherit;transition:all .15s;}
.mbtn-cancel{background:var(--bg2);color:var(--text2);}
.mbtn-ok{background:var(--blue);color:#fff;}

/* ── Upload area ── */
.upload-zone{display:block;border:3px dashed var(--border);border-radius:16px;padding:32px;text-align:center;cursor:pointer;transition:all .2s;background:var(--bg);}
.upload-zone:hover,.upload-zone.drag{border-color:var(--blue);background:var(--blue-l);}
.upload-zone input{display:none;}

/* ── Responsive ── */
@media(max-width:900px){
  .sidebar{width:56px;}
  .nav-item span:not(.nav-icon){display:none;}
  .nav-badge{display:none;}
  .kpi-grid{grid-template-columns:1fr 1fr;}
  .grid-2,.grid-3{grid-template-columns:1fr;}
}
@media(max-width:600px){
  .kpi-grid{grid-template-columns:1fr;}
  .sidebar{display:none;}
}

/* ── 原料カード ── */
.ingr-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px;}
.ingr-card{background:var(--surface);border:2px solid var(--border);border-radius:14px;padding:14px;position:relative;transition:all .15s;cursor:default;}
.ingr-card:hover{box-shadow:var(--shadow);border-color:var(--blue-l);}
.ingr-card.warn-low{border-color:var(--amber);background:var(--amber-l);}
.ingr-card.warn-out{border-color:var(--coral);background:var(--coral-l);}
.ingr-unit{font-size:10px;font-weight:700;background:var(--bg2);color:var(--text2);border-radius:6px;padding:1px 7px;display:inline-block;margin-bottom:6px;}
.ingr-name{font-size:14px;font-weight:800;margin-bottom:2px;line-height:1.2;}
.ingr-cat{font-size:10px;color:var(--text2);margin-bottom:8px;}
.ingr-qty-wrap{display:flex;align-items:baseline;gap:4px;}
.ingr-qty{font-family:'Nunito',sans-serif;font-size:28px;font-weight:900;color:var(--text);line-height:1;}
.ingr-qty-unit{font-size:13px;color:var(--text2);font-weight:700;}
.ingr-theory{font-size:11px;color:var(--text2);margin-top:2px;}
.ingr-alert{position:absolute;top:10px;right:10px;font-size:16px;}
.ingr-actions{display:flex;gap:6px;margin-top:8px;}
.ingr-act-btn{flex:1;border:none;border-radius:8px;padding:4px 0;font-size:11px;font-weight:700;cursor:pointer;font-family:inherit;transition:all .15s;}
.ingr-act-btn.edit{background:var(--blue-l);color:var(--blue);}
.ingr-act-btn.edit:hover{background:var(--blue);color:#fff;}
.ingr-act-btn.del{background:var(--coral-l);color:var(--coral);}
.ingr-act-btn.del:hover{background:var(--coral);color:#fff;}

/* ── レシピカード ── */
.recipe-list{display:flex;flex-direction:column;gap:8px;}
.recipe-card{background:var(--surface);border:2px solid var(--border);border-radius:12px;overflow:hidden;}
.recipe-head{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;transition:background .15s;}
.recipe-head:hover{background:var(--bg);}
.recipe-prod-icon{font-size:20px;}
.recipe-prod-name{flex:1;font-size:13px;font-weight:800;}
.recipe-ing-count{font-size:11px;color:var(--text2);background:var(--bg2);border-radius:6px;padding:2px 8px;}
.recipe-toggle{font-size:14px;color:var(--text3);transition:transform .2s;}
.recipe-toggle.open{transform:rotate(180deg);}
.recipe-body{display:none;padding:0 14px 12px;border-top:1px solid var(--border);}
.recipe-body.open{display:block;}
.recipe-ing-row{display:flex;justify-content:space-between;align-items:center;padding:5px 0;border-bottom:1px dotted var(--border);font-size:12px;}
.recipe-ing-row:last-child{border-bottom:none;}
.recipe-del-btn{border:none;background:none;color:var(--text3);cursor:pointer;font-size:14px;padding:0 2px;}
.recipe-del-btn:hover{color:var(--coral);}

/* ── 棚卸しテーブル ── */
.stocktake-row td input{width:90px;border:2px solid var(--border);border-radius:8px;padding:4px 8px;font-size:13px;font-family:inherit;text-align:right;outline:none;}
.stocktake-row td input:focus{border-color:var(--blue);}
.loss-pos{color:var(--coral);font-weight:800;}
.loss-neg{color:var(--mint);font-weight:800;}

/* scrollbar */
::-webkit-scrollbar{width:5px;height:5px;}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px;}
</style>
</head>
<body>

<header class="mgr-header">
  <div class="mgr-logo">📊 MISE Manager<span class="badge">経営</span></div>
  <div id="hdr-clock" class="hdr-clock"></div>
  <div class="hdr-actions">
    <button class="hdr-btn" onclick="openImportModal()">📂 CSVを読み込む</button>
    <button class="hdr-btn primary" onclick="exportReport()">📤 レポート出力</button>
  </div>
</header>

<div class="mgr-wrap">

  <nav class="sidebar">
    <div class="nav-section">メニュー</div>
    <button class="nav-item active" onclick="showPanel('dashboard',this)"><span class="nav-icon">📊</span><span>ダッシュボード</span></button>
    <button class="nav-item" onclick="showPanel('stock',this)"><span class="nav-icon">📦</span><span>在庫管理</span><span class="nav-badge" id="nb-stock" style="display:none">0</span></button>
    <button class="nav-item" onclick="showPanel('finance',this)"><span class="nav-icon">💰</span><span>収支・経理</span></button>
    <button class="nav-item" onclick="showPanel('analysis',this)"><span class="nav-icon">📈</span><span>売上分析</span></button>
    <button class="nav-item" onclick="showPanel('purchase',this)"><span class="nav-icon">🗒️</span><span>仕入れ帳</span></button>
    <button class="nav-item" onclick="showPanel('settings',this)"><span class="nav-icon">⚙️</span><span>設定</span></button>
    <div class="nav-footer">
      <div class="nav-store" id="nav-store">店舗未設定</div>
    </div>
  </nav>

  <main class="main-content">

    <div id="panel-dashboard" class="panel active">
      <div class="panel-title">📊 ダッシュボード</div>
      <div class="panel-sub" id="dash-date-label">本日の経営状況をひと目でチェック</div>

      <div id="dash-alerts"></div>

      <div class="kpi-grid" id="kpi-grid">
        <div class="kpi-card kpi-blue">
          <div class="kpi-label">今日の売上</div>
          <div class="kpi-val" id="kpi-today-sales">¥—</div>
          <div class="kpi-diff flat" id="kpi-today-diff">—</div>
          <div class="kpi-icon">💴</div>
        </div>
        <div class="kpi-card kpi-mint">
          <div class="kpi-label">今月の売上</div>
          <div class="kpi-val" id="kpi-month-sales">¥—</div>
          <div class="kpi-diff flat" id="kpi-month-diff">—</div>
          <div class="kpi-icon">📅</div>
        </div>
        <div class="kpi-card kpi-amber">
          <div class="kpi-label">今月の客数</div>
          <div class="kpi-val" id="kpi-month-tx"><small></small>—</div>
          <div class="kpi-diff flat" id="kpi-month-tx-diff">—</div>
          <div class="kpi-icon">👥</div>
        </div>
        <div class="kpi-card kpi-purple">
          <div class="kpi-label">今月の客単価</div>
          <div class="kpi-val" id="kpi-atv">¥—</div>
          <div class="kpi-diff flat" id="kpi-atv-diff">—</div>
          <div class="kpi-icon">🎯</div>
        </div>
      </div>

      <div class="grid-2">
        <div class="s-card">
          <div class="s-card-title">📈 直近30日の売上推移 <span>日別</span></div>
          <div class="chart-wrap"><canvas id="ch-daily"></canvas></div>
        </div>
        <div class="s-card">
          <div class="s-card-title">🍰 今月のカテゴリ別売上 <span></span></div>
          <div class="chart-wrap"><canvas id="ch-cat-pie"></canvas></div>
        </div>
      </div>

      <div class="s-card">
        <div class="s-card-title">⚠️ 在庫アラート <span id="stock-alert-count"></span></div>
        <div id="dash-stock-alerts">
          <div style="color:var(--text3);text-align:center;padding:20px;font-size:13px;">データがありません</div>
        </div>
      </div>

      <div class="s-card">
        <div class="s-card-title">🏆 今月のトップ商品(売上金額) <span></span></div>
        <div id="dash-top-products"></div>
      </div>
    </div>

    <div id="panel-stock" class="panel">
      <div class="panel-title">📦 在庫管理</div>
      <div class="panel-sub">商品の販売在庫と、原料・食材の在庫を両方管理できます</div>

      <div class="period-bar" id="stock-main-tabs" style="margin-bottom:20px;">
        <button class="period-btn active" onclick="showStockMainTab('product',this)">🛍️ 商品在庫</button>
        <button class="period-btn" onclick="showStockMainTab('ingredient',this)">🧂 原料在庫</button>
      </div>

      <div id="stock-product-area">

      <div class="grid-2" style="margin-bottom:16px;">
        <div class="s-card">
          <div class="s-card-title">➕ 在庫を更新</div>
          <div class="f-group" style="margin-bottom:10px;">
            <label>商品を選択</label>
            <select class="f-input" id="stock-prod-sel" style="width:100%;"></select>
          </div>
          <div class="f-row">
            <div class="f-group">
              <label>操作種類</label>
              <select class="f-input" id="stock-op" style="width:140px;">
                <option value="set">在庫数をセット</option>
                <option value="add">追加(仕入れ)</option>
                <option value="sub">減らす(廃棄など)</option>
              </select>
            </div>
            <div class="f-group">
              <label>数量</label>
              <input type="number" class="f-input" id="stock-qty" min="0" placeholder="10">
            </div>
          </div>
          <div class="f-row">
            <div class="f-group" style="flex:1;">
              <label>メモ(任意)</label>
              <input class="f-input" id="stock-memo" placeholder="例: 朝仕入れ" style="width:100%;">
            </div>
          </div>
          <button class="f-btn f-btn-blue" style="width:100%" onclick="updateStock()">✅ 在庫を更新する</button>
        </div>
        <div class="s-card">
          <div class="s-card-title">🔔 アラート設定</div>
          <div class="f-group" style="margin-bottom:10px;">
            <label>商品を選択</label>
            <select class="f-input" id="alert-prod-sel" style="width:100%;"></select>
          </div>
          <div class="f-row">
            <div class="f-group">
              <label>警告ライン(残りX個以下)</label>
              <input type="number" class="f-input" id="alert-warn" min="0" placeholder="5" style="width:110px;">
            </div>
            <div class="f-group">
              <label>危険ライン(残りX個以下)</label>
              <input type="number" class="f-input" id="alert-danger" min="0" placeholder="2" style="width:110px;">
            </div>
          </div>
          <button class="f-btn f-btn-amber" style="width:100%" onclick="saveAlertSetting()">💾 アラート設定を保存</button>
        </div>
      </div>

      <div class="f-row" style="margin-bottom:12px;">
        <button class="period-btn active" onclick="filterStock('all',this)">すべて</button>
        <button class="period-btn" onclick="filterStock('danger',this)">🔴 危険</button>
        <button class="period-btn" onclick="filterStock('warn',this)">🟡 警告</button>
        <button class="period-btn" onclick="filterStock('ok',this)">🟢 正常</button>
        <input class="f-input" id="stock-search" placeholder="🔍 商品名で検索..." onInput="renderStockGrid()" style="margin-left:auto;width:200px;">
      </div>

      <div class="stock-grid" id="stock-grid"></div>

      </div><div id="stock-ingr-area" style="display:none;">

      <div class="period-bar" style="margin-bottom:16px;" id="stock-tab-bar">
        <button class="period-btn active" onclick="showStockTab('list',this)">📋 在庫一覧</button>
        <button class="period-btn" onclick="showStockTab('ingr-edit',this)">➕ 原料登録</button>
        <button class="period-btn" onclick="showStockTab('adjust',this)">📥 入出庫</button>
        <button class="period-btn" onclick="showStockTab('recipe',this)">📖 レシピ</button>
        <button class="period-btn" onclick="showStockTab('stocktake',this)">📊 棚卸し</button>
      </div>

      <div id="stock-tab-list">
        <div class="f-row" style="margin-bottom:12px;">
          <button class="period-btn active" id="sf-all"   onclick="filterIngr('all',this)">すべて</button>
          <button class="period-btn"         id="sf-danger" onclick="filterIngr('danger',this)">🔴 危険</button>
          <button class="period-btn"         id="sf-warn"   onclick="filterIngr('warn',this)">🟡 警告</button>
          <button class="period-btn"         id="sf-ok"     onclick="filterIngr('ok',this)">🟢 正常</button>
          <input class="f-input" id="ingr-search" placeholder="🔍 原料名で検索..." oninput="renderIngrGrid()" style="margin-left:auto;width:200px;">
        </div>
        <div class="ingr-grid" id="ingr-grid">
          <div style="grid-column:1/-1;text-align:center;padding:40px;color:var(--text3);">
            <div style="font-size:40px;margin-bottom:10px;">🧂</div>
            <div style="font-size:14px;font-weight:800;">原料がまだ登録されていません</div>
            <div style="font-size:12px;margin-top:6px;">「原料登録」タブから追加してください</div>
          </div>
        </div>
      </div>

      <div id="stock-tab-ingr-edit" style="display:none;">
        <div class="grid-2">
          <div class="s-card">
            <div class="s-card-title" id="ingr-form-title">➕ 原料を新規登録</div>
            <input type="hidden" id="ingr-edit-id">
            <div class="f-row">
              <div class="f-group" style="flex:1;">
                <label>原料名 <span style="color:var(--coral)">*</span></label>
                <input class="f-input" id="ingr-name" placeholder="例: コーヒー豆" style="width:100%;">
              </div>
              <div class="f-group">
                <label>カテゴリ</label>
                <input class="f-input" id="ingr-cat" placeholder="例: 豆類" list="ingr-cat-list" style="width:130px;">
                <datalist id="ingr-cat-list"></datalist>
              </div>
            </div>
            <div class="f-row">
              <div class="f-group">
                <label>単位 <span style="color:var(--coral)">*</span></label>
                <select class="f-input" id="ingr-unit" style="width:110px;">
                  <option value="g">g(グラム)</option>
                  <option value="kg">kg(キログラム)</option>
                  <option value="ml">ml(ミリリットル)</option>
                  <option value="L">L(リットル)</option>
                  <option value="個">個</option>
                  <option value="枚">枚</option>
                  <option value="本">本</option>
                  <option value="袋">袋</option>
                  <option value="パック">パック</option>
                  <option value="缶">缶</option>
                </select>
              </div>
              <div class="f-group">
                <label>現在の在庫量</label>
                <input type="number" class="f-input" id="ingr-qty" placeholder="0" min="0" step="0.1">
              </div>
              <div class="f-group">
                <label>仕入れ単価(円/単位)</label>
                <input type="number" class="f-input" id="ingr-cost" placeholder="例: 50" min="0" step="0.01">
              </div>
            </div>
            <div class="f-row">
              <div class="f-group">
                <label>警告ライン(残りX以下)</label>
                <input type="number" class="f-input" id="ingr-warn" placeholder="100" min="0" step="0.1">
              </div>
              <div class="f-group">
                <label>危険ライン(残りX以下)</label>
                <input type="number" class="f-input" id="ingr-danger" placeholder="30" min="0" step="0.1">
              </div>
            </div>
            <div class="f-group" style="margin-bottom:14px;">
              <label>メモ(保存先・規格など)</label>
              <input class="f-input" id="ingr-note" placeholder="例: 冷蔵保存、エチオピア産" style="width:100%;">
            </div>
            <div style="display:flex;gap:10px;">
              <button class="f-btn f-btn-blue" style="flex:1;" onclick="saveIngredient()">✅ 保存する</button>
              <button class="f-btn f-btn-ghost" onclick="resetIngrForm()">リセット</button>
            </div>
            <div id="ingr-feedback" style="text-align:center;margin-top:8px;font-size:12px;font-weight:700;color:var(--mint);min-height:18px;"></div>
          </div>
          <div class="s-card" style="overflow:hidden;">
            <div class="s-card-title">📋 登録済み原料</div>
            <div class="tbl-scroll" style="max-height:440px;">
              <table class="tbl">
                <thead><tr><th>原料名</th><th>カテゴリ</th><th>単位</th><th class="num">在庫</th><th class="num">単価</th><th></th></tr></thead>
                <tbody id="ingr-list-tbody"></tbody>
              </table>
            </div>
          </div>
        </div>
      </div>

      <div id="stock-tab-adjust" style="display:none;">
        <div class="grid-2">
          <div class="s-card">
            <div class="s-card-title">📥 在庫を入出庫</div>
            <div class="f-group" style="margin-bottom:10px;">
              <label>原料を選択 <span style="color:var(--coral)">*</span></label>
              <select class="f-input" id="adj-ingr-sel" style="width:100%;" onchange="onAdjIngrChange()"></select>
            </div>
            <div style="background:var(--bg2);border-radius:10px;padding:10px 14px;margin-bottom:12px;font-size:13px;">
              現在の在庫:<b id="adj-current">—</b>
            </div>
            <div class="f-row">
              <div class="f-group">
                <label>操作</label>
                <select class="f-input" id="adj-op" style="width:150px;">
                  <option value="add">➕ 追加(仕入れ)</option>
                  <option value="sub">➖ 減らす(廃棄・使用)</option>
                  <option value="set">🔄 在庫数をセット</option>
                </select>
              </div>
              <div class="f-group">
                <label>数量</label>
                <input type="number" class="f-input" id="adj-qty" min="0" step="0.1" placeholder="0" style="width:110px;" oninput="onAdjQtyChange()">
              </div>
              <div class="f-group" id="adj-cost-wrap">
                <label>単価(円)※任意</label>
                <input type="number" class="f-input" id="adj-unit-cost" min="0" step="0.01" placeholder="" style="width:110px;">
              </div>
            </div>
            <div style="background:var(--bg2);border-radius:10px;padding:8px 14px;margin-bottom:12px;font-size:13px;">
              操作後の在庫:<b id="adj-after">—</b>
            </div>
            <div class="f-group" style="margin-bottom:12px;">
              <label>メモ(任意)</label>
              <input class="f-input" id="adj-memo" placeholder="例: 朝仕入れ、スタッフ賄い" style="width:100%;">
            </div>
            <div class="f-row">
              <button class="f-btn f-btn-blue" style="flex:1;" onclick="adjustIngr()">✅ 更新する</button>
            </div>
          </div>
          <div class="s-card">
            <div class="s-card-title">📋 入出庫ログ <span id="adj-log-ingr-name"></span></div>
            <div class="tbl-scroll" style="max-height:400px;">
              <table class="tbl">
                <thead><tr><th>日時</th><th>操作</th><th class="num">数量</th><th class="num">在庫後</th><th>メモ</th></tr></thead>
                <tbody id="adj-log-tbody"></tbody>
              </table>
            </div>
          </div>
        </div>
        <div class="s-card" style="margin-top:4px;">
          <div class="s-card-title">🤖 売上データから原料消費を自動計算</div>
          <div class="alert alert-info" style="margin-bottom:12px;">
            <div class="alert-icon">💡</div>
            <div class="alert-body">
              <div class="alert-title">レシピを登録すると、売上CSVから原料の消費量を自動計算できます</div>
              <div class="alert-text">計算結果を確認してから「在庫に反映」を押すと、まとめて在庫が更新されます。</div>
            </div>
          </div>
          <div class="f-row" style="margin-bottom:10px;">
            <div class="f-group">
              <label>計算対象の期間(開始日)</label>
              <input type="date" class="f-input" id="auto-calc-from">
            </div>
            <div class="f-group">
              <label>終了日</label>
              <input type="date" class="f-input" id="auto-calc-to">
            </div>
            <button class="f-btn f-btn-blue" style="margin-top:20px;" onclick="calcAutoConsumption()">🔍 消費量を計算</button>
          </div>
          <div id="auto-calc-result"></div>
        </div>
      </div>

      <div id="stock-tab-recipe" style="display:none;">
        <div class="grid-2">
          <div class="s-card">
            <div class="s-card-title">📖 レシピを登録</div>
            <div class="alert alert-info" style="margin-bottom:12px;">
              <div class="alert-icon">💡</div>
              <div class="alert-body">
                <div class="alert-title">商品1個を作るのに使う原料量を登録</div>
                <div class="alert-text">例:カフェラテ1杯 = コーヒー豆15g + 牛乳150ml</div>
              </div>
            </div>
            <div class="f-group" style="margin-bottom:10px;">
              <label>対象商品(MISE POSの商品名に合わせる)</label>
              <input class="f-input" id="recipe-prod-name" placeholder="例: カフェラテ" list="recipe-prod-list" style="width:100%;">
              <datalist id="recipe-prod-list"></datalist>
            </div>
            <div id="recipe-ingr-rows" style="margin-bottom:10px;display:flex;flex-direction:column;gap:6px;">
              </div>
            <button class="f-btn f-btn-ghost" style="width:100%;margin-bottom:10px;" onclick="addRecipeIngrRow()">➕ 原料を追加</button>
            <button class="f-btn f-btn-mint" style="width:100%;" onclick="saveRecipe()">💾 レシピを保存</button>
            <div id="recipe-feedback" style="text-align:center;margin-top:8px;font-size:12px;font-weight:700;color:var(--mint);min-height:18px;"></div>
          </div>
          <div class="s-card">
            <div class="s-card-title">📚 登録済みレシピ一覧</div>
            <div class="recipe-list" id="recipe-list">
              <div style="text-align:center;color:var(--text3);padding:30px;font-size:13px;">レシピがまだありません</div>
            </div>
          </div>
        </div>
      </div>

      <div id="stock-tab-stocktake" style="display:none;">
        <div class="s-card">
          <div class="s-card-title">📊 棚卸し — 実数入力して理論値と比較</div>
          <div class="alert alert-warn" style="margin-bottom:12px;">
            <div class="alert-icon">⚠️</div>
            <div class="alert-body">
              <div class="alert-title">実際に数えた在庫量を入力してください</div>
              <div class="alert-text">「差異」列がマイナスならロス(廃棄・食べ残し・盗難)、プラスなら棚卸し利益です。「反映」を押すと在庫が実数で更新されます。</div>
            </div>
          </div>
          <div class="f-row" style="margin-bottom:12px;">
            <div class="f-group">
              <label>棚卸し日</label>
              <input type="date" class="f-input" id="stocktake-date">
            </div>
            <button class="f-btn f-btn-blue" style="margin-top:20px;" onclick="applyStocktake()">✅ 選択した原料の在庫を実数で更新</button>
            <button class="f-btn f-btn-ghost" style="margin-top:20px;" onclick="exportStocktakeCSV()">📤 棚卸し表をCSV出力</button>
          </div>
          <div class="tbl-scroll">
            <table class="tbl" id="stocktake-table">
              <thead>
                <tr>
                  <th><input type="checkbox" onchange="toggleStocktakeAll(this)" title="全選択"></th>
                  <th>原料名</th><th>カテゴリ</th><th>単位</th>
                  <th class="num">理論在庫</th>
                  <th class="num">実在庫(入力)</th>
                  <th class="num">差異</th>
                  <th class="num">ロス金額</th>
                </tr>
              </thead>
              <tbody id="stocktake-tbody"></tbody>
            </table>
          </div>
        </div>
      </div>

      </div></div><div id="panel-finance" class="panel">
      <div class="panel-title">💰 収支・経理</div>
      <div class="panel-sub">月ごとの収入・支出・利益を管理します</div>

      <div class="f-row" style="margin-bottom:16px;">
        <div class="f-group">
          <label>表示月</label>
          <input type="month" class="f-input" id="finance-month" onchange="renderFinance()">
        </div>
        <div style="display:flex;gap:6px;margin-top:20px;">
          <button class="f-btn f-btn-ghost" onclick="financeMonthOffset(-1)">◀ 前月</button>
          <button class="f-btn f-btn-ghost" onclick="financeMonthOffset(1)">翌月 ▶</button>
          <button class="f-btn f-btn-blue" onclick="financeMonthOffset(0)">今月</button>
        </div>
      </div>

      <div class="grid-2">
        <div class="s-card">
          <div class="s-card-title">📋 損益計算書(月次)</div>
          <div id="pl-body"></div>
        </div>
        <div class="s-card">
          <div class="s-card-title">📊 収支グラフ(月次推移)</div>
          <div class="chart-wrap"><canvas id="ch-finance"></canvas></div>
        </div>
      </div>

      <div class="s-card">
        <div class="s-card-title">➕ 経費・支出を登録</div>
        <div class="f-row">
          <div class="f-group">
            <label>日付</label>
            <input type="date" class="f-input" id="exp-date">
          </div>
          <div class="f-group">
            <label>科目</label>
            <select class="f-input" id="exp-cat" style="width:160px;">
              <option value="仕入れ">仕入れ原価</option>
              <option value="家賃">家賃・地代</option>
              <option value="光熱費">光熱費</option>
              <option value="人件費">人件費</option>
              <option value="消耗品">消耗品費</option>
              <option value="広告">広告・販促費</option>
              <option value="通信費">通信費</option>
              <option value="その他">その他経費</option>
            </select>
          </div>
          <div class="f-group">
            <label>金額(円)</label>
            <input type="number" class="f-input" id="exp-amount" placeholder="50000" min="0">
          </div>
          <div class="f-group" style="flex:1;">
            <label>メモ</label>
            <input class="f-input" id="exp-memo" placeholder="例: 4月分家賃" style="width:100%;">
          </div>
          <button class="f-btn f-btn-blue" style="margin-top:20px;" onclick="addExpense()">➕ 追加</button>
        </div>
        <div class="tbl-scroll" style="max-height:260px;">
          <table class="tbl" id="exp-table">
            <thead><tr><th>日付</th><th>科目</th><th>内容</th><th class="num">金額</th><th></th></tr></thead>
            <tbody id="exp-tbody"></tbody>
          </table>
        </div>
      </div>

      <div class="s-card">
        <div class="s-card-title">📅 日別売上一覧</div>
        <div class="tbl-scroll">
          <table class="tbl" id="daily-tbl">
            <thead><tr><th>日付</th><th>曜日</th><th class="num">売上</th><th class="num">客数</th><th class="num">客単価</th><th class="num">割引額</th></tr></thead>
            <tbody id="daily-tbody"></tbody>
          </table>
        </div>
      </div>
    </div>

    <div id="panel-analysis" class="panel">
      <div class="panel-title">📈 売上分析</div>
      <div class="panel-sub">データを多角的に分析して経営改善に活かします</div>

      <div class="period-bar">
        <span style="font-size:12px;font-weight:700;color:var(--text2);">期間:</span>
        <button class="period-btn active" onclick="setAnalysisPeriod('week',this)">直近7日</button>
        <button class="period-btn" onclick="setAnalysisPeriod('month',this)">直近30日</button>
        <button class="period-btn" onclick="setAnalysisPeriod('quarter',this)">直近90日</button>
        <button class="period-btn" onclick="setAnalysisPeriod('all',this)">全期間</button>
        <input type="date" class="f-input" id="an-from" style="margin-left:auto;" onchange="setAnalysisPeriod('custom',null)">
        <span style="font-size:12px;color:var(--text2);">〜</span>
        <input type="date" class="f-input" id="an-to" onchange="setAnalysisPeriod('custom',null)">
      </div>

      <div class="grid-2">
        <div class="s-card">
          <div class="s-card-title">🏆 商品別売上ランキング <span id="an-rank-metric-lbl">売上金額</span></div>
          <div style="display:flex;gap:6px;margin-bottom:12px;">
            <button class="period-btn active" onclick="setRankMetric('amount',this)">金額</button>
            <button class="period-btn" onclick="setRankMetric('qty',this)">個数</button>
            <button class="period-btn" onclick="setRankMetric('tx',this)">回数</button>
          </div>
          <div id="product-ranking"></div>
        </div>
        <div class="s-card">
          <div class="s-card-title">🍰 カテゴリ別売上</div>
          <div class="chart-wrap"><canvas id="ch-an-cat"></canvas></div>
        </div>
      </div>

      <div class="grid-2">
        <div class="s-card">
          <div class="s-card-title">🕐 時間帯別売上(棒グラフ)</div>
          <div class="chart-wrap"><canvas id="ch-hourly"></canvas></div>
        </div>
        <div class="s-card">
          <div class="s-card-title">📅 曜日別売上(平均)</div>
          <div class="chart-wrap"><canvas id="ch-dow"></canvas></div>
        </div>
      </div>

      <div class="grid-2">
        <div class="s-card">
          <div class="s-card-title">👥 客種別売上構成</div>
          <div class="chart-wrap"><canvas id="ch-cust"></canvas></div>
        </div>
        <div class="s-card">
          <div class="s-card-title">🎂 年齢層別売上</div>
          <div class="chart-wrap"><canvas id="ch-age"></canvas></div>
        </div>
      </div>

      <div class="s-card">
        <div class="s-card-title">📈 月別売上推移</div>
        <div class="chart-wrap tall"><canvas id="ch-monthly"></canvas></div>
      </div>
    </div>

    <div id="panel-purchase" class="panel">
      <div class="panel-title">🗒️ 仕入れ帳</div>
      <div class="panel-sub">仕入れ・入荷の記録と仕入れ先を管理します</div>

      <div class="grid-2" style="margin-bottom:16px;">
        <div class="s-card">
          <div class="s-card-title">➕ 仕入れを記録</div>
          <div class="f-row">
            <div class="f-group">
              <label>日付</label>
              <input type="date" class="f-input" id="pur-date">
            </div>
            <div class="f-group" style="flex:1;">
              <label>仕入れ先</label>
              <input class="f-input" id="pur-supplier" placeholder="例: 山田商店" list="supplier-list" style="width:100%;">
              <datalist id="supplier-list"></datalist>
            </div>
          </div>
          <div class="f-row">
            <div class="f-group" style="flex:1;">
              <label>仕入れ品目
                <span style="font-size:10px;color:var(--text2);">(商品在庫 or 原料在庫に反映されます)</span>
              </label>
              <select class="f-input" id="pur-prod" style="width:100%;" onchange="onPurProdChange()">
                <option value="">(品目を選択)</option>
              </select>
            </div>
          </div>
          <div class="f-row">
            <div class="f-group">
              <label>数量</label>
              <input type="number" class="f-input" id="pur-qty" min="1" placeholder="10">
            </div>
            <div class="f-group">
              <label>仕入れ単価(円)</label>
              <input type="number" class="f-input" id="pur-unit-price" min="0" placeholder="200">
            </div>
            <div class="f-group">
              <label>合計金額(円)</label>
              <input type="number" class="f-input" id="pur-total" min="0" placeholder="2000" readonly style="background:var(--bg2);">
            </div>
          </div>
          <div class="f-group" style="margin-bottom:10px;">
            <label>メモ</label>
            <input class="f-input" id="pur-memo" placeholder="例: 定期仕入れ" style="width:100%;">
          </div>
          <button class="f-btn f-btn-mint" style="width:100%;" onclick="addPurchase()">✅ 仕入れを登録</button>
        </div>

        <div class="s-card">
          <div class="s-card-title">📊 仕入れサマリー <span>今月</span></div>
          <div id="pur-summary"></div>
          <div class="chart-wrap" style="height:200px;margin-top:12px;"><canvas id="ch-pur-cat"></canvas></div>
        </div>
      </div>

      <div class="s-card">
        <div class="s-card-title">📋 仕入れ一覧 
          <div style="display:flex;gap:8px;">
            <input type="month" class="f-input" id="pur-filter-month" onchange="renderPurchaseList()" style="font-size:12px;">
            <button class="f-btn f-btn-ghost" style="padding:5px 12px;font-size:12px;" onclick="exportPurchaseCSV()">📤 CSV出力</button>
          </div>
        </div>
        <div class="tbl-scroll">
          <table class="tbl">
            <thead><tr><th>日付</th><th>仕入れ先</th><th>商品</th><th class="num">数量</th><th class="num">単価</th><th class="num">合計</th><th>メモ</th><th></th></tr></thead>
            <tbody id="pur-tbody"></tbody>
          </table>
        </div>
      </div>
    </div>

    <div id="panel-settings" class="panel">
      <div class="panel-title">⚙️ 設定</div>
      <div class="panel-sub">データの読み込みと基本設定を行います</div>

      <div class="grid-2">
        <div class="s-card">
          <div class="s-card-title">🏪 店舗情報</div>
          <div class="f-group" style="margin-bottom:10px;">
            <label>店舗名</label>
            <input class="f-input" id="set-store" placeholder="例: カフェ花まる" style="width:100%;">
          </div>
          <div class="f-group" style="margin-bottom:10px;">
            <label>業種</label>
            <input class="f-input" id="set-type" placeholder="例: カフェ・喫茶店" style="width:100%;">
          </div>
          <div class="f-group" style="margin-bottom:14px;">
            <label>月次目標売上(円)</label>
            <input type="number" class="f-input" id="set-target" placeholder="500000" style="width:100%;">
          </div>
          <button class="f-btn f-btn-blue" onclick="saveSettings()">💾 設定を保存</button>
        </div>

        <div class="s-card">
          <div class="s-card-title">📂 データ読み込み</div>
          <div style="margin-bottom:14px;">
            <div style="font-size:12px;font-weight:800;color:var(--text2);margin-bottom:8px;">📤 MISE POS 売上CSV(複数可)</div>
            <label class="upload-zone" style="padding:20px;" id="sales-drop">
              <input type="file" multiple accept=".csv" onchange="handleSalesFiles(this.files)">
              <div style="font-size:28px;margin-bottom:6px;">📄</div>
              <div style="font-size:13px;font-weight:800;">CSVをドロップ または クリックして選択</div>
              <div style="font-size:11px;color:var(--text2);margin-top:4px;">MISE POS で出力した売上CSVを読み込みます</div>
            </label>
            <div id="sales-load-info" style="font-size:12px;color:var(--mint);font-weight:700;margin-top:6px;"></div>
          </div>
          <div style="margin-bottom:14px;">
            <div style="font-size:12px;font-weight:800;color:var(--text2);margin-bottom:8px;">⚙️ MISE POS setting.txt</div>
            <label class="upload-zone" style="padding:16px;" id="setting-drop">
              <input type="file" accept=".txt,.json" onchange="handleSettingFile(this.files[0])">
              <div style="font-size:13px;font-weight:800;">setting.txt をドロップ</div>
              <div style="font-size:11px;color:var(--text2);">商品マスタを取り込みます</div>
            </label>
            <div id="setting-load-info" style="font-size:12px;color:var(--mint);font-weight:700;margin-top:6px;"></div>
          </div>
        </div>
      </div>

      <div class="s-card">
        <div class="s-card-title">📊 読み込みデータ状態</div>
        <div class="grid-3" id="data-status">
          <div style="text-align:center;padding:16px;background:var(--bg);border-radius:12px;">
            <div style="font-size:28px;margin-bottom:4px;">📄</div>
            <div style="font-size:24px;font-weight:900;font-family:'Nunito',sans-serif;" id="ds-sales-cnt">0</div>
            <div style="font-size:11px;color:var(--text2);font-weight:700;">売上レコード数</div>
          </div>
          <div style="text-align:center;padding:16px;background:var(--bg);border-radius:12px;">
            <div style="font-size:28px;margin-bottom:4px;">📦</div>
            <div style="font-size:24px;font-weight:900;font-family:'Nunito',sans-serif;" id="ds-prod-cnt">0</div>
            <div style="font-size:11px;color:var(--text2);font-weight:700;">商品マスタ数</div>
          </div>
          <div style="text-align:center;padding:16px;background:var(--bg);border-radius:12px;">
            <div style="font-size:28px;margin-bottom:4px;">📅</div>
            <div style="font-size:24px;font-weight:900;font-family:'Nunito',sans-serif;" id="ds-date-range">—</div>
            <div style="font-size:11px;color:var(--text2);font-weight:700;">データ期間</div>
          </div>
        </div>
        <div style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap;">
          <button class="f-btn f-btn-ghost" onclick="clearAllData()">🗑 全データをリセット</button>
          <button class="f-btn f-btn-blue" onclick="exportAllCSV()">📤 データをCSV保存</button>
        </div>
      </div>

      <div class="alert alert-info" style="margin-top:4px;">
        <div class="alert-icon">💡</div>
        <div class="alert-body">
          <div class="alert-title">MISE POS との連携方法</div>
          <div class="alert-text">① MISE POSの「📤 CSV出力」ボタンで売上CSVをダウンロード<br>② このページの「CSVを読み込む」から取り込む<br>③ setting.txtを読み込むと商品マスタが自動同期されます<br>④ データはブラウザのlocalStorageに保存されます(ページを閉じても保持)</div>
        </div>
      </div>
    </div>

  </main>
</div>

<div class="modal hidden" id="import-modal">
  <div class="modal-box">
    <div class="modal-title">📂 CSVを読み込む</div>
    <label class="upload-zone" id="modal-drop">
      <input type="file" multiple accept=".csv" id="modal-csv-input" onchange="handleModalFiles(this.files)">
      <div style="font-size:36px;margin-bottom:8px;">📄</div>
      <div style="font-size:15px;font-weight:800;margin-bottom:4px;">CSVをドロップ または クリックして選択</div>
      <div style="font-size:12px;color:var(--text2);">MISE POS の売上CSVを読み込みます(複数ファイル可)</div>
    </label>
    <div id="import-info" style="margin-top:12px;font-size:13px;font-weight:700;color:var(--mint);min-height:20px;"></div>
    <div class="modal-btns">
      <button class="mbtn-cancel" onclick="closeImportModal()">閉じる</button>
    </div>
  </div>
</div>

<div class="toast" id="toast"></div>

<script>
'use strict';
// ══════════════════════════════════════════════════════════════
// ── データ ──
// ══════════════════════════════════════════════════════════════
let salesData = JSON.parse(localStorage.getItem('mgr_sales') || '[]');
let products  = JSON.parse(localStorage.getItem('mgr_products') || '[]');
let stockData = JSON.parse(localStorage.getItem('mgr_stock') || '{}');
  // 商品在庫: { product_id: { qty, warn, danger, log[] } }
let ingredients = JSON.parse(localStorage.getItem('mgr_ingredients') || '[]');
  // 原料マスタ: [{id,name,cat,unit,warn,danger,cost,note}]
let ingrStock   = JSON.parse(localStorage.getItem('mgr_ingr_stock') || '{}');
  // 原料在庫:  {ingr_id: {qty, log[]}}
let recipes     = JSON.parse(localStorage.getItem('mgr_recipes') || '[]');
  // レシピ:   [{id,prodName,ingredients:[{ingrId,amount}]}]
let expenses  = JSON.parse(localStorage.getItem('mgr_expenses') || '[]');
  // [{id, date, cat, amount, memo}]
let purchases = JSON.parse(localStorage.getItem('mgr_purchases') || '[]');
  // [{id, date, supplier, product_id, product_name, qty, unit_price, total, memo}]
let settings  = JSON.parse(localStorage.getItem('mgr_settings') || '{}');
  // {store, type, target_monthly}

// 分析期間
let analysisPeriod = 'month';
let analysisFrom = '', analysisTo = '';
let rankMetric = 'amount';
let stockFilter = 'all';
let financeMonth = ''; // YYYY-MM

// Chart.js インスタンス管理
const charts = {};

// ══════════════════════════════════════════════════════════════
// ── 初期化 ──
// ══════════════════════════════════════════════════════════════
function init(){
  // 時計
  setInterval(()=>{
    const n=new Date();
    document.getElementById('hdr-clock').textContent=
      n.getFullYear()+'年'+(n.getMonth()+1)+'月'+n.getDate()+'日 '+
      String(n.getHours()).padStart(2,'0')+':'+String(n.getMinutes()).padStart(2,'0');
  },1000);
  // 設定適用
  if(settings.store){
    document.getElementById('set-store').value = settings.store;
    document.getElementById('nav-store').textContent = settings.store;
  }
  if(settings.type) document.getElementById('set-type').value = settings.type;
  if(settings.target_monthly) document.getElementById('set-target').value = settings.target_monthly;
  // 今月を初期値
  const now = new Date();
  financeMonth = now.getFullYear()+'-'+String(now.getMonth()+1).padStart(2,'0');
  document.getElementById('finance-month').value = financeMonth;
  // 仕入れ帳の月初期値
  document.getElementById('pur-filter-month').value = financeMonth;
  // 日付デフォルト
  const today = todayStr();
  document.getElementById('exp-date').value = today;
  document.getElementById('pur-date').value = today;
  // 分析期間初期値
  const d30 = new Date(); d30.setDate(d30.getDate()-29);
  analysisFrom = d30.toISOString().slice(0,10);
  analysisTo = today;
  document.getElementById('an-from').value = analysisFrom;
  document.getElementById('an-to').value = analysisTo;
  // ドロップ設定
  setupDropZone('sales-drop','modal-drop');
  setupDropZone2('setting-drop');
  // 日付フィールドの初期値(原料在庫)
  document.getElementById('auto-calc-from').value = analysisFrom;
  document.getElementById('auto-calc-to').value   = today;
  document.getElementById('stocktake-date').value  = today;
  // 商品セレクト
  rebuildProductSelects();
  // 原料在庫:セレクト・グリッド初期化
  rebuildIngrSelects();
  updateDataStatus();
  renderDashboard();
  updateNavBadge();
}

// ══════════════════════════════════════════════════════════════
// ── パネル切替 ──
// ══════════════════════════════════════════════════════════════
function showPanel(id, btn){
  document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));
  document.querySelectorAll('.nav-item').forEach(b=>b.classList.remove('active'));
  document.getElementById('panel-'+id).classList.add('active');
  if(btn) btn.classList.add('active');
  // 各パネル描画
  if(id==='dashboard') renderDashboard();
  if(id==='stock')     renderStockPanel();
  if(id==='finance')   renderFinance();
  if(id==='analysis')  renderAnalysis();
  if(id==='purchase')  renderPurchasePanel();
  if(id==='settings')  updateDataStatus();
}

// ══════════════════════════════════════════════════════════════
// ── CSVパース ──
// ══════════════════════════════════════════════════════════════
function parseCSVLine(line){
  const res=[];let cur='',inQ=false;
  for(let i=0;i<line.length;i++){
    const c=line[i];
    if(c==='"'){if(inQ&&line[i+1]==='"'){cur+='"';i++;}else inQ=!inQ;}
    else if(c===','&&!inQ){res.push(cur);cur='';}
    else cur+=c;
  }
  res.push(cur);return res;
}

function parseSalesCSV(text){
  const lines=text.replace(/^\uFEFF/,'').split(/\r?\n/).filter(l=>l.trim());
  if(lines.length<2) return [];
  const hdrs=parseCSVLine(lines[0]);
  const required=['datetime','date','product','amount'];
  if(required.some(h=>!hdrs.includes(h))) return null; // 不正フォーマット
  const rows=[];
  for(let i=1;i<lines.length;i++){
    const cells=parseCSVLine(lines[i]);
    if(cells.length<2) continue;
    const row={};
    hdrs.forEach((h,idx)=>{row[h]=cells[idx]!==undefined?cells[idx]:''});
    ['hour','dow','month','price','quantity','amount','tax','discount_pct'].forEach(k=>{
      if(row[k]!==undefined&&row[k]!==''){const n=parseFloat(row[k]);if(!isNaN(n))row[k]=n;}
    });
    if(!row.date) continue;
    rows.push(row);
  }
  return rows;
}

function mergeSales(newRows){
  if(!newRows||!newRows.length) return 0;
  // txid+product_idをキーに重複排除
  const existing=new Set(salesData.map(r=>`${r.txid||''}|${r.datetime||''}|${r.product_id||r.product||''}`));
  let added=0;
  newRows.forEach(r=>{
    const key=`${r.txid||''}|${r.datetime||''}|${r.product_id||r.product||''}`;
    if(!existing.has(key)){salesData.push(r);existing.add(key);added++;}
  });
  salesData.sort((a,b)=>(a.datetime||a.date||'').localeCompare(b.datetime||b.date||''));
  localStorage.setItem('mgr_sales',JSON.stringify(salesData));
  return added;
}

// ══════════════════════════════════════════════════════════════
// ── ファイル読み込みハンドラ ──
// ══════════════════════════════════════════════════════════════
function handleSalesFiles(files){
  let totalAdded=0;let totalFailed=0;
  const fileArr=Array.from(files);
  let done=0;
  fileArr.forEach(f=>{
    const reader=new FileReader();
    reader.onload=e=>{
      const rows=parseSalesCSV(e.target.result);
      if(rows===null){totalFailed++;}
      else{totalAdded+=mergeSales(rows);}
      done++;
      if(done===fileArr.length){
        const info=document.getElementById('sales-load-info');
        if(info) info.textContent=`✅ ${totalAdded}件を追加しました(${totalFailed}ファイルはスキップ)`;
        updateDataStatus();
        renderDashboard();
        showToast(`📥 ${totalAdded}件の売上データを読み込みました`);
      }
    };
    reader.readAsText(f,'UTF-8');
  });
}

function handleModalFiles(files){
  const info=document.getElementById('import-info');
  let totalAdded=0;let done=0;
  const fileArr=Array.from(files);
  fileArr.forEach(f=>{
    const reader=new FileReader();
    reader.onload=e=>{
      const rows=parseSalesCSV(e.target.result);
      if(rows!==null) totalAdded+=mergeSales(rows);
      done++;
      if(done===fileArr.length){
        if(info) info.textContent=`✅ ${totalAdded}件を読み込みました`;
        updateDataStatus();
        renderDashboard();
        showToast(`📥 ${totalAdded}件を読み込みました`);
      }
    };
    reader.readAsText(f,'UTF-8');
  });
}

function handleSettingFile(file){
  if(!file) return;
  const reader=new FileReader();
  reader.onload=e=>{
    try{
      const s=JSON.parse(e.target.result);
      if(s.products&&Array.isArray(s.products)){
        products=s.products;
        localStorage.setItem('mgr_products',JSON.stringify(products));
        rebuildProductSelects();
        updateDataStatus();
        const info=document.getElementById('setting-load-info');
        if(info) info.textContent=`✅ 商品マスタ ${products.length}件を取り込みました`;
        showToast('📦 商品マスタを読み込みました');
      } else {
        showToast('⚠️ 商品情報が見つかりませんでした');
      }
    }catch(e){showToast('⚠️ ファイルの読み込みに失敗しました');}
  };
  reader.readAsText(file,'UTF-8');
}

// ドロップゾーン設定
function setupDropZone(id1, id2){
  [id1,id2].forEach(id=>{
    const el=document.getElementById(id);
    if(!el) return;
    el.addEventListener('dragover',e=>{e.preventDefault();el.classList.add('drag');});
    el.addEventListener('dragleave',()=>el.classList.remove('drag'));
    el.addEventListener('drop',e=>{
      e.preventDefault();el.classList.remove('drag');
      handleModalFiles(e.dataTransfer.files);
      const inp=document.getElementById('modal-csv-input');
      if(inp) inp.value='';
    });
  });
}
function setupDropZone2(id){
  const el=document.getElementById(id);
  if(!el) return;
  el.addEventListener('dragover',e=>{e.preventDefault();el.classList.add('drag');});
  el.addEventListener('dragleave',()=>el.classList.remove('drag'));
  el.addEventListener('drop',e=>{
    e.preventDefault();el.classList.remove('drag');
    if(e.dataTransfer.files[0]) handleSettingFile(e.dataTransfer.files[0]);
  });
}

function openImportModal(){ document.getElementById('import-modal').classList.remove('hidden'); }
function closeImportModal(){ document.getElementById('import-modal').classList.add('hidden'); }
document.getElementById('import-modal').addEventListener('click',e=>{
  if(e.target===document.getElementById('import-modal')) closeImportModal();
});

// ══════════════════════════════════════════════════════════════
// ── ユーティリティ ──
// ══════════════════════════════════════════════════════════════
function todayStr(){
  const n=new Date();
  return n.getFullYear()+'-'+String(n.getMonth()+1).padStart(2,'0')+'-'+String(n.getDate()).padStart(2,'0');
}
function monthStr(d){ return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0'); }
function yen(v){ return '¥'+Math.round(v).toLocaleString(); }
function pct(v,base){ return base>0?((v/base)*100).toFixed(1)+'%':'—'; }
function DOW_LABEL(){ return ['日','月','火','水','木','金','土']; }
function escH(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }

function filteredSales(from, to){
  const f=from||'0000-00-00', t=to||'9999-99-99';
  return salesData.filter(r=>r.date>=f && r.date<=t);
}
function salesForMonth(ym){
  return salesData.filter(r=>r.date&&r.date.startsWith(ym));
}
function txAmount(rows){
  return rows.reduce((s,r)=>s+(r.amount||0),0);
}
function txCount(rows){
  return new Set(rows.map(r=>r.txid||(r.datetime||r.date||'')+'|'+(r.product||''))).size;
}
function uniqueTxIds(rows){
  const s=new Set();
  rows.forEach(r=>{ const k=r.txid||(r.datetime+r.product);s.add(k); });
  return s.size;
}

// Chart.js インスタンスの安全な作成・破棄
function makeChart(id, config){
  if(charts[id]) charts[id].destroy();
  const canvas=document.getElementById(id);
  if(!canvas) return;
  charts[id]=new Chart(canvas.getContext('2d'), config);
  return charts[id];
}

const COLORS=['#3b6ef5','#22c55e','#f59e0b','#ef4444','#8b5cf6','#0ea5e9','#f97316','#ec4899','#14b8a6','#a3e635'];

// ══════════════════════════════════════════════════════════════
// ── ダッシュボード ──
// ══════════════════════════════════════════════════════════════
function renderDashboard(){
  const today = todayStr();
  const now = new Date();
  const thisMonth = monthStr(now);
  const lastMonth = monthStr(new Date(now.getFullYear(),now.getMonth()-1,1));

  // 日付ラベル
  const DOW=['日','月','火','水','木','金','土'];
  document.getElementById('dash-date-label').textContent=
    `${now.getFullYear()}年${now.getMonth()+1}月${now.getDate()}日(${DOW[now.getDay()]})の経営状況`;

  // KPI計算
  const todayRows = salesData.filter(r=>r.date===today);
  const yesterdayRows = salesData.filter(r=>{
    const y=new Date(today); y.setDate(y.getDate()-1);
    return r.date===y.toISOString().slice(0,10);
  });
  const thisMonthRows = salesForMonth(thisMonth);
  const lastMonthRows = salesForMonth(lastMonth);

  const todaySales=txAmount(todayRows);
  const ydSales=txAmount(yesterdayRows);
  const mSales=txAmount(thisMonthRows);
  const lmSales=txAmount(lastMonthRows);
  const mTx=uniqueTxIds(thisMonthRows);
  const lmTx=uniqueTxIds(lastMonthRows);
  const mAtv=mTx>0?mSales/mTx:0;
  const lmAtv=lmTx>0?lmSales/lmTx:0;

  document.getElementById('kpi-today-sales').textContent=yen(todaySales);
  document.getElementById('kpi-month-sales').textContent=yen(mSales);
  document.getElementById('kpi-month-tx').innerHTML=`${mTx}<small>件</small>`;
  document.getElementById('kpi-atv').textContent=yen(mAtv);

  // 差分表示
  const diffEl=(id,curr,prev,isYen=true)=>{
    const el=document.getElementById(id);
    if(!el) return;
    if(prev===0){el.textContent='前期間データなし';el.className='kpi-diff flat';return;}
    const d=curr-prev;const dp=(d/prev*100).toFixed(1);
    const sign=d>=0?'▲':'▼';
    el.className='kpi-diff '+(d>=0?'up':'down');
    el.textContent=`${sign}${isYen?yen(Math.abs(d)):Math.abs(d).toLocaleString()} (${Math.abs(dp)}%)`;
  };
  diffEl('kpi-today-diff',todaySales,ydSales);
  diffEl('kpi-month-diff',mSales,lmSales);
  diffEl('kpi-month-tx-diff',mTx,lmTx,false);
  diffEl('kpi-atv-diff',mAtv,lmAtv);

  // アラート
  renderDashAlerts(mSales);

  // 直近30日グラフ
  renderDailyChart(today);

  // カテゴリ円グラフ
  renderCatPieChart(thisMonthRows);

  // 在庫アラート
  renderDashStockAlerts();

  // トップ商品
  renderDashTopProducts(thisMonthRows);
}

function renderDashAlerts(mSales){
  const container=document.getElementById('dash-alerts');
  const alerts=[];
  // 目標達成率
  if(settings.target_monthly){
    const target=parseInt(settings.target_monthly)||0;
    const rate=mSales/target*100;
    if(rate<50){
      const now=new Date();const dom=now.getDate();const totalDays=new Date(now.getFullYear(),now.getMonth()+1,0).getDate();
      const expectedRate=dom/totalDays*100;
      if(rate<expectedRate*0.7){
        alerts.push({type:'bad',icon:'⚠️',title:'月次目標が大幅に未達です',text:`目標 ${yen(target)} に対して現在 ${rate.toFixed(0)}%。日割りペースを大きく下回っています。`});
      }
    }
  }
  // 在庫アラート数
  const alertCount=getStockAlertCount();
  if(alertCount.danger>0) alerts.push({type:'bad',icon:'🔴',title:`在庫危険警告: ${alertCount.danger}商品`,text:'危険ライン以下の商品があります。至急補充をご確認ください。'});
  else if(alertCount.warn>0) alerts.push({type:'warn',icon:'🟡',title:`在庫警告: ${alertCount.warn}商品`,text:'警告ライン以下の商品があります。早めに補充を検討してください。'});
  container.innerHTML=alerts.map(a=>`
    <div class="alert alert-${a.type}">
      <div class="alert-icon">${a.icon}</div>
      <div class="alert-body"><div class="alert-title">${a.title}</div><div class="alert-text">${a.text}</div></div>
    </div>`).join('');
}

function renderDailyChart(today){
  const days=[];const sales30=[];
  for(let i=29;i>=0;i--){
    const d=new Date(today);d.setDate(d.getDate()-i);
    const ds=d.toISOString().slice(0,10);
    days.push(ds.slice(5));
    sales30.push(txAmount(salesData.filter(r=>r.date===ds)));
  }
  makeChart('ch-daily',{
    type:'bar',
    data:{labels:days,datasets:[{label:'売上',data:sales30,backgroundColor:COLORS[0]+'88',borderColor:COLORS[0],borderWidth:2,borderRadius:4}]},
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{y:{beginAtZero:true,ticks:{callback:v=>v>=1000?yen(v/1000)+'k':yen(v),font:{size:10}},grid:{color:'rgba(0,0,0,.04)'}},x:{ticks:{maxTicksLimit:10,font:{size:10}},grid:{display:false}}}}
  });
}

function renderCatPieChart(rows){
  const catMap={};
  rows.forEach(r=>{ const c=r.category||'その他'; catMap[c]=(catMap[c]||0)+(r.amount||0); });
  const cats=Object.keys(catMap).sort((a,b)=>catMap[b]-catMap[a]);
  makeChart('ch-cat-pie',{
    type:'doughnut',
    data:{labels:cats,datasets:[{data:cats.map(c=>catMap[c]),backgroundColor:COLORS.slice(0,cats.length),borderWidth:2,borderColor:'#fff'}]},
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'right',labels:{font:{size:11},boxWidth:12}}}}
  });
}

function renderDashTopProducts(rows){
  const prodMap={};
  rows.forEach(r=>{ const k=r.product||'不明'; prodMap[k]=(prodMap[k]||0)+(r.amount||0); });
  const sorted=Object.entries(prodMap).sort((a,b)=>b[1]-a[1]).slice(0,8);
  const maxAmt=sorted[0]?sorted[0][1]:1;
  const container=document.getElementById('dash-top-products');
  container.innerHTML=sorted.map(([name,amt],i)=>`
    <div class="rank-item">
      <div class="rank-no${i===0?' top1':i===1?' top2':i===2?' top3':''}">${i+1}</div>
      <div class="rank-bar-wrap">
        <div class="rank-name">${escH(name)}</div>
        <div class="rank-bar-track"><div class="rank-bar-fill" style="width:${Math.round(amt/maxAmt*100)}%"></div></div>
      </div>
      <div class="rank-val">${yen(amt)}</div>
    </div>`).join('');
  if(!sorted.length) container.innerHTML='<div style="color:var(--text3);text-align:center;padding:20px;font-size:13px;">データがありません</div>';
}

// ══════════════════════════════════════════════════════════════
// ── 在庫管理 ──
// ══════════════════════════════════════════════════════════════
// ── ユーティリティ共通 ──
// ══════════════════════════════════════════════════════════════
function getProductById(id){ return products.find(p=>p.id===id)||null; }

function buildProductsFromSales(){
  const map={};
  salesData.forEach(r=>{
    const id=r.product_id||r.product||'';
    if(!map[id]) map[id]={id,name:r.product||id,cat:r.category||'',icon:'📦',price:r.price||0,tax:r.tax||10};
  });
  return Object.values(map);
}

function fmtQty(v,unit){
  const n=parseFloat(v)||0;
  return (n%1===0?n:n.toFixed(2))+(unit?(' '+unit):'');
}

// ══════════════════════════════════════════════════════════════
// ── 商品在庫(stockData) ──
// ══════════════════════════════════════════════════════════════
function getStockItem(prodId){
  if(!stockData[prodId]) stockData[prodId]={qty:0,warn:5,danger:2,log:[]};
  return stockData[prodId];
}

function getStockAlertCount(){
  let danger=0,warn=0;
  // 商品在庫
  (products.length?products:buildProductsFromSales()).forEach(p=>{
    const s=stockData[p.id];
    if(!s) return;
    if(s.qty<=s.danger) danger++;
    else if(s.qty<=s.warn) warn++;
  });
  // 原料在庫
  ingredients.forEach(ig=>{
    const s=ingrStock[ig.id];
    if(!s) return;
    if(s.qty<=ig.danger) danger++;
    else if(s.qty<=ig.warn) warn++;
  });
  return {danger,warn};
}

function updateStock(){
  const prodId=document.getElementById('stock-prod-sel').value;
  const op=document.getElementById('stock-op').value;
  const qty=parseFloat(document.getElementById('stock-qty').value||'0');
  const memo=document.getElementById('stock-memo').value.trim();
  if(!prodId){showToast('⚠️ 商品を選択してください');return;}
  if(isNaN(qty)||qty<0){showToast('⚠️ 正しい数量を入力してください');return;}
  const s=getStockItem(prodId);
  const before=s.qty;
  if(op==='set') s.qty=qty;
  else if(op==='add') s.qty=+(s.qty+qty).toFixed(4);
  else if(op==='sub') s.qty=+Math.max(0,s.qty-qty).toFixed(4);
  s.log=s.log||[];
  s.log.unshift({date:todayStr(),op,qty,memo,before,after:s.qty});
  if(s.log.length>50) s.log=s.log.slice(0,50);
  localStorage.setItem('mgr_stock',JSON.stringify(stockData));
  showToast(`✅ 在庫を更新しました(${before}→${s.qty})`);
  document.getElementById('stock-qty').value='';
  document.getElementById('stock-memo').value='';
  renderStockGrid();
  updateNavBadge();
}

function saveAlertSetting(){
  const prodId=document.getElementById('alert-prod-sel').value;
  const w=parseFloat(document.getElementById('alert-warn').value||'5');
  const d=parseFloat(document.getElementById('alert-danger').value||'2');
  if(!prodId){showToast('⚠️ 商品を選択してください');return;}
  const s=getStockItem(prodId);
  s.warn=w;s.danger=d;
  localStorage.setItem('mgr_stock',JSON.stringify(stockData));
  showToast('💾 アラート設定を保存しました');
  renderStockGrid();
  updateNavBadge();
}

function renderStockPanel(){
  rebuildProductSelects();
  // 商品在庫タブ
  renderStockGrid();
  // 原料在庫タブ
  rebuildIngrSelects();
  renderIngrGrid();
  renderIngrListTable();
  renderRecipeList();
  renderStocktakeTable();
  updateNavBadge();
}

// 在庫パネルのメインタブ切替(商品 / 原料)
let stockMainTab='product'; // 'product' | 'ingredient'
function showStockMainTab(tab, btn){
  stockMainTab=tab;
  document.querySelectorAll('#stock-main-tabs .period-btn').forEach(b=>b.classList.remove('active'));
  if(btn) btn.classList.add('active');
  document.getElementById('stock-product-area').style.display = tab==='product'?'block':'none';
  document.getElementById('stock-ingr-area').style.display   = tab==='ingredient'?'block':'none';
  if(tab==='ingredient') showStockTab('list', document.querySelector('#stock-tab-bar .period-btn'));
}

// 原料在庫サブタブ切替
let ingrFilter='all';
function showStockTab(tab, btn){
  ['list','ingr-edit','adjust','recipe','stocktake'].forEach(t=>{
    const el=document.getElementById('stock-tab-'+t);
    if(el) el.style.display = t===tab?'block':'none';
  });
  document.querySelectorAll('#stock-tab-bar .period-btn').forEach(b=>b.classList.remove('active'));
  if(btn) btn.classList.add('active');
  if(tab==='list')      renderIngrGrid();
  if(tab==='ingr-edit'){ renderIngrListTable(); rebuildIngrCatList(); }
  if(tab==='adjust'){   rebuildAdjIngrSel(); renderAdjLog(); }
  if(tab==='recipe'){   renderRecipeList(); rebuildRecipeProdList(); }
  if(tab==='stocktake') renderStocktakeTable();
}

function filterStock(type, btn){
  stockFilter=type;
  document.querySelectorAll('#sf-all,#sf-danger,#sf-warn,#sf-ok').forEach(b=>b.classList.remove('active'));
  if(btn) btn.classList.add('active');
  renderStockGrid();
}

function filterIngr(type, btn){
  ingrFilter=type;
  document.querySelectorAll('#sf-all,#sf-danger,#sf-warn,#sf-ok').forEach(b=>b.classList.remove('active'));
  if(btn) btn.classList.add('active');
  renderIngrGrid();
}

function renderStockGrid(){
  const q=(document.getElementById('stock-search')?.value||'').toLowerCase();
  const grid=document.getElementById('stock-grid');
  if(!grid) return;
  let prods=products.length?products:buildProductsFromSales();
  if(q) prods=prods.filter(p=>p.name.toLowerCase().includes(q)||((p.cat||'').toLowerCase().includes(q)));
  prods=prods.filter(p=>{
    const s=stockData[p.id];
    if(!s) return stockFilter==='all'||stockFilter==='ok';
    if(stockFilter==='danger') return s.qty<=s.danger;
    if(stockFilter==='warn')   return s.qty>s.danger && s.qty<=s.warn;
    if(stockFilter==='ok')     return s.qty>s.warn;
    return true;
  });
  if(!prods.length){
    grid.innerHTML='<div style="grid-column:1/-1;text-align:center;padding:40px;color:var(--text3);font-size:14px;font-weight:700;">商品がありません</div>';
    return;
  }
  grid.innerHTML=prods.map(p=>{
    const s=getStockItem(p.id);
    const isDanger=s.qty<=s.danger;
    const isWarn=!isDanger&&s.qty<=s.warn;
    const statusClass=isDanger?'warn-out':isWarn?'warn-low':'';
    const alertBadge=isDanger?'🔴':isWarn?'🟡':'';
    const prog=s.warn>0?Math.min(100,Math.round(s.qty/Math.max(s.warn*2,1)*100)):100;
    const progColor=isDanger?'var(--coral)':isWarn?'var(--amber)':'var(--mint)';
    return `<div class="stock-card ${statusClass}">
      ${alertBadge?`<div class="stock-alert-badge">${alertBadge}</div>`:''}
      <span class="stock-icon">${p.icon||'📦'}</span>
      <div class="stock-name">${escH(p.name)}</div>
      <div class="stock-cat">${escH(p.cat||'')}</div>
      <div class="stock-qty">${s.qty}<small>個</small></div>
      <div class="prog-bar"><div class="prog-fill" style="width:${prog}%;background:${progColor}"></div></div>
      <div class="stock-meta">
        <span class="chip chip-gray" style="font-size:10px;">警告:${s.warn}</span>
        <span class="chip chip-gray" style="font-size:10px;">危険:${s.danger}</span>
        ${p.price?`<span class="chip chip-blue" style="font-size:10px;">¥${p.price.toLocaleString()}</span>`:''}
      </div>
      <div style="margin-top:8px;font-size:10px;color:var(--text3);">
        ${s.log&&s.log[0]?`最終更新: ${s.log[0].date} (${escH(s.log[0].memo||s.log[0].op)})`:'更新なし'}
      </div>
    </div>`;
  }).join('');
}

// ══════════════════════════════════════════════════════════════
// ── 原料マスタ(ingredients / ingrStock) ──
// ingredients: [{id,name,cat,unit,warn,danger,cost,note}]
// ingrStock:   {ingr_id: {qty, log:[{date,op,qty,after,memo}]}}
// recipes:     [{id,prodName,ingredients:[{ingrId,amount}]}]
// ══════════════════════════════════════════════════════════════

function getIngrStock(ingrId){
  if(!ingrStock[ingrId]) ingrStock[ingrId]={qty:0,log:[]};
  return ingrStock[ingrId];
}

function saveIngrData(){
  localStorage.setItem('mgr_ingredients',JSON.stringify(ingredients));
  localStorage.setItem('mgr_ingr_stock',JSON.stringify(ingrStock));
}

function saveRecipes(){
  localStorage.setItem('mgr_recipes',JSON.stringify(recipes));
}

// ── 原料登録・編集 ──
let editingIngrId=null;

function saveIngredient(){
  const name=document.getElementById('ingr-name').value.trim();
  const cat=document.getElementById('ingr-cat').value.trim();
  const unit=document.getElementById('ingr-unit').value;
  const qty=parseFloat(document.getElementById('ingr-qty').value||'0');
  const cost=parseFloat(document.getElementById('ingr-cost').value||'0');
  const warn=parseFloat(document.getElementById('ingr-warn').value||'0');
  const danger=parseFloat(document.getElementById('ingr-danger').value||'0');
  const note=document.getElementById('ingr-note').value.trim();
  if(!name){showToast('⚠️ 原料名を入力してください');return;}
  if(!unit){showToast('⚠️ 単位を選択してください');return;}

  if(editingIngrId){
    const ig=ingredients.find(i=>i.id===editingIngrId);
    if(ig){ Object.assign(ig,{name,cat,unit,warn,danger,cost,note}); }
    // 在庫数は直接編集しない(入出庫で管理)
  } else {
    const newId='IG'+Date.now();
    ingredients.push({id:newId,name,cat,unit,warn,danger,cost,note});
    // 初期在庫をセット
    const s=getIngrStock(newId);
    if(qty>0){
      const before=s.qty;
      s.qty=qty;
      s.log.unshift({date:todayStr(),op:'set',qty,after:qty,memo:'初期登録',before});
    }
  }
  saveIngrData();
  rebuildIngrCatList();
  rebuildIngrSelects();
  renderIngrListTable();
  renderIngrGrid();
  updateNavBadge();
  const fb=document.getElementById('ingr-feedback');
  if(fb){ fb.textContent='✅ 保存しました!'; setTimeout(()=>fb.textContent='',2000); }
  resetIngrForm();
}

function resetIngrForm(){
  editingIngrId=null;
  ['ingr-name','ingr-cat','ingr-qty','ingr-cost','ingr-warn','ingr-danger','ingr-note'].forEach(id=>{
    const el=document.getElementById(id); if(el) el.value='';
  });
  document.getElementById('ingr-unit').value='g';
  document.getElementById('ingr-form-title').textContent='➕ 原料を新規登録';
}

function editIngredient(id){
  const ig=ingredients.find(i=>i.id===id);
  if(!ig) return;
  editingIngrId=id;
  document.getElementById('ingr-name').value=ig.name;
  document.getElementById('ingr-cat').value=ig.cat||'';
  document.getElementById('ingr-unit').value=ig.unit||'g';
  document.getElementById('ingr-cost').value=ig.cost||'';
  document.getElementById('ingr-warn').value=ig.warn||'';
  document.getElementById('ingr-danger').value=ig.danger||'';
  document.getElementById('ingr-note').value=ig.note||'';
  // qty フィールドは編集時は空(入出庫タブで操作)
  document.getElementById('ingr-qty').value='';
  document.getElementById('ingr-form-title').textContent='✏️ 原料を編集';
  showStockTab('ingr-edit', document.querySelectorAll('#stock-tab-bar .period-btn')[1]);
}

function deleteIngredient(id){
  if(!confirm('この原料を削除します。在庫ログも削除されます。よろしいですか?')) return;
  ingredients=ingredients.filter(i=>i.id!==id);
  delete ingrStock[id];
  recipes=recipes.map(r=>({...r,ingredients:r.ingredients.filter(ri=>ri.ingrId!==id)}));
  saveIngrData();
  saveRecipes();
  rebuildIngrSelects();
  renderIngrListTable();
  renderIngrGrid();
  updateNavBadge();
  showToast('🗑 原料を削除しました');
}

function renderIngrListTable(){
  const tbody=document.getElementById('ingr-list-tbody');
  if(!tbody) return;
  if(!ingredients.length){
    tbody.innerHTML='<tr><td colspan="6" style="text-align:center;color:var(--text3);padding:20px;">原料が未登録です</td></tr>';
    return;
  }
  tbody.innerHTML=ingredients.map(ig=>{
    const s=getIngrStock(ig.id);
    return `<tr>
      <td style="font-weight:800;">${escH(ig.name)}</td>
      <td>${escH(ig.cat||'—')}</td>
      <td><span class="ingr-unit">${escH(ig.unit)}</span></td>
      <td class="num">${fmtQty(s.qty,'')}</td>
      <td class="num">${ig.cost?'¥'+ig.cost:'-'}</td>
      <td style="white-space:nowrap;">
        <button class="ingr-act-btn edit" onclick="editIngredient('${ig.id}')">編集</button>
        <button class="ingr-act-btn del"  onclick="deleteIngredient('${ig.id}')" style="margin-left:4px;">削除</button>
      </td>
    </tr>`;
  }).join('');
}

// ── 原料在庫グリッド ──
function renderIngrGrid(){
  const q=(document.getElementById('ingr-search')?.value||'').toLowerCase();
  const grid=document.getElementById('ingr-grid');
  if(!grid) return;
  let list=ingredients;
  if(q) list=list.filter(ig=>ig.name.toLowerCase().includes(q)||(ig.cat||'').toLowerCase().includes(q));
  list=list.filter(ig=>{
    const s=ingrStock[ig.id];
    const qty=s?s.qty:0;
    if(ingrFilter==='danger') return qty<=ig.danger;
    if(ingrFilter==='warn')   return qty>ig.danger && qty<=ig.warn;
    if(ingrFilter==='ok')     return qty>ig.warn;
    return true;
  });
  if(!list.length){
    grid.innerHTML=ingredients.length
      ? '<div style="grid-column:1/-1;text-align:center;padding:30px;color:var(--text3);font-size:13px;">該当する原料がありません</div>'
      : `<div style="grid-column:1/-1;text-align:center;padding:40px;color:var(--text3);">
           <div style="font-size:40px;margin-bottom:10px;">🧂</div>
           <div style="font-size:14px;font-weight:800;">原料がまだ登録されていません</div>
           <div style="font-size:12px;margin-top:6px;">「原料登録」タブから追加してください</div>
         </div>`;
    return;
  }
  grid.innerHTML=list.map(ig=>{
    const s=getIngrStock(ig.id);
    const qty=s.qty;
    const isDanger=qty<=ig.danger;
    const isWarn=!isDanger&&qty<=ig.warn;
    const statusClass=isDanger?'warn-out':isWarn?'warn-low':'';
    const alertBadge=isDanger?'🔴':isWarn?'🟡':'';
    const prog=ig.warn>0?Math.min(100,Math.round(qty/Math.max(ig.warn*2,0.001)*100)):100;
    const progColor=isDanger?'var(--coral)':isWarn?'var(--amber)':'var(--mint)';
    return `<div class="ingr-card ${statusClass}">
      ${alertBadge?`<div class="ingr-alert">${alertBadge}</div>`:''}
      <span class="ingr-unit">${escH(ig.unit)}</span>
      <div class="ingr-name">${escH(ig.name)}</div>
      <div class="ingr-cat">${escH(ig.cat||'')}</div>
      <div class="ingr-qty-wrap">
        <div class="ingr-qty">${fmtQty(qty,'')}</div>
        <div class="ingr-qty-unit">${escH(ig.unit)}</div>
      </div>
      <div class="prog-bar" style="margin-top:6px;"><div class="prog-fill" style="width:${prog}%;background:${progColor}"></div></div>
      <div class="ingr-theory" style="margin-top:3px;">
        警告:${ig.warn}${ig.unit} / 危険:${ig.danger}${ig.unit}
        ${ig.cost?` / ¥${ig.cost}/単位`:''}
      </div>
      <div class="ingr-actions" style="margin-top:8px;">
        <button class="ingr-act-btn edit" onclick="editIngredient('${ig.id}')">✏️ 編集</button>
        <button class="ingr-act-btn edit" onclick="quickAdjIngr('${ig.id}')" style="background:var(--mint-l);color:#16a34a;">📥 入出庫</button>
      </div>
      <div style="font-size:10px;color:var(--text3);margin-top:6px;">
        ${s.log&&s.log[0]?'最終更新: '+s.log[0].date+' ('+escH(s.log[0].memo||s.log[0].op)+')':'更新なし'}
      </div>
    </div>`;
  }).join('');
}

// 在庫一覧から直接入出庫タブへジャンプ
function quickAdjIngr(id){
  showStockTab('adjust', document.querySelectorAll('#stock-tab-bar .period-btn')[2]);
  const sel=document.getElementById('adj-ingr-sel');
  if(sel){ sel.value=id; onAdjIngrChange(); }
}

// ── 入出庫 ──
function rebuildAdjIngrSel(){
  const sel=document.getElementById('adj-ingr-sel');
  if(!sel) return;
  sel.innerHTML=ingredients.map(ig=>`<option value="${ig.id}">${escH(ig.name)} (${escH(ig.unit)})</option>`).join('');
  onAdjIngrChange();
}

function onAdjIngrChange(){
  const id=document.getElementById('adj-ingr-sel')?.value;
  if(!id) return;
  const ig=ingredients.find(i=>i.id===id);
  const s=getIngrStock(id);
  const el=document.getElementById('adj-current');
  if(el) el.textContent=`${fmtQty(s.qty,ig?.unit||'')}`;
  const costEl=document.getElementById('adj-unit-cost');
  if(costEl&&ig?.cost) costEl.value=ig.cost;
  onAdjQtyChange();
  renderAdjLog();
}

function onAdjQtyChange(){
  const id=document.getElementById('adj-ingr-sel')?.value;
  const op=document.getElementById('adj-op')?.value;
  const qty=parseFloat(document.getElementById('adj-qty')?.value||'0');
  const s=id?getIngrStock(id):null;
  const ig=id?ingredients.find(i=>i.id===id):null;
  const after=document.getElementById('adj-after');
  if(!after||!s) return;
  let result=s.qty;
  if(op==='add') result=+(s.qty+qty).toFixed(4);
  else if(op==='sub') result=+Math.max(0,s.qty-qty).toFixed(4);
  else if(op==='set') result=qty;
  after.textContent=`${fmtQty(result, ig?.unit||'')}`;
}

function adjustIngr(){
  const id=document.getElementById('adj-ingr-sel').value;
  const op=document.getElementById('adj-op').value;
  const qty=parseFloat(document.getElementById('adj-qty').value||'0');
  const unitCost=parseFloat(document.getElementById('adj-unit-cost').value||'0');
  const memo=document.getElementById('adj-memo').value.trim();
  if(!id){showToast('⚠️ 原料を選択してください');return;}
  if(isNaN(qty)||qty<0){showToast('⚠️ 正しい数量を入力してください');return;}
  const s=getIngrStock(id);
  const ig=ingredients.find(i=>i.id===id);
  const before=s.qty;
  if(op==='add') s.qty=+(s.qty+qty).toFixed(4);
  else if(op==='sub') s.qty=+Math.max(0,s.qty-qty).toFixed(4);
  else if(op==='set') s.qty=qty;
  const logEntry={
    date:new Date().toISOString().slice(0,16).replace('T',' '),
    op,qty,before,after:s.qty,memo,unitCost
  };
  s.log=s.log||[];
  s.log.unshift(logEntry);
  if(s.log.length>100) s.log=s.log.slice(0,100);
  saveIngrData();
  const opLabel={add:'追加',sub:'減少',set:'セット'}[op]||op;
  showToast(`✅ ${ig?.name||'原料'}を${opLabel}しました(${before}→${s.qty}${ig?.unit||''})`);
  document.getElementById('adj-qty').value='';
  document.getElementById('adj-memo').value='';
  onAdjIngrChange();
  renderIngrGrid();
  updateNavBadge();
}

function renderAdjLog(){
  const id=document.getElementById('adj-ingr-sel')?.value;
  const tbody=document.getElementById('adj-log-tbody');
  const nameEl=document.getElementById('adj-log-ingr-name');
  if(!tbody) return;
  if(!id||!ingredients.find(i=>i.id===id)){
    tbody.innerHTML='<tr><td colspan="5" style="text-align:center;color:var(--text3);padding:16px;">原料を選択してください</td></tr>';
    return;
  }
  const ig=ingredients.find(i=>i.id===id);
  if(nameEl) nameEl.textContent=ig.name;
  const s=getIngrStock(id);
  const log=s.log||[];
  if(!log.length){
    tbody.innerHTML='<tr><td colspan="5" style="text-align:center;color:var(--text3);padding:16px;">入出庫記録がありません</td></tr>';
    return;
  }
  const OP_LABEL={add:'📥追加',sub:'📤減少',set:'🔄セット'};
  tbody.innerHTML=log.slice(0,30).map(l=>`
    <tr>
      <td style="font-size:12px;">${escH(l.date||'')}</td>
      <td>${OP_LABEL[l.op]||escH(l.op)}</td>
      <td class="num">${fmtQty(l.qty, ig.unit)}</td>
      <td class="num">${fmtQty(l.after, ig.unit)}</td>
      <td style="font-size:11px;color:var(--text2);">${escH(l.memo||'')}</td>
    </tr>`).join('');
}

// ── レシピ ──
let recipeIngrRows=[];

function addRecipeIngrRow(ingrId='', amount=''){
  const container=document.getElementById('recipe-ingr-rows');
  if(!container) return;
  const rowId='rrow-'+Date.now()+'-'+Math.random().toString(36).slice(2,6);
  const ingrOpts=ingredients.map(ig=>`<option value="${ig.id}" ${ig.id===ingrId?'selected':''}>${escH(ig.name)} (${ig.unit})</option>`).join('');
  const row=document.createElement('div');
  row.id=rowId;
  row.style.cssText='display:flex;gap:8px;align-items:center;background:var(--bg2);padding:8px 10px;border-radius:10px;';
  row.innerHTML=`
    <select class="f-input" style="flex:1;" data-role="ingr-sel">
      <option value="">— 原料を選択 —</option>${ingrOpts}
    </select>
    <input type="number" class="f-input" placeholder="使用量" min="0" step="0.01" style="width:100px;" value="${escH(String(amount))}" data-role="amount">
    <span class="ingr-unit" id="${rowId}-unit">${ingrId?((ingredients.find(i=>i.id===ingrId)?.unit)||''):'—'}</span>
    <button onclick="document.getElementById('${rowId}').remove()" style="border:none;background:none;color:var(--text3);cursor:pointer;font-size:18px;">✕</button>
  `;
  row.querySelector('[data-role="ingr-sel"]').addEventListener('change', function(){
    const ig=ingredients.find(i=>i.id===this.value);
    document.getElementById(rowId+'-unit').textContent=ig?ig.unit:'—';
  });
  container.appendChild(row);
}

function saveRecipe(){
  const prodName=document.getElementById('recipe-prod-name').value.trim();
  if(!prodName){showToast('⚠️ 商品名を入力してください');return;}
  const rows=document.getElementById('recipe-ingr-rows').querySelectorAll('[id^="rrow-"]');
  const ingrList=[];
  let valid=true;
  rows.forEach(row=>{
    const ingrId=row.querySelector('[data-role="ingr-sel"]').value;
    const amount=parseFloat(row.querySelector('[data-role="amount"]').value||'0');
    if(!ingrId||isNaN(amount)||amount<=0){ valid=false; return; }
    ingrList.push({ingrId,amount});
  });
  if(!valid||!ingrList.length){showToast('⚠️ 原料と使用量をすべて正しく入力してください');return;}
  // 同名商品のレシピは上書き
  const existing=recipes.findIndex(r=>r.prodName===prodName);
  if(existing>=0){ recipes[existing]={...recipes[existing],ingredients:ingrList}; }
  else { recipes.push({id:'RC'+Date.now(),prodName,ingredients:ingrList}); }
  saveRecipes();
  document.getElementById('recipe-prod-name').value='';
  document.getElementById('recipe-ingr-rows').innerHTML='';
  document.getElementById('recipe-feedback').textContent='✅ レシピを保存しました!';
  setTimeout(()=>{ const el=document.getElementById('recipe-feedback');if(el)el.textContent=''; },2000);
  renderRecipeList();
}

function deleteRecipe(id){
  recipes=recipes.filter(r=>r.id!==id);
  saveRecipes();
  renderRecipeList();
  showToast('🗑 レシピを削除しました');
}

function renderRecipeList(){
  const container=document.getElementById('recipe-list');
  if(!container) return;
  if(!recipes.length){
    container.innerHTML='<div style="text-align:center;color:var(--text3);padding:30px;font-size:13px;">レシピがまだありません</div>';
    return;
  }
  container.innerHTML=recipes.map(r=>{
    const ingrCount=r.ingredients.length;
    return `<div class="recipe-card">
      <div class="recipe-head" onclick="toggleRecipeBody('${r.id}')">
        <span class="recipe-prod-icon">🍽️</span>
        <span class="recipe-prod-name">${escH(r.prodName)}</span>
        <span class="recipe-ing-count">${ingrCount}種類</span>
        <span class="recipe-toggle" id="rtog-${r.id}">▾</span>
      </div>
      <div class="recipe-body" id="rbody-${r.id}">
        ${r.ingredients.map(ri=>{
          const ig=ingredients.find(i=>i.id===ri.ingrId);
          return `<div class="recipe-ing-row">
            <span style="font-weight:700">${ig?escH(ig.name):'<span style="color:var(--coral)">削除済み</span>'}</span>
            <span style="color:var(--text2)">${fmtQty(ri.amount, ig?.unit||'')}</span>
          </div>`;
        }).join('')}
        <div style="display:flex;gap:8px;margin-top:8px;">
          <button class="f-btn f-btn-ghost" style="font-size:12px;padding:5px 12px;" onclick="loadRecipeToEdit('${r.id}')">✏️ 編集</button>
          <button class="f-btn f-btn-coral" style="font-size:12px;padding:5px 12px;" onclick="deleteRecipe('${r.id}')">🗑 削除</button>
        </div>
      </div>
    </div>`;
  }).join('');
}

function toggleRecipeBody(id){
  const body=document.getElementById('rbody-'+id);
  const tog=document.getElementById('rtog-'+id);
  if(!body) return;
  const isOpen=body.classList.toggle('open');
  if(tog) tog.classList.toggle('open',isOpen);
}

function loadRecipeToEdit(id){
  const r=recipes.find(rc=>rc.id===id);
  if(!r) return;
  document.getElementById('recipe-prod-name').value=r.prodName;
  document.getElementById('recipe-ingr-rows').innerHTML='';
  r.ingredients.forEach(ri=>addRecipeIngrRow(ri.ingrId,ri.amount));
  // 編集フォームが表示されているタブに切り替え
  const btn=document.querySelectorAll('#stock-tab-bar .period-btn')[3]; // レシピタブ
  showStockTab('recipe',btn);
}

// ── 売上からの自動消費計算 ──
function calcAutoConsumption(){
  const from=document.getElementById('auto-calc-from').value;
  const to=document.getElementById('auto-calc-to').value;
  const container=document.getElementById('auto-calc-result');
  if(!recipes.length){
    container.innerHTML='<div class="alert alert-warn"><div class="alert-icon">⚠️</div><div class="alert-body"><div class="alert-title">レシピが未登録です</div><div class="alert-text">「レシピ」タブから商品ごとの原料使用量を登録してください。</div></div></div>';
    return;
  }
  const rows=salesData.filter(r=>(!from||r.date>=from)&&(!to||r.date<=to));
  if(!rows.length){
    container.innerHTML='<div class="alert alert-warn"><div class="alert-icon">⚠️</div><div class="alert-body"><div class="alert-title">該当期間に売上データがありません</div></div></div>';
    return;
  }
  // 商品別販売数を集計
  const soldMap={};
  rows.forEach(r=>{
    const name=r.product||'';
    soldMap[name]=(soldMap[name]||0)+(parseFloat(r.quantity)||1);
  });
  // レシピに従って原料消費量を計算
  const consumeMap={}; // ingrId -> {ig, amount}
  let matched=0;
  Object.entries(soldMap).forEach(([prodName,qty])=>{
    // 商品名が完全一致 or 前方一致でレシピを探す
    const recipe=recipes.find(r=>r.prodName===prodName||prodName.startsWith(r.prodName)||r.prodName.startsWith(prodName));
    if(!recipe) return;
    matched++;
    recipe.ingredients.forEach(ri=>{
      if(!consumeMap[ri.ingrId]) consumeMap[ri.ingrId]={ig:ingredients.find(i=>i.id===ri.ingrId),amount:0};
      consumeMap[ri.ingrId].amount+=ri.amount*qty;
    });
  });
  if(!Object.keys(consumeMap).length){
    container.innerHTML=`<div class="alert alert-warn"><div class="alert-icon">ℹ️</div><div class="alert-body"><div class="alert-title">マッチするレシピが見つかりませんでした</div><div class="alert-text">売上商品名(${Object.keys(soldMap).join('、')})とレシピの商品名が一致するか確認してください。</div></div></div>`;
    return;
  }
  // 結果テーブルを生成
  const rows2=Object.entries(consumeMap).map(([ingrId,{ig,amount}])=>{
    const s=getIngrStock(ingrId);
    const after=+(s.qty-amount).toFixed(4);
    return {ingrId,ig,amount:+amount.toFixed(4),currentQty:s.qty,after};
  });
  container.innerHTML=`
    <div style="font-size:13px;font-weight:700;color:var(--text2);margin-bottom:8px;">
      計算結果(${from||'全期間'} 〜 ${to||''}): 売上${rows.length}件、マッチ商品${matched}種類
    </div>
    <div class="tbl-scroll">
      <table class="tbl">
        <thead><tr><th>原料</th><th>単位</th><th class="num">現在在庫</th><th class="num">消費量</th><th class="num">在庫後</th><th><input type="checkbox" id="consume-all-chk" onchange="toggleConsumeAll(this)" title="全選択"> 反映</th></tr></thead>
        <tbody>${rows2.map((r,i)=>`
          <tr>
            <td style="font-weight:800;">${r.ig?escH(r.ig.name):'<span style="color:var(--coral)">削除済み</span>'}</td>
            <td>${r.ig?escH(r.ig.unit):''}</td>
            <td class="num">${fmtQty(r.currentQty,'')}</td>
            <td class="num" style="color:var(--coral);">-${fmtQty(r.amount,'')}</td>
            <td class="num ${r.after<0?'bad':''}">${fmtQty(r.after,'')}</td>
            <td style="text-align:center;"><input type="checkbox" class="consume-chk" data-ingr="${r.ingrId}" data-amount="${r.amount}" checked></td>
          </tr>`).join('')}
        </tbody>
      </table>
    </div>
    <div style="margin-top:12px;display:flex;gap:10px;">
      <button class="f-btn f-btn-blue" onclick="applyConsumption()">✅ チェックした原料の在庫を減らす</button>
      <div style="font-size:11px;color:var(--text2);align-self:center;">※実際の廃棄・試食・賄い分は含まれないため、棚卸しで調整してください</div>
    </div>`;
}

function toggleConsumeAll(chk){
  document.querySelectorAll('.consume-chk').forEach(c=>c.checked=chk.checked);
}

function applyConsumption(){
  const chks=document.querySelectorAll('.consume-chk:checked');
  if(!chks.length){showToast('⚠️ 反映する原料を選択してください');return;}
  let count=0;
  chks.forEach(chk=>{
    const ingrId=chk.dataset.ingr;
    const amount=parseFloat(chk.dataset.amount||'0');
    const s=getIngrStock(ingrId);
    const ig=ingredients.find(i=>i.id===ingrId);
    const before=s.qty;
    s.qty=+Math.max(0,s.qty-amount).toFixed(4);
    s.log=s.log||[];
    s.log.unshift({date:new Date().toISOString().slice(0,16).replace('T',' '),op:'sub',qty:amount,before,after:s.qty,memo:'売上自動消費計算'});
    count++;
  });
  saveIngrData();
  document.getElementById('auto-calc-result').innerHTML=`<div class="alert alert-good"><div class="alert-icon">✅</div><div class="alert-body"><div class="alert-title">${count}種類の原料在庫を更新しました</div></div></div>`;
  renderIngrGrid();
  updateNavBadge();
  showToast(`✅ ${count}種類の原料在庫を反映しました`);
}

// ── 棚卸し ──
function renderStocktakeTable(){
  const tbody=document.getElementById('stocktake-tbody');
  if(!tbody) return;
  const date=document.getElementById('stocktake-date')?.value||todayStr();
  if(!ingredients.length){
    tbody.innerHTML='<tr><td colspan="8" style="text-align:center;color:var(--text3);padding:24px;">原料が未登録です</td></tr>';
    return;
  }
  tbody.innerHTML=ingredients.map(ig=>{
    const s=getIngrStock(ig.id);
    const theory=+(s.qty).toFixed(4);
    return `<tr class="stocktake-row" data-ingr="${ig.id}">
      <td style="text-align:center;"><input type="checkbox" class="stocktake-chk" checked></td>
      <td style="font-weight:800;">${escH(ig.name)}</td>
      <td>${escH(ig.cat||'—')}</td>
      <td><span class="ingr-unit">${escH(ig.unit)}</span></td>
      <td class="num">${fmtQty(theory,'')}</td>
      <td><input type="number" class="stocktake-actual" min="0" step="0.01" value="${theory}" oninput="updateStocktakeDiff(this,'${ig.id}',${theory},${ig.cost||0})"></td>
      <td class="num" id="std-diff-${ig.id}">—</td>
      <td class="num" id="std-loss-${ig.id}">—</td>
    </tr>`;
  }).join('');
}

function updateStocktakeDiff(input, ingrId, theory, cost){
  const actual=parseFloat(input.value||'0');
  const diff=+(actual-theory).toFixed(4);
  const loss=+(-diff*cost).toFixed(0);
  const diffEl=document.getElementById('std-diff-'+ingrId);
  const lossEl=document.getElementById('std-loss-'+ingrId);
  if(diffEl){
    diffEl.textContent=(diff>=0?'+':'')+diff;
    diffEl.className='num '+(diff<0?'loss-pos':diff>0?'loss-neg':'');
  }
  if(lossEl){
    lossEl.textContent=cost?(loss>=0?'+':'')+'¥'+loss:'—';
    lossEl.className='num '+(loss>0?'loss-pos':loss<0?'loss-neg':'');
  }
}

function applyStocktake(){
  const date=document.getElementById('stocktake-date').value||todayStr();
  const rows=document.querySelectorAll('.stocktake-row');
  let count=0;
  rows.forEach(row=>{
    const chk=row.querySelector('.stocktake-chk');
    if(!chk||!chk.checked) return;
    const ingrId=row.dataset.ingr;
    const actualInput=row.querySelector('.stocktake-actual');
    if(!actualInput) return;
    const actual=parseFloat(actualInput.value||'0');
    const s=getIngrStock(ingrId);
    const ig=ingredients.find(i=>i.id===ingrId);
    const before=s.qty;
    s.qty=actual;
    s.log=s.log||[];
    s.log.unshift({date,op:'set',qty:actual,before,after:actual,memo:'棚卸し確定'});
    count++;
  });
  if(!count){showToast('⚠️ 更新する原料を選択してください');return;}
  saveIngrData();
  renderIngrGrid();
  updateNavBadge();
  showToast(`✅ ${count}種類の原料在庫を棚卸し数量で更新しました`);
}

function toggleStocktakeAll(chk){
  document.querySelectorAll('.stocktake-chk').forEach(c=>c.checked=chk.checked);
}

function exportStocktakeCSV(){
  const date=document.getElementById('stocktake-date').value||todayStr();
  const rows=document.querySelectorAll('.stocktake-row');
  const hdrs=['原料名','カテゴリ','単位','理論在庫','実在庫','差異','ロス金額'];
  const data=[hdrs.join(',')];
  rows.forEach(row=>{
    const ig=ingredients.find(i=>i.id===row.dataset.ingr);
    if(!ig) return;
    const s=getIngrStock(ig.id);
    const theory=s.qty;
    const actual=parseFloat(row.querySelector('.stocktake-actual')?.value||'0');
    const diff=+(actual-theory).toFixed(4);
    const loss=+(diff*ig.cost).toFixed(0);
    data.push([escH(ig.name),escH(ig.cat||''),ig.unit,theory,actual,diff,ig.cost?loss:''].join(','));
  });
  const blob=new Blob(['\uFEFF'+data.join('\n')],{type:'text/csv;charset=utf-8'});
  const a=document.createElement('a');
  a.href=URL.createObjectURL(blob);
  a.download='MISE_stocktake_'+date+'.csv';
  a.click();
  setTimeout(()=>URL.revokeObjectURL(a.href),5000);
  showToast('📤 棚卸し表をCSV出力しました');
}

// ── ダッシュボードのアラート表示(商品在庫+原料在庫) ──
function renderDashStockAlerts(){
  const container=document.getElementById('dash-stock-alerts');
  const cnt=document.getElementById('stock-alert-count');
  const alertItems=[];
  // 商品在庫アラート
  (products.length?products:buildProductsFromSales()).forEach(p=>{
    const s=stockData[p.id];
    if(!s) return;
    const isDanger=s.qty<=s.danger;
    const isWarn=!isDanger&&s.qty<=s.warn;
    if(isDanger||isWarn) alertItems.push({type:isDanger?'bad':'warn',icon:isDanger?'🔴':'🟡',title:`${p.icon||'📦'} ${p.name}(商品在庫)— 残り${s.qty}個`,text:isDanger?`危険ライン(${s.danger}個以下)です。至急補充してください。`:`警告ライン(${s.warn}個以下)です。`});
  });
  // 原料在庫アラート
  ingredients.forEach(ig=>{
    const s=ingrStock[ig.id];
    if(!s) return;
    const qty=s.qty;
    const isDanger=qty<=ig.danger;
    const isWarn=!isDanger&&qty<=ig.warn;
    if(isDanger||isWarn) alertItems.push({type:isDanger?'bad':'warn',icon:isDanger?'🔴':'🟡',title:`🧂 ${ig.name}(原料)— 残り${fmtQty(qty,ig.unit)}`,text:isDanger?`危険ライン(${ig.danger}${ig.unit}以下)に達しています。至急補充してください。`:`警告ライン(${ig.warn}${ig.unit}以下)です。`});
  });
  if(cnt) cnt.textContent=alertItems.length?`${alertItems.length}件`:'なし';
  if(!alertItems.length){
    container.innerHTML='<div style="color:var(--text3);text-align:center;padding:20px;font-size:13px;">✅ アラートはありません</div>';
    return;
  }
  container.innerHTML=alertItems.sort((a,b)=>a.type.localeCompare(b.type)).map(a=>`
    <div class="alert alert-${a.type}">
      <div class="alert-icon">${a.icon}</div>
      <div class="alert-body"><div class="alert-title">${a.title}</div><div class="alert-text">${a.text}</div></div>
    </div>`).join('');
}

function updateNavBadge(){
  const cnt=getStockAlertCount();
  const badge=document.getElementById('nb-stock');
  if(!badge) return;
  const total=cnt.danger+cnt.warn;
  badge.style.display=total>0?'inline':'none';
  badge.textContent=total;
}

// ── ヘルパー(原料セレクト関連) ──
function rebuildIngrSelects(){
  // 入出庫セレクト
  rebuildAdjIngrSel();
  rebuildRecipeProdList();
  rebuildIngrCatList();
}

function rebuildIngrCatList(){
  const cats=[...new Set(ingredients.map(ig=>ig.cat).filter(Boolean))];
  const dl=document.getElementById('ingr-cat-list');
  if(dl) dl.innerHTML=cats.map(c=>`<option value="${escH(c)}">`).join('');
}

function rebuildRecipeProdList(){
  const dl=document.getElementById('recipe-prod-list');
  if(!dl) return;
  const names=[...new Set([
    ...products.map(p=>p.name),
    ...buildProductsFromSales().map(p=>p.name),
    ...recipes.map(r=>r.prodName),
  ])];
  dl.innerHTML=names.map(n=>`<option value="${escH(n)}">`).join('');
}

// ══════════════════════════════════════════════════════════════
// ── 収支・経理 ──
// ══════════════════════════════════════════════════════════════
function financeMonthOffset(delta){
  if(delta===0){
    const now=new Date();
    financeMonth=monthStr(now);
  } else {
    const d=new Date(financeMonth+'-01');
    d.setMonth(d.getMonth()+delta);
    financeMonth=monthStr(d);
  }
  document.getElementById('finance-month').value=financeMonth;
  renderFinance();
}

function renderFinance(){
  financeMonth=document.getElementById('finance-month').value || financeMonth;
  renderPL();
  renderDailySalesTable();
  renderFinanceChart();
  renderExpenseTable();
}

function renderPL(){
  const rows=salesForMonth(financeMonth);
  const sales=txAmount(rows);
  // 消費税を分解(売上に含まれる)
  let taxAmt=0;
  rows.forEach(r=>{
    const taxRate=(r.tax||10)/100;
    const preAmt=r.amount/(1+taxRate);
    taxAmt+=r.amount-preAmt;
  });
  const salesExTax=sales-taxAmt;

  // 仕入れ
  const purRows=purchases.filter(r=>(r.date||'').startsWith(financeMonth));
  const purTotal=purRows.reduce((s,r)=>s+(r.total||0),0);

  // 経費(仕入れ以外)
  const expRows=expenses.filter(r=>(r.date||'').startsWith(financeMonth)&&r.cat!=='仕入れ');
  const expBycat={};
  expRows.forEach(r=>{ expBycat[r.cat]=(expBycat[r.cat]||0)+(r.amount||0); });
  const expTotal=expRows.reduce((s,r)=>s+(r.amount||0),0);

  // 粗利・営業利益
  const grossProfit=salesExTax-purTotal;
  const operatingProfit=grossProfit-expTotal;

  const plBody=document.getElementById('pl-body');
  plBody.innerHTML=`
    <div class="pl-row header"><span>項目</span><span class="num">金額</span></div>
    <div class="pl-section">
      <div class="pl-row subtotal"><span>📈 売上高(税込)</span><span class="num" style="color:var(--blue)">${yen(sales)}</span></div>
      <div class="pl-row item" style="padding-left:24px;"><span style="color:var(--text2);">└ 消費税(概算)</span><span class="num" style="color:var(--text2);">-${yen(taxAmt)}</span></div>
      <div class="pl-row item" style="padding-left:24px;"><span style="color:var(--text2);">└ 売上高(税抜)</span><span class="num" style="color:var(--text2);">${yen(salesExTax)}</span></div>
    </div>
    <div class="pl-section">
      <div class="pl-row subtotal"><span>📦 仕入れ原価</span><span class="num" style="color:var(--coral)">-${yen(purTotal)}</span></div>
    </div>
    <div class="pl-row" style="background:var(--mint-l);border-radius:10px;padding:10px 12px;margin-bottom:12px;">
      <span style="font-weight:800;">粗利益</span>
      <span class="num" style="color:${grossProfit>=0?'var(--mint)':'var(--coral)'};font-size:16px;">${yen(grossProfit)}</span>
    </div>
    <div class="pl-section">
      <div class="pl-row subtotal"><span>💸 経費合計</span><span class="num" style="color:var(--coral)">-${yen(expTotal)}</span></div>
      ${Object.entries(expBycat).map(([cat,amt])=>`
        <div class="pl-row item" style="padding-left:24px;">
          <span style="color:var(--text2);">└ ${escH(cat)}</span>
          <span class="num" style="color:var(--text2);">-${yen(amt)}</span>
        </div>`).join('')}
    </div>
    <div class="pl-row total">
      <span>💰 営業利益</span>
      <span class="num">${yen(operatingProfit)}</span>
    </div>
    ${settings.target_monthly?`
      <div style="margin-top:12px;padding:10px 12px;background:var(--blue-l);border-radius:10px;">
        <div style="font-size:11px;font-weight:800;color:var(--text2);margin-bottom:6px;">月次目標進捗</div>
        <div style="display:flex;justify-content:space-between;font-size:13px;font-weight:800;margin-bottom:4px;">
          <span>売上 ${yen(sales)}</span><span>目標 ${yen(settings.target_monthly)}</span>
        </div>
        <div class="prog-bar" style="height:12px;"><div class="prog-fill" style="width:${Math.min(100,Math.round(sales/settings.target_monthly*100))}%;background:var(--blue)"></div></div>
        <div style="font-size:12px;font-weight:700;color:var(--blue);margin-top:4px;text-align:right;">${Math.round(sales/settings.target_monthly*100)}% 達成</div>
      </div>`:''}
  `;
}

function renderDailySalesTable(){
  const tbody=document.getElementById('daily-tbody');
  const DOWS=['日','月','火','水','木','金','土'];
  // この月の日付ごとに集計
  const dayMap={};
  salesForMonth(financeMonth).forEach(r=>{
    if(!r.date) return;
    if(!dayMap[r.date]) dayMap[r.date]={date:r.date,sales:0,tx:new Set(),disc:0};
    dayMap[r.date].sales+=(r.amount||0);
    dayMap[r.date].tx.add(r.txid||(r.datetime||''));
    dayMap[r.date].disc+=Math.round((r.amount/(1-r.discount_pct/100)*r.discount_pct/100)||0);
  });
  const days=Object.values(dayMap).sort((a,b)=>a.date.localeCompare(b.date));
  if(!days.length){tbody.innerHTML='<tr><td colspan="6" style="text-align:center;color:var(--text3);padding:20px;">データがありません</td></tr>';return;}
  tbody.innerHTML=days.map(d=>{
    const dow=new Date(d.date+'T00:00:00').getDay();
    const txCnt=d.tx.size;
    const atv=txCnt>0?Math.round(d.sales/txCnt):0;
    const isHoliday=dow===0||dow===6;
    return `<tr>
      <td>${d.date}</td>
      <td style="${isHoliday?'color:var(--coral)':''}${dow===6?'color:var(--blue)':''}">${DOWS[dow]}</td>
      <td class="num">${yen(d.sales)}</td>
      <td class="num">${txCnt}</td>
      <td class="num">${yen(atv)}</td>
      <td class="num" style="color:var(--mint);">-${yen(d.disc)}</td>
    </tr>`;
  }).join('');
}

function renderFinanceChart(){
  // 直近12ヶ月の売上・仕入れ・経費推移
  const now=new Date();
  const months=[];const salesArr=[];const purArr=[];const expArr=[];
  for(let i=11;i>=0;i--){
    const d=new Date(now.getFullYear(),now.getMonth()-i,1);
    const ym=monthStr(d);
    months.push(ym.slice(5)+'月');
    salesArr.push(txAmount(salesForMonth(ym)));
    purArr.push(purchases.filter(r=>(r.date||'').startsWith(ym)).reduce((s,r)=>s+(r.total||0),0));
    expArr.push(expenses.filter(r=>(r.date||'').startsWith(ym)&&r.cat!=='仕入れ').reduce((s,r)=>s+(r.amount||0),0));
  }
  makeChart('ch-finance',{
    type:'bar',
    data:{
      labels:months,
      datasets:[
        {label:'売上',data:salesArr,backgroundColor:COLORS[0]+'99',borderColor:COLORS[0],borderWidth:2,borderRadius:4,order:2},
        {label:'仕入れ',data:purArr,backgroundColor:COLORS[3]+'88',borderColor:COLORS[3],borderWidth:2,borderRadius:4,order:2},
        {label:'経費',data:expArr,backgroundColor:COLORS[2]+'88',borderColor:COLORS[2],borderWidth:2,borderRadius:4,order:2},
      ]
    },
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{labels:{font:{size:11},boxWidth:10}}},scales:{x:{ticks:{font:{size:10}},grid:{display:false}},y:{beginAtZero:true,ticks:{callback:v=>yen(v/1000)+'k',font:{size:10}}}}}
  });
}

function addExpense(){
  const date=document.getElementById('exp-date').value||todayStr();
  const cat=document.getElementById('exp-cat').value;
  const amount=parseInt(document.getElementById('exp-amount').value||'0');
  const memo=document.getElementById('exp-memo').value.trim();
  if(!amount||amount<=0){showToast('⚠️ 金額を入力してください');return;}
  expenses.unshift({id:'E'+Date.now(),date,cat,amount,memo});
  localStorage.setItem('mgr_expenses',JSON.stringify(expenses));
  document.getElementById('exp-amount').value='';
  document.getElementById('exp-memo').value='';
  showToast('✅ 経費を登録しました');
  renderExpenseTable();
  renderPL();
  renderFinanceChart();
}

function deleteExpense(id){
  expenses=expenses.filter(e=>e.id!==id);
  localStorage.setItem('mgr_expenses',JSON.stringify(expenses));
  renderExpenseTable();
  renderPL();
  renderFinanceChart();
}

function renderExpenseTable(){
  const tbody=document.getElementById('exp-tbody');
  const monthRows=expenses.filter(e=>(e.date||'').startsWith(financeMonth))
    .sort((a,b)=>b.date.localeCompare(a.date));
  if(!monthRows.length){tbody.innerHTML='<tr><td colspan="5" style="text-align:center;color:var(--text3);padding:20px;">この月の経費はありません</td></tr>';return;}
  tbody.innerHTML=monthRows.map(e=>`
    <tr>
      <td>${e.date}</td>
      <td><span class="chip chip-amber">${escH(e.cat)}</span></td>
      <td style="color:var(--text2);">${escH(e.memo||'')}</td>
      <td class="num bad">-${yen(e.amount)}</td>
      <td><button onclick="deleteExpense('${e.id}')" style="border:none;background:none;color:var(--text3);cursor:pointer;font-size:14px;" title="削除">🗑</button></td>
    </tr>`).join('');
}

// ══════════════════════════════════════════════════════════════
// ── 売上分析 ──
// ══════════════════════════════════════════════════════════════
function setAnalysisPeriod(period, btn){
  analysisPeriod=period;
  document.querySelectorAll('.period-bar .period-btn').forEach(b=>b.classList.remove('active'));
  if(btn) btn.classList.add('active');
  const today=todayStr();
  if(period==='week'){
    const d=new Date(today);d.setDate(d.getDate()-6);
    analysisFrom=d.toISOString().slice(0,10);analysisTo=today;
  } else if(period==='month'){
    const d=new Date(today);d.setDate(d.getDate()-29);
    analysisFrom=d.toISOString().slice(0,10);analysisTo=today;
  } else if(period==='quarter'){
    const d=new Date(today);d.setDate(d.getDate()-89);
    analysisFrom=d.toISOString().slice(0,10);analysisTo=today;
  } else if(period==='all'){
    analysisFrom='';analysisTo='';
  }
  if(period!=='custom'){
    document.getElementById('an-from').value=analysisFrom;
    document.getElementById('an-to').value=analysisTo;
  } else {
    analysisFrom=document.getElementById('an-from').value;
    analysisTo=document.getElementById('an-to').value;
  }
  renderAnalysis();
}

function setRankMetric(metric, btn){
  rankMetric=metric;
  document.querySelectorAll('#panel-analysis .s-card .period-btn').forEach(b=>b.classList.remove('active'));
  if(btn) btn.classList.add('active');
  const rows=filteredSales(analysisFrom,analysisTo);
  renderProductRanking(rows);
}

function renderAnalysis(){
  const rows=filteredSales(analysisFrom,analysisTo);
  renderProductRanking(rows);
  renderCatAnalysis(rows);
  renderHourlyChart(rows);
  renderDowChart(rows);
  renderCustCharts(rows);
  renderMonthlyChart();
}

function renderProductRanking(rows){
  const prodMap={};
  rows.forEach(r=>{
    const k=r.product||'不明';
    if(!prodMap[k]) prodMap[k]={amount:0,qty:0,txids:new Set()};
    prodMap[k].amount+=(r.amount||0);
    prodMap[k].qty+=(r.quantity||1);
    prodMap[k].txids.add(r.txid||(r.datetime||'')+(r.product||''));
  });
  const entries=Object.entries(prodMap).map(([name,v])=>({name,amount:v.amount,qty:v.qty,tx:v.txids.size}));
  entries.sort((a,b)=>b[rankMetric]-a[rankMetric]);
  const top=entries.slice(0,10);
  const maxVal=top[0]?top[0][rankMetric]:1;
  const container=document.getElementById('product-ranking');
  const lbl=rankMetric==='amount'?'売上金額':rankMetric==='qty'?'販売個数':'販売回数';
  document.getElementById('an-rank-metric-lbl').textContent=lbl;
  container.innerHTML=top.map((p,i)=>{
    const val=p[rankMetric];
    const valStr=rankMetric==='amount'?yen(val):val.toLocaleString()+(rankMetric==='qty'?'個':'回');
    return `<div class="rank-item">
      <div class="rank-no${i===0?' top1':i===1?' top2':i===2?' top3':''}">${i+1}</div>
      <div class="rank-bar-wrap">
        <div class="rank-name">${escH(p.name)}</div>
        <div class="rank-bar-track"><div class="rank-bar-fill" style="width:${Math.round(val/maxVal*100)}%;background:${COLORS[i%COLORS.length]}"></div></div>
      </div>
      <div class="rank-val">${valStr}</div>
    </div>`;
  }).join('');
  if(!top.length) container.innerHTML='<div style="color:var(--text3);text-align:center;padding:20px;">データがありません</div>';
}

function renderCatAnalysis(rows){
  const catMap={};
  rows.forEach(r=>{ const c=r.category||'その他'; catMap[c]=(catMap[c]||0)+(r.amount||0); });
  const cats=Object.keys(catMap).sort((a,b)=>catMap[b]-catMap[a]);
  makeChart('ch-an-cat',{
    type:'doughnut',
    data:{labels:cats,datasets:[{data:cats.map(c=>catMap[c]),backgroundColor:COLORS.slice(0,cats.length),borderWidth:2,borderColor:'#fff'}]},
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'right',labels:{font:{size:11},boxWidth:10}}}}
  });
}

function renderHourlyChart(rows){
  const hours=Array.from({length:24},(_,i)=>i);
  const vals=Array(24).fill(0);
  rows.forEach(r=>{ const h=parseInt(r.hour||0); if(h>=0&&h<24) vals[h]+=(r.amount||0); });
  makeChart('ch-hourly',{
    type:'bar',
    data:{labels:hours.map(h=>h+'時'),datasets:[{label:'売上',data:vals,backgroundColor:COLORS[1]+'88',borderColor:COLORS[1],borderWidth:2,borderRadius:4}]},
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{ticks:{maxTicksLimit:12,font:{size:10}},grid:{display:false}},y:{beginAtZero:true,ticks:{callback:v=>v>=1000?yen(v/1000)+'k':yen(v),font:{size:10}}}}}
  });
}

function renderDowChart(rows){
  const DOWS=['日','月','火','水','木','金','土'];
  const vals=Array(7).fill(0);const cnts=Array(7).fill(0);
  rows.forEach(r=>{const d=parseInt(r.dow||0);if(d>=0&&d<7){vals[d]+=(r.amount||0);cnts[d]++;}});
  const avgs=vals.map((v,i)=>cnts[i]>0?Math.round(v/cnts[i]):0);
  const bgColors=DOWS.map((_,i)=>i===0?COLORS[3]+'88':i===6?COLORS[0]+'88':COLORS[1]+'88');
  makeChart('ch-dow',{
    type:'bar',
    data:{labels:DOWS,datasets:[{label:'平均売上',data:avgs,backgroundColor:bgColors,borderColor:bgColors.map(c=>c.slice(0,-2)),borderWidth:2,borderRadius:6}]},
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{grid:{display:false}},y:{beginAtZero:true,ticks:{callback:v=>v>=1000?yen(v/1000)+'k':yen(v),font:{size:10}}}}}
  });
}

function renderCustCharts(rows){
  const CUST_LABEL={general:'一般',member:'会員',student:'学生',senior:'シニア',staff:'スタッフ'};
  const custMap={};const ageMap={};
  rows.forEach(r=>{
    const c=CUST_LABEL[r.customer_type]||(r.customer_type||'未記録');
    custMap[c]=(custMap[c]||0)+(r.amount||0);
    const a=r.age_band||'未記録';
    ageMap[a]=(ageMap[a]||0)+(r.amount||0);
  });
  const custEntries=Object.entries(custMap).sort((a,b)=>b[1]-a[1]);
  const ageOrder=['〜19','20-29','30-39','40-49','50-59','60+','未記録'];
  const ageEntries=ageOrder.filter(k=>ageMap[k]).map(k=>[k,ageMap[k]]);

  makeChart('ch-cust',{
    type:'doughnut',
    data:{labels:custEntries.map(e=>e[0]),datasets:[{data:custEntries.map(e=>e[1]),backgroundColor:COLORS.slice(0,custEntries.length),borderWidth:2,borderColor:'#fff'}]},
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'right',labels:{font:{size:11},boxWidth:10}}}}
  });
  makeChart('ch-age',{
    type:'bar',
    data:{labels:ageEntries.map(e=>e[0]),datasets:[{label:'売上',data:ageEntries.map(e=>e[1]),backgroundColor:COLORS[4]+'99',borderColor:COLORS[4],borderWidth:2,borderRadius:6}]},
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{grid:{display:false}},y:{beginAtZero:true,ticks:{callback:v=>v>=1000?yen(v/1000)+'k':yen(v),font:{size:10}}}}}
  });
}

function renderMonthlyChart(){
  const now=new Date();
  const months=[];const vals=[];
  for(let i=11;i>=0;i--){
    const d=new Date(now.getFullYear(),now.getMonth()-i,1);
    const ym=monthStr(d);
    months.push(ym.slice(5)+'月');
    vals.push(txAmount(salesForMonth(ym)));
  }
  makeChart('ch-monthly',{
    type:'bar',
    data:{labels:months,datasets:[{label:'月売上',data:vals,backgroundColor:COLORS[0]+'88',borderColor:COLORS[0],borderWidth:2,borderRadius:6}]},
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{grid:{display:false}},y:{beginAtZero:true,ticks:{callback:v=>v>=1000?yen(v/1000)+'k':yen(v),font:{size:10}}}}}
  });
}

// ══════════════════════════════════════════════════════════════
// ── 仕入れ帳 ──
// ══════════════════════════════════════════════════════════════
function renderPurchasePanel(){
  rebuildProductSelects();
  renderPurchaseSummary();
  renderPurchaseList();
}

function addPurchase(){
  const date=document.getElementById('pur-date').value||todayStr();
  const supplier=document.getElementById('pur-supplier').value.trim();
  const purProdVal=document.getElementById('pur-prod').value; // 'prod:ID' or 'ingr:ID' or ''
  const qty=parseFloat(document.getElementById('pur-qty').value||'0');
  const unitPrice=parseFloat(document.getElementById('pur-unit-price').value||'0');
  const total=parseFloat(document.getElementById('pur-total').value||'0')||(qty*unitPrice);
  const memo=document.getElementById('pur-memo').value.trim();
  if(!supplier){showToast('⚠️ 仕入れ先を入力してください');return;}
  if(!qty||qty<=0){showToast('⚠️ 数量を入力してください');return;}
  if(!total||total<=0){showToast('⚠️ 金額を入力してください');return;}

  // 品目の種別を判定
  let prodId='', ingrId='', prodName='';
  if(purProdVal.startsWith('prod:')){
    prodId=purProdVal.slice(5);
    prodName=products.find(p=>p.id===prodId)?.name||prodId;
  } else if(purProdVal.startsWith('ingr:')){
    ingrId=purProdVal.slice(5);
    prodName=ingredients.find(i=>i.id===ingrId)?.name||ingrId;
  }

  const pur={
    id:'PU'+Date.now(), date, supplier,
    product_id:prodId, ingr_id:ingrId, product_name:prodName,
    qty, unit_price:unitPrice, total, memo
  };
  purchases.unshift(pur);
  localStorage.setItem('mgr_purchases',JSON.stringify(purchases));

  // 商品在庫に反映
  if(prodId){
    const s=getStockItem(prodId);
    const before=s.qty;
    s.qty=+(s.qty+qty).toFixed(4);
    s.log=s.log||[];
    s.log.unshift({date,op:'add',qty,memo:'仕入れ: '+supplier,before,after:s.qty});
    localStorage.setItem('mgr_stock',JSON.stringify(stockData));
  }
  // 原料在庫に反映
  if(ingrId){
    const s=getIngrStock(ingrId);
    const ig=ingredients.find(i=>i.id===ingrId);
    const before=s.qty;
    s.qty=+(s.qty+qty).toFixed(4);
    s.log=s.log||[];
    s.log.unshift({date:new Date().toISOString().slice(0,16).replace('T',' '),op:'add',qty,before,after:s.qty,memo:'仕入れ: '+supplier,unitCost:unitPrice});
    // 原料マスタの単価を更新(仕入れ単価で上書き)
    if(ig&&unitPrice>0) ig.cost=+(total/qty).toFixed(4);
    saveIngrData();
  }
  // 経費に追加
  expenses.unshift({id:'E'+Date.now(),date,cat:'仕入れ',amount:total,memo:supplier+(prodName?' '+prodName:'')});
  localStorage.setItem('mgr_expenses',JSON.stringify(expenses));

  // フォームクリア
  ['pur-qty','pur-unit-price','pur-total','pur-memo'].forEach(id=>{ const el=document.getElementById(id); if(el) el.value=''; });
  document.getElementById('pur-supplier').value='';
  document.getElementById('pur-prod').value='';

  const target=ingrId?'原料在庫':prodId?'商品在庫':'経費のみ';
  showToast(`✅ 仕入れを登録しました(${target}・経費に反映)`);
  renderPurchaseSummary();
  renderPurchaseList();
  rebuildSupplierList();
  // 原料在庫グリッドが表示中なら更新
  renderIngrGrid();
  updateNavBadge();
}

// 数量×単価の自動計算
document.getElementById('pur-qty').addEventListener('input',calcPurTotal);
document.getElementById('pur-unit-price').addEventListener('input',calcPurTotal);
function calcPurTotal(){
  const q=parseInt(document.getElementById('pur-qty').value||'0');
  const u=parseInt(document.getElementById('pur-unit-price').value||'0');
  if(q>0&&u>0) document.getElementById('pur-total').value=q*u;
}

function deletePurchase(id){
  const pur=purchases.find(p=>p.id===id);
  if(!pur) return;
  // 在庫から戻す確認
  purchases=purchases.filter(p=>p.id!==id);
  localStorage.setItem('mgr_purchases',JSON.stringify(purchases));
  // 対応する経費も削除(同日・仕入れ科目)
  expenses=expenses.filter(e=>!(e.cat==='仕入れ'&&e.date===pur.date&&e.amount===pur.total&&e.memo.includes(pur.supplier)));
  localStorage.setItem('mgr_expenses',JSON.stringify(expenses));
  showToast('🗑 削除しました');
  renderPurchaseSummary();
  renderPurchaseList();
}

function renderPurchaseSummary(){
  const now=new Date();
  const thisMonth=monthStr(now);
  const purRows=purchases.filter(r=>(r.date||'').startsWith(thisMonth));
  const total=purRows.reduce((s,r)=>s+(r.total||0),0);
  const supplierMap={};
  purRows.forEach(r=>{ supplierMap[r.supplier]=(supplierMap[r.supplier]||0)+(r.total||0); });
  const topSuppliers=Object.entries(supplierMap).sort((a,b)=>b[1]-a[1]).slice(0,3);

  // 仕入れカテゴリ(商品カテゴリ別)
  const catMap={};
  purRows.forEach(r=>{
    const p=products.find(pr=>pr.id===r.product_id);
    const cat=p?.cat||r.product_name||'その他';
    catMap[cat]=(catMap[cat]||0)+(r.total||0);
  });
  const cats=Object.keys(catMap).sort((a,b)=>catMap[b]-catMap[a]);

  document.getElementById('pur-summary').innerHTML=`
    <div style="display:flex;gap:16px;flex-wrap:wrap;margin-bottom:12px;">
      <div style="text-align:center;"><div style="font-size:22px;font-weight:900;font-family:'Nunito',sans-serif;color:var(--blue)">${yen(total)}</div><div style="font-size:11px;color:var(--text2);font-weight:700;">今月の仕入れ合計</div></div>
      <div style="text-align:center;"><div style="font-size:22px;font-weight:900;font-family:'Nunito',sans-serif;color:var(--navy)">${purRows.length}</div><div style="font-size:11px;color:var(--text2);font-weight:700;">仕入れ件数</div></div>
    </div>
    ${topSuppliers.map(([sup,amt])=>`
      <div style="display:flex;justify-content:space-between;font-size:12px;padding:5px 0;border-bottom:1px solid var(--border);">
        <span style="font-weight:700">${escH(sup)}</span><span style="font-family:'Nunito',sans-serif;font-weight:800;color:var(--blue)">${yen(amt)}</span>
      </div>`).join('')}
  `;

  makeChart('ch-pur-cat',{
    type:'doughnut',
    data:{labels:cats,datasets:[{data:cats.map(c=>catMap[c]),backgroundColor:COLORS.slice(0,cats.length),borderWidth:2,borderColor:'#fff'}]},
    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'right',labels:{font:{size:10},boxWidth:8}}}}
  });
}

function renderPurchaseList(){
  const filterMonth=document.getElementById('pur-filter-month').value||monthStr(new Date());
  const rows=purchases.filter(r=>(r.date||'').startsWith(filterMonth)).sort((a,b)=>b.date.localeCompare(a.date));
  const tbody=document.getElementById('pur-tbody');
  if(!rows.length){tbody.innerHTML='<tr><td colspan="8" style="text-align:center;color:var(--text3);padding:20px;">この月の仕入れ記録はありません</td></tr>';return;}
  tbody.innerHTML=rows.map(r=>`
    <tr>
      <td>${r.date}</td>
      <td style="font-weight:700;">${escH(r.supplier)}</td>
      <td>${escH(r.product_name||'—')}</td>
      <td class="num">${r.qty.toLocaleString()}</td>
      <td class="num">${r.unit_price?yen(r.unit_price):'—'}</td>
      <td class="num" style="color:var(--blue);font-weight:800;">${yen(r.total)}</td>
      <td style="color:var(--text2);font-size:12px;">${escH(r.memo||'')}</td>
      <td><button onclick="deletePurchase('${r.id}')" class="purchase-del" title="削除">🗑</button></td>
    </tr>`).join('');
}

function exportPurchaseCSV(){
  const filterMonth=document.getElementById('pur-filter-month').value||monthStr(new Date());
  const rows=purchases.filter(r=>(r.date||'').startsWith(filterMonth));
  if(!rows.length){showToast('⚠️ 仕入れデータがありません');return;}
  const hdrs=['date','supplier','product_id','product_name','qty','unit_price','total','memo'];
  const csv=[hdrs.join(','),...rows.map(r=>hdrs.map(h=>{
    const v=String(r[h]||'');
    return v.includes(',')||v.includes('"')?`"${v.replace(/"/g,'""')}"`:`${v}`;
  }).join(','))].join('\n');
  const blob=new Blob(['\uFEFF'+csv],{type:'text/csv;charset=utf-8'});
  const a=document.createElement('a');
  a.href=URL.createObjectURL(blob);
  a.download='MISE_purchase_'+filterMonth+'.csv';
  a.click();
  setTimeout(()=>URL.revokeObjectURL(a.href),5000);
  showToast('📤 仕入れCSVを出力しました');
}

// ══════════════════════════════════════════════════════════════
// ── 設定 ──
// ══════════════════════════════════════════════════════════════
function saveSettings(){
  settings.store=document.getElementById('set-store').value.trim();
  settings.type=document.getElementById('set-type').value.trim();
  settings.target_monthly=parseInt(document.getElementById('set-target').value||'0')||0;
  localStorage.setItem('mgr_settings',JSON.stringify(settings));
  document.getElementById('nav-store').textContent=settings.store||'店舗未設定';
  showToast('💾 設定を保存しました');
}

function updateDataStatus(){
  document.getElementById('ds-sales-cnt').textContent=salesData.length.toLocaleString();
  document.getElementById('ds-prod-cnt').textContent=products.length.toLocaleString();
  if(salesData.length){
    const dates=salesData.map(r=>r.date||'').filter(Boolean).sort();
    document.getElementById('ds-date-range').innerHTML=
      `<span style="font-size:14px;">${dates[0]}<br>〜${dates[dates.length-1]}</span>`;
  } else {
    document.getElementById('ds-date-range').textContent='—';
  }
}

function clearAllData(){
  if(!confirm('売上・仕入れ・経費データをすべて削除します。\n原料マスタ・レシピは保持します。\nこの操作は取り消せません。')) return;
  salesData=[];purchases=[];expenses=[];
  ['mgr_sales','mgr_purchases','mgr_expenses'].forEach(k=>localStorage.setItem(k,'[]'));
  updateDataStatus();
  renderDashboard();
  showToast('🗑 データをリセットしました');
}

function exportAllCSV(){
  if(!salesData.length){showToast('⚠️ 売上データがありません');return;}
  const hdrs=['txid','datetime','date','hour','dow','month','product_id','product','category','price','quantity','amount','tax','discount_pct','customer_type','age_band','note'];
  const csv=[hdrs.join(','),...salesData.map(r=>hdrs.map(h=>{
    const v=String(r[h]!==undefined?r[h]:'');
    return v.includes(',')||v.includes('"')?`"${v.replace(/"/g,'""')}"`:`${v}`;
  }).join(','))].join('\n');
  const blob=new Blob(['\uFEFF'+csv],{type:'text/csv;charset=utf-8'});
  const a=document.createElement('a');
  a.href=URL.createObjectURL(blob);
  a.download='MISE_all_sales_'+todayStr()+'.csv';
  a.click();
  setTimeout(()=>URL.revokeObjectURL(a.href),5000);
  showToast('📤 全売上データをCSV出力しました');
}

function exportReport(){
  // 現在のパネルに応じてエクスポート
  if(document.getElementById('panel-purchase').classList.contains('active')){
    exportPurchaseCSV();
  } else {
    exportAllCSV();
  }
}

// ══════════════════════════════════════════════════════════════
// ── 商品セレクト構築 ──
// ══════════════════════════════════════════════════════════════
function rebuildProductSelects(){
  const prods=products.length?products:buildProductsFromSales();
  const makeOpts=()=>{
    return prods.map(p=>`<option value="${escH(p.id)}">${escH(p.icon||'')} ${escH(p.name)} (${escH(p.cat||'')})</option>`).join('');
  };
  // 商品在庫の商品選択
  ['stock-prod-sel','alert-prod-sel'].forEach(id=>{
    const el=document.getElementById(id);
    if(el) el.innerHTML=makeOpts();
  });
  // 仕入れ帳:商品マスタ + 原料マスタ をグループ分けして選択
  const purSel=document.getElementById('pur-prod');
  if(purSel){
    let html='<option value="">(品目を選択)</option>';
    if(prods.length){
      html+=`<optgroup label="🛍️ 商品在庫">`;
      html+=prods.map(p=>`<option value="prod:${escH(p.id)}">${escH(p.icon||'')} ${escH(p.name)}</option>`).join('');
      html+=`</optgroup>`;
    }
    if(ingredients.length){
      html+=`<optgroup label="🧂 原料在庫">`;
      html+=ingredients.map(ig=>`<option value="ingr:${escH(ig.id)}">🧂 ${escH(ig.name)} (${escH(ig.unit)})</option>`).join('');
      html+=`</optgroup>`;
    }
    purSel.innerHTML=html;
  }
  rebuildSupplierList();
}

// 仕入れ品目変更時:単位ヒントを出す
function onPurProdChange(){
  const val=document.getElementById('pur-prod')?.value||'';
  if(val.startsWith('ingr:')){
    const ingrId=val.slice(5);
    const ig=ingredients.find(i=>i.id===ingrId);
    if(ig&&ig.cost){
      document.getElementById('pur-unit-price').value=ig.cost;
    }
  }
}

function rebuildSupplierList(){
  const suppliers=[...new Set(purchases.map(p=>p.supplier).filter(Boolean))];
  const dl=document.getElementById('supplier-list');
  if(dl) dl.innerHTML=suppliers.map(s=>`<option value="${escH(s)}">`).join('');
}

// ══════════════════════════════════════════════════════════════
// ── Toast ──
// ══════════════════════════════════════════════════════════════
let toastTimer;
function showToast(msg){
  const t=document.getElementById('toast');
  t.textContent=msg;t.classList.add('show');
  clearTimeout(toastTimer);
  toastTimer=setTimeout(()=>t.classList.remove('show'),2400);
}

// ── 起動 ──
init();
</script>
</body>
</html>

いいなと思ったら応援しよう!

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
お店の経営ツール「Mise Manager」|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1