const {useState,useEffect,useCallback} = React;

// ═══ CONFIG ═══
const API = window.location.origin;
const DEV_USER = new URLSearchParams(window.location.search).get("user") || null;
const SUPABASE_URL = "https://cekiibugdwrmqcshbicu.supabase.co";
const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNla2lpYnVnZHdybXFjc2hiaWN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc0NzgwNDAsImV4cCI6MjA5MzA1NDA0MH0.3wvtLWV7aJgTwa7iJ-J6ljCNCThZal4p78ISu0y1CRo";

// Token storage
const getToken = () => {
  try { return JSON.parse(localStorage.getItem("cc_session"))?.access_token || null; } catch { return null; }
};
const setSession = s => localStorage.setItem("cc_session", JSON.stringify(s));
const clearSession = () => localStorage.removeItem("cc_session");

// Supabase auth calls
const supabaseLogin = async (email, password) => {
  const r = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=password`, {
    method: "POST",
    headers: {"Content-Type":"application/json","apikey":SUPABASE_ANON_KEY},
    body: JSON.stringify({email, password})
  });
  const data = await r.json();
  if (!r.ok) throw new Error(data.error_description || data.msg || "Login failed");
  return data;
};

const supabaseRefresh = async (refresh_token) => {
  const r = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`, {
    method: "POST",
    headers: {"Content-Type":"application/json","apikey":SUPABASE_ANON_KEY},
    body: JSON.stringify({refresh_token})
  });
  const data = await r.json();
  if (!r.ok) return null;
  return data;
};

// ═══ API ═══
// Platform-admin "acting as" — when a Bruton Intelligence admin scopes
// themselves into a different franchise, the chosen franchise_id is
// stored here and sent on every API call via X-Active-Franchise-Id.
const getActiveFranchiseId = () => {
  try { return localStorage.getItem("cc_active_franchise_id") || null; } catch { return null; }
};
const setActiveFranchiseId = (id) => {
  try {
    if (id) localStorage.setItem("cc_active_franchise_id", id);
    else localStorage.removeItem("cc_active_franchise_id");
  } catch {}
};

const apiFetch = async (path, opts={}, _retried=false) => {
  const headers = {"Content-Type":"application/json", ...opts.headers};
  // Forward the active-franchise override on every request (no-op if not set
  // or the caller isn't a platform admin — backend silently ignores)
  const af = getActiveFranchiseId();
  if (af) headers["X-Active-Franchise-Id"] = af;
  // Dev bypass via ?user= URL param
  if (DEV_USER) {
    headers["x-dev-user-id"] = DEV_USER;
    const sep = path.includes("?") ? "&" : "?";
    const r = await fetch(`${API}${path}${sep}dev_user_id=${DEV_USER}`, {...opts, headers});
    if (!r.ok) { const e = await r.json().catch(()=>({})); throw new Error(e.detail||r.statusText); }
    return r.json();
  }
  // Production: use JWT token
  const token = getToken();
  if (token) headers["Authorization"] = `Bearer ${token}`;
  const r = await fetch(`${API}${path}`, {...opts, headers});
  if (r.status === 401 && !_retried) {
    // Try refreshing the token once before giving up
    const sess = JSON.parse(localStorage.getItem("cc_session")||"{}");
    if (sess.refresh_token) {
      const newSess = await supabaseRefresh(sess.refresh_token);
      if (newSess?.access_token) {
        setSession(newSess);
        return apiFetch(path, opts, true);
      }
    }
    clearSession();
    window.location.reload();
    return null;
  }
  if (!r.ok) { const e = await r.json().catch(()=>({})); throw new Error(e.detail||r.statusText); }
  return r.json();
};

// ═══ HELPERS ═══
const dk = d => d.toISOString().slice(0,10);
const mondayOf = d => { const m=new Date(d); m.setDate(m.getDate()-((m.getDay()+6)%7)); return m; };
const weekDays = mon => Array.from({length:7},(_,i)=>{ const d=new Date(mon); d.setDate(d.getDate()+i); return d; });
const isSame = (a,b) => dk(a)===dk(b);
const apiTime = t => { if(!t)return""; const m=t.match(/(\d+):(\d+)\s*(AM|PM)/i); if(!m)return t; return `${+m[1]}:${m[2]}${m[3][0].toLowerCase()}`; };
const to24 = t => { const[tm,p]=t.split(/(?=[ap])/); let[h,m]=tm.split(":").map(Number); if(p==="p"&&h!==12)h+=12; if(p==="a"&&h===12)h=0; return `${String(h).padStart(2,"0")}:${String(m||0).padStart(2,"0")}:00`; };
const pT = t => { const[tm,p]=t.split(/(?=[ap])/); let[h,m]=tm.split(":").map(Number); if(p==="p"&&h!==12)h+=12; if(p==="a"&&h===12)h=0; return h+(m||0)/60; };
const fmtH = h => { const hr=Math.floor(h),mn=Math.round((h-hr)*60); return mn?`${hr}h ${mn}m`:`${hr}h`; };

const DS = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
const DF = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
const MS = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
const RC = { Stylist:{bg:"#dbeafe",tx:"#1e40af",bd:"#93c5fd"}, Receptionist:{bg:"#f3e8ff",tx:"#7c3aed",bd:"#c4b5fd"} };
const LC = [
  {bg:"#dbeafe",tx:"#1e40af",bd:"#93c5fd",pill:"#3b82f6"},  // blue  (e.g. Fort Worth)
  {bg:"#fce7f3",tx:"#9d174d",bd:"#f9a8d4",pill:"#ec4899"},  // pink  (e.g. Arlington)
  {bg:"#d1fae5",tx:"#065f46",bd:"#6ee7b7",pill:"#10b981"},  // green
  {bg:"#fef3c7",tx:"#92400e",bd:"#fcd34d",pill:"#f59e0b"},  // amber
  {bg:"#e0e7ff",tx:"#3730a3",bd:"#a5b4fc",pill:"#6366f1"},  // indigo
  {bg:"#ffe4e6",tx:"#9f1239",bd:"#fda4af",pill:"#f43f5e"},  // rose
];
const getLC = (locId, locs) => { const idx = (locs||[]).findIndex(l=>l.id===locId); return LC[idx>=0?idx%LC.length:0]; };
const TOPTS = (()=>{ const a=[]; for(let h=6;h<=21;h++) for(let m=0;m<60;m+=10){ const hr=h>12?h-12:h===0?12:h; a.push(`${hr}:${String(m).padStart(2,"0")}${h>=12?"p":"a"}`); } return a; })();

// ═══ STYLES ═══
const SI = {width:"100%",padding:"8px 10px",borderRadius:6,border:"1px solid #d1d5db",fontSize:14,color:"#111",background:"#fff",outline:"none"};
const CD = {background:"#fff",borderRadius:10,border:"1px solid #e5e7eb",padding:16,marginBottom:12};
const GH = {padding:"8px 4px",textAlign:"center",borderBottom:"1px solid #e5e7eb",borderRight:"1px solid #f3f4f6",background:"#f9fafb",fontSize:11,fontWeight:600,color:"#6b7280"};
const NB = {width:36,height:36,borderRadius:8,border:"1px solid #e5e7eb",background:"#fff",fontSize:18,color:"#374151",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center"};
const AV = {width:36,height:36,borderRadius:8,background:"#f3f4f6",display:"flex",alignItems:"center",justifyContent:"center",fontSize:12,fontWeight:700,color:"#6b7280",flexShrink:0};
const IB = {width:30,height:30,borderRadius:6,border:"1px solid #e5e7eb",background:"#fff",display:"flex",alignItems:"center",justifyContent:"center",cursor:"pointer",fontSize:14,color:"#6b7280"};
const SH = {fontSize:13,fontWeight:700,color:"#374151",marginBottom:10,textTransform:"uppercase",letterSpacing:.5};
const BTN = {padding:"10px 16px",borderRadius:8,border:"none",fontSize:14,fontWeight:600,cursor:"pointer",background:"#3b82f6",color:"#fff"};

// ═══ SHARED COMPONENTS ═══
const Badge = ({s}) => {
  const m = {DRAFT:["#fef3c7","#92400e"],PUBLISHED:["#d1fae5","#065f46"],PENDING:["#fef3c7","#92400e"],APPROVED:["#d1fae5","#065f46"],DENIED:["#fee2e2","#991b1b"]};
  const[bg,c] = m[s]||m.DRAFT;
  return <span style={{background:bg,color:c,fontSize:10,fontWeight:600,padding:"2px 8px",borderRadius:4,textTransform:"uppercase",letterSpacing:.5}}>{s}</span>;
};

const Toast = ({msg}) => msg ? <div style={{position:"fixed",bottom:80,left:"50%",transform:"translateX(-50%)",background:"#1f2937",color:"#fff",padding:"10px 20px",borderRadius:8,fontSize:13,fontWeight:500,zIndex:999,boxShadow:"0 4px 12px rgba(0,0,0,.25)",animation:"fadeUp .25s ease"}}>{msg}</div> : null;

// ═══ SHIFT MODAL ═══
function ShiftModal({mode,shift,name,day,positions,templates,locId,userLocs,locs,onSave,onDelete,onClose}) {
  const[start,setStart]=useState(shift?.start||"9:50a");
  const[end,setEnd]=useState(shift?.end||"6:00p");
  const[role,setRole]=useState(shift?.role||positions?.[0]||"Stylist");
  const[selTpl,setSelTpl]=useState(null);
  const[selLoc,setSelLoc]=useState(locId||userLocs?.[0]||"");
  const ok = pT(end)>pT(start);

  // Filter locs to only the user's assigned locations
  const userLocList = (locs||[]).filter(l=>userLocs?.includes(l.id));

  return <div onClick={onClose} style={{position:"fixed",inset:0,background:"rgba(0,0,0,.4)",zIndex:500,display:"flex",alignItems:"center",justifyContent:"center",padding:16}}>
    <div onClick={e=>e.stopPropagation()} style={{background:"#fff",borderRadius:12,padding:24,width:"100%",maxWidth:380,boxShadow:"0 20px 60px rgba(0,0,0,.2)"}}>
      <div style={{display:"flex",justifyContent:"space-between",marginBottom:16}}>
        <h3 style={{fontSize:16,fontWeight:600,color:"#111",margin:0}}>{mode==="add"?"Add Shift":"Edit Shift"}</h3>
        <button onClick={onClose} style={{background:"none",border:"none",fontSize:18,color:"#9ca3af",cursor:"pointer"}}>✕</button>
      </div>
      <div style={{background:"#f9fafb",borderRadius:8,padding:12,marginBottom:16,fontSize:13}}>
        <strong>{name}</strong><br/><span style={{color:"#6b7280"}}>{day}</span>
      </div>
      <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Role</div>
      <div style={{display:"flex",gap:8,marginBottom:16}}>
        {(positions||["Stylist"]).map(p =>
          <button key={p} onClick={()=>setRole(p)} style={{flex:1,padding:8,borderRadius:6,fontSize:13,fontWeight:500,cursor:"pointer",
            border:`1.5px solid ${role===p?"#3b82f6":"#e5e7eb"}`,
            background:role===p?"#eff6ff":"#fff",
            color:role===p?"#1d4ed8":"#6b7280"}}>{p}</button>
        )}
      </div>
      {templates?.length>0 && mode==="add" &&
        <div style={{display:"flex",gap:6,marginBottom:12,flexWrap:"wrap"}}>
          {templates.map(t => {
            const active = selTpl===t.id;
            return <button key={t.id} onClick={()=>{setSelTpl(t.id);setStart(apiTime(t.start_time));setEnd(apiTime(t.end_time))}}
              style={{padding:"5px 10px",borderRadius:6,fontSize:11,cursor:"pointer",
                border:`1.5px solid ${active?"#3b82f6":"#e5e7eb"}`,
                background:active?"#eff6ff":"#f9fafb",
                color:active?"#1d4ed8":"#374151",
                fontWeight:active?600:400}}>{t.template_name}</button>;
          })}
        </div>
      }
      {userLocList.length>1 && mode==="add" && <>
        <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Location</div>
        <div style={{display:"flex",gap:8,marginBottom:16}}>
          {userLocList.map(l => {
            const lc = getLC(l.id, locs);
            const sel = selLoc===l.id;
            return <button key={l.id} onClick={()=>setSelLoc(l.id)} style={{flex:1,padding:8,borderRadius:6,fontSize:13,fontWeight:sel?600:500,cursor:"pointer",
              border:`1.5px solid ${sel?lc.pill:"#e5e7eb"}`,
              background:sel?lc.bg:"#fff",
              color:sel?lc.tx:"#6b7280"}}>{l.name}</button>;
          })}
        </div>
      </>}
      <div style={{display:"flex",gap:12,marginBottom:16}}>
        <div style={{flex:1}}>
          <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4}}>Start</div>
          <select value={start} onChange={e=>{setStart(e.target.value);setSelTpl(null)}} style={SI}>{TOPTS.map(t=><option key={t}>{t}</option>)}</select>
        </div>
        <div style={{flex:1}}>
          <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4}}>End</div>
          <select value={end} onChange={e=>{setEnd(e.target.value);setSelTpl(null)}} style={SI}>{TOPTS.map(t=><option key={t}>{t}</option>)}</select>
        </div>
      </div>
      {ok && <div style={{background:"#f0fdf4",borderRadius:6,padding:8,textAlign:"center",fontSize:13,color:"#166534",fontWeight:500,marginBottom:16}}>{start} – {end} · {fmtH(pT(end)-pT(start))}</div>}
      <div style={{display:"flex",gap:8}}>
        <button disabled={!ok} onClick={()=>onSave({start,end,role},selLoc)} style={{...BTN,flex:1,opacity:ok?1:.4}}>{mode==="add"?"Add Shift":"Save"}</button>
        {mode==="edit" && <button onClick={onDelete} style={{...BTN,background:"#fee2e2",color:"#991b1b"}}>Delete</button>}
      </div>
    </div>
  </div>;
}

// ═══ HELP / DOCUMENTATION VIEW ═══
// Comprehensive how-to guide for owners/managers/staff.
// Image placeholders use the <HelpImg> component below — drop real
// screenshots into /static/help/ and replace the placeholders later.
function HelpImg({alt,caption}){
  return <figure style={{margin:"14px 0",border:"1px dashed #d1d5db",borderRadius:8,overflow:"hidden",background:"#fafafa"}}>
    <div style={{padding:"32px 16px",textAlign:"center",color:"#6b7280",fontSize:13,fontStyle:"italic"}}>
      📷 {alt}
    </div>
    {caption && <figcaption style={{padding:"8px 14px",fontSize:12,color:"#9ca3af",borderTop:"1px dashed #e5e7eb",background:"#fff",textAlign:"center"}}>{caption}</figcaption>}
  </figure>;
}

const HelpRole = ({role}) => {
  const colors = {
    owner: {bg:"#dbeafe",tx:"#1e40af",label:"Owner only"},
    manager: {bg:"#f3e8ff",tx:"#7c3aed",label:"Manager+"},
    staff: {bg:"#f3f4f6",tx:"#6b7280",label:"All roles"},
  }[role] || {bg:"#f3f4f6",tx:"#6b7280",label:role};
  return <span style={{fontSize:10,fontWeight:700,padding:"2px 8px",borderRadius:4,background:colors.bg,color:colors.tx,marginLeft:8,verticalAlign:"middle",letterSpacing:.3}}>{colors.label}</span>;
};

const HelpTip = ({children}) => <div style={{background:"#fff7ed",border:"1px solid #fed7aa",borderLeft:"4px solid #f97316",borderRadius:6,padding:"10px 14px",margin:"12px 0",fontSize:13,color:"#9a3412"}}>
  💡 {children}
</div>;

const HelpNote = ({children}) => <div style={{background:"#eff6ff",border:"1px solid #bfdbfe",borderLeft:"4px solid #3b82f6",borderRadius:6,padding:"10px 14px",margin:"12px 0",fontSize:13,color:"#1e40af"}}>
  ℹ️ {children}
</div>;

const HelpSec = ({id,title,role,children}) => <section id={id} style={{marginBottom:36,scrollMarginTop:80}}>
  <h2 style={{fontSize:20,fontWeight:700,color:"#111",borderBottom:"2px solid #e5e7eb",paddingBottom:8,marginBottom:16}}>
    {title}{role && <HelpRole role={role}/>}
  </h2>
  <div style={{fontSize:14,color:"#374151",lineHeight:1.65}}>{children}</div>
</section>;

function HelpView({me,isOwner,isMgr}){
  const TOC = [
    {id:"start",label:"Getting Started"},
    {id:"schedule",label:"The Schedule View"},
    {id:"shifts",label:"Adding & Editing Shifts"},
    {id:"publish",label:"Publishing the Schedule"},
    {id:"auto",label:"✨ Auto-Schedule (AI)"},
    {id:"timeoff",label:"Time-Off Requests"},
    {id:"people",label:"Managing People"},
    {id:"admin",label:"Admin Settings"},
    {id:"perms",label:"Permissions Reference"},
    {id:"best",label:"Best Practices"},
    {id:"contact",label:"Need More Help?"},
  ];
  return <div style={{paddingTop:16,paddingBottom:80}}>
    {/* Header */}
    <div style={{...CD,background:"linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%)",border:"none",color:"#fff",padding:"22px 24px"}}>
      <div style={{fontSize:11,fontWeight:700,letterSpacing:1.5,textTransform:"uppercase",opacity:.85}}>Bruton Intelligence</div>
      <div style={{fontSize:24,fontWeight:700,marginTop:4}}>How-to Guide</div>
      <div style={{fontSize:13,opacity:.9,marginTop:6,maxWidth:560}}>Everything you need to run your salon's scheduling — from the daily basics to AI-generated monthly schedules.</div>
    </div>

    {/* Table of contents */}
    <div style={{...CD,padding:"14px 18px"}}>
      <div style={{fontSize:11,fontWeight:700,color:"#6b7280",textTransform:"uppercase",letterSpacing:.5,marginBottom:8}}>Jump to</div>
      <div style={{display:"flex",flexWrap:"wrap",gap:6}}>
        {TOC.map(t=><a key={t.id} href={`#${t.id}`} style={{fontSize:12,padding:"5px 10px",borderRadius:6,background:"#f3f4f6",color:"#374151",textDecoration:"none",fontWeight:500}}>{t.label}</a>)}
      </div>
    </div>

    {/* ── Getting Started ── */}
    <HelpSec id="start" title="Getting Started" role="staff">
      <p>When your manager creates your account, you'll receive an email from <strong>Bruton Intelligence</strong> with a link to set your password. Click the link, choose a password (at least 8 characters), and you're in.</p>
      <HelpImg alt="Email invitation showing 'Set Your Password' button" caption="Welcome email from Bruton Intelligence"/>
      <p><strong>Forgot your password?</strong> Click "Forgot password?" on the sign-in screen and enter your email. We'll send you a reset link. The link expires after one hour.</p>
      <p><strong>Your role</strong> determines what you can do. You'll see one of three badges next to your name in the header:</p>
      <ul style={{marginLeft:20,marginTop:6}}>
        <li><strong>Owner</strong> — you set up the franchise, can manage everything across all locations</li>
        <li><strong>Manager</strong> — you can manage schedules, users, and time-off at your assigned locations</li>
        <li><strong>Staff</strong> — you can view your schedule and request time off</li>
      </ul>
      <HelpTip>Show or hide your password by clicking the 👁 eye icon in the password field on the sign-in screen.</HelpTip>
    </HelpSec>

    {/* ── Schedule view ── */}
    <HelpSec id="schedule" title="The Schedule View" role="staff">
      <p>The Schedule tab is the heart of the app. You can view it as a calendar (week-at-a-glance grid) or a list (compact daily breakdown).</p>
      <HelpImg alt="Weekly calendar view showing stylists down the left, days across the top, with shift pills in each cell" caption="Calendar view"/>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Navigating weeks</h3>
      <ul style={{marginLeft:20}}>
        <li>Use the <strong>‹ ›</strong> arrows above the calendar to step backwards/forwards one week at a time</li>
        <li>Click <strong>Today</strong> to jump back to the current week</li>
        <li>Use the <strong>date picker</strong> to jump to a specific date</li>
      </ul>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Reading the colors</h3>
      <ul style={{marginLeft:20}}>
        <li><strong>Solid colored pills</strong> — published shifts visible to staff</li>
        <li><strong>Hatched / dashed pills</strong> — draft shifts (managers only see these until published)</li>
        <li><strong>Red CLOSED days</strong> — the salon is closed (no shifts can be scheduled)</li>
        <li><strong>Gray "OFF" or "REQ" badges</strong> — approved or pending time-off</li>
        <li><strong>Different colors per location</strong> — Fort Worth might be blue, Arlington pink, etc. — helps multi-location stylists see at a glance which store they're assigned to</li>
      </ul>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Filtering by location</h3>
      <p>The <strong>"All Locations"</strong> dropdown in the top-right header lets you switch between viewing all locations or just one. Owners see this; managers see only their assigned locations.</p>
    </HelpSec>

    {/* ── Adding/editing shifts ── */}
    <HelpSec id="shifts" title="Adding & Editing Shifts" role="manager">
      <p>Building the schedule is mostly clicking and dragging.</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:14,marginBottom:8}}>Add a new shift</h3>
      <ol style={{marginLeft:20}}>
        <li>Click an empty cell on the calendar (where you see a <strong>+</strong> placeholder)</li>
        <li>The <strong>Add Shift</strong> modal opens, pre-filled with the stylist and date</li>
        <li>Pick a <strong>Role</strong> (Stylist or Receptionist if applicable)</li>
        <li>Optional: click a <strong>Template</strong> chip (Full Day, Half Day, etc.) to auto-fill times</li>
        <li>If the stylist works at multiple locations, pick which <strong>Location</strong> the shift is at</li>
        <li>Adjust <strong>Start</strong> and <strong>End</strong> times if needed</li>
        <li>Click <strong>Add Shift</strong></li>
      </ol>
      <HelpImg alt="Add Shift modal showing stylist name, date, role buttons, template chips, location buttons, and start/end time selectors" caption="Adding a shift"/>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Edit an existing shift</h3>
      <p>Click any existing shift pill on the calendar — same modal opens, but with a <strong>Delete</strong> button alongside <strong>Save</strong>.</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Drag to move</h3>
      <p>You can drag any shift to a different day in the same week. Hold and drop on the target day. If the stylist already has a shift on that target day, the two shifts swap.</p>
      <HelpNote>Editing a published shift automatically sets it back to <strong>DRAFT</strong>. You'll need to re-publish to push the change to staff.</HelpNote>
    </HelpSec>

    {/* ── Publishing ── */}
    <HelpSec id="publish" title="Publishing the Schedule" role="manager">
      <p>Shifts have two states: <strong>DRAFT</strong> (only managers see them) and <strong>PUBLISHED</strong> (staff sees them too). This lets you build out a full schedule without staff seeing it half-finished.</p>
      <HelpImg alt="Yellow draft banner saying 'Unpublished changes' with a Publish button on the right" caption="Draft banner appears when you have unpublished shifts"/>
      <p><strong>To publish</strong>: scroll below the calendar grid. You'll see a yellow banner saying <em>"Unpublished changes"</em> with a <strong>Publish</strong> button. Click it. All draft shifts in the current week are pushed to PUBLISHED status.</p>
      <p><strong>To unpublish</strong> (rare — useful if you need to make widespread changes): click the green <em>"Published"</em> banner's <strong>Unpublish</strong> button to revert all shifts in the week back to DRAFT.</p>
      <HelpTip>Get into the habit of publishing weekly, on the same day. Staff will learn when to expect their schedule to drop.</HelpTip>
    </HelpSec>

    {/* ── Auto-Schedule ── */}
    <HelpSec id="auto" title="✨ Auto-Schedule (AI)" role="manager">
      <p>The Auto-Schedule feature is what makes Bruton Intelligence different from every other scheduling tool. It generates a complete monthly schedule from your last 13 months of POS data — services delivered, busy days, slow days — and respects your stylists' hour caps, time-off requests, and custom rules.</p>
      <HelpImg alt="Schedule view with a purple-gradient '✨ Auto-Schedule' button next to the Calendar/List toggle" caption="The Auto-Schedule button on the Schedule tab"/>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>When to use it</h3>
      <p>The 15th–20th of each month is the sweet spot — you generate next month's schedule, review it, tweak a few things, and have it published a week before the month starts. You can also re-run mid-month if circumstances change drastically.</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Generating a schedule</h3>
      <ol style={{marginLeft:20}}>
        <li>Click <strong>✨ Auto-Schedule</strong> on the Schedule tab</li>
        <li>Pick the <strong>Month</strong> (defaults to next month) and <strong>Location</strong> (defaults to whatever location you have selected)</li>
        <li>Click <strong>✨ Generate [Month] Schedule</strong></li>
        <li>The AI analyzes your POS history and proposes a schedule in ~3 seconds</li>
      </ol>
      <HelpImg alt="Auto-Schedule modal showing a proposal with 4 stat cards (shifts proposed, stylists scheduled, total hours, demand filled %), a per-stylist hours table, and a daily coverage heatmap" caption="The proposal review screen"/>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Reviewing the proposal</h3>
      <p>The proposal shows you four key things at the top:</p>
      <ul style={{marginLeft:20}}>
        <li><strong>Shifts proposed</strong> — total number of shifts the AI built</li>
        <li><strong>Stylists scheduled</strong> — how many of your active stylists got shifts</li>
        <li><strong>Total scheduled</strong> — sum of all shift hours across the month</li>
        <li><strong>Demand filled %</strong> — what fraction of forecast demand the proposal covers (95%+ green, 85-94% amber, &lt;85% red)</li>
      </ul>
      <p>Below that, you'll see:</p>
      <ul style={{marginLeft:20}}>
        <li>A <strong>per-stylist hours table</strong> — each stylist's monthly total vs their target. Green = on target, amber = a bit low, red = over the cap.</li>
        <li>A <strong>daily coverage heatmap</strong> — color-coded grid showing forecast demand vs assigned hours per day.</li>
        <li>A <strong>warnings panel</strong> — flagged days where demand exceeds available stylist capacity.</li>
      </ul>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Save or discard</h3>
      <p>If the proposal looks good, click <strong>Save as Drafts →</strong>. All shifts get inserted into the calendar as DRAFT — you can edit individual shifts before publishing.</p>
      <p>If something's off, click <strong>← Back</strong> to tweak the inputs and re-generate, or <strong>Discard</strong> to throw it away.</p>
      <HelpTip><strong>Demo mode:</strong> The expandable "Demo mode (advanced)" section lets you preview the schedule for any other store in the chain by entering its POS site ID. Useful for showing the system to other franchise owners. Save is disabled in demo mode (you can't write data to a store you don't own).</HelpTip>
      <HelpNote>For Auto-Schedule to work well, your stylists need to be linked to their POS records. Open the People tab, edit each stylist, and pick their match from the <strong>Link to POS Employee</strong> dropdown. Without this link, that stylist's historical service patterns can't be used.</HelpNote>
    </HelpSec>

    {/* ── Time off ── */}
    <HelpSec id="timeoff" title="Time-Off Requests" role="staff">
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:6,marginBottom:8}}>Requesting time off (staff)</h3>
      <ol style={{marginLeft:20}}>
        <li>Open the <strong>Time Off</strong> tab</li>
        <li>Click <strong>+ Request</strong></li>
        <li>Pick a <strong>Start</strong> and (optional) <strong>End</strong> date</li>
        <li>Choose a <strong>Reason</strong> (Vacation, Personal, Medical, Family)</li>
        <li>Click <strong>Submit</strong></li>
      </ol>
      <p>You'll see your request appear with a yellow PENDING badge. Once your manager approves or denies it, the badge changes color and a notification appears on the schedule.</p>
      <HelpImg alt="Time-off request form with date pickers and reason dropdown" caption="Submitting a time-off request"/>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Approving requests <HelpRole role="manager"/></h3>
      <p>Managers see all pending requests at the top of the Time Off tab. Click <strong>Approve</strong> to grant the request (the schedule grid will mark those days "OFF" for that stylist), or <strong>Deny</strong> to reject. Denying prompts you for a reason that's emailed to the staff member.</p>
      <HelpNote>Time-off requests that overlap a <strong>blackout period</strong> are rejected automatically. Set blackouts in the Admin tab (peak season, holidays, etc.).</HelpNote>
    </HelpSec>

    {/* ── People ── */}
    <HelpSec id="people" title="Managing People" role="manager">
      <p>The People tab is where you add, edit, and manage your team.</p>
      <HelpImg alt="People tab showing a list of team members with role/employment type/POS link badges and edit/resend/deactivate buttons on the right" caption="People tab"/>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Adding a team member</h3>
      <ol style={{marginLeft:20}}>
        <li>Click <strong>+ Add</strong> in the People tab</li>
        <li>Enter <strong>First name</strong>, <strong>Last name</strong>, <strong>Email</strong> (required for login)</li>
        <li>Pick <strong>Employment Type</strong> (Full-Time or Part-Time)</li>
        <li>Pick their <strong>Schedule Position(s)</strong>: Stylist, Receptionist, or both. Leave blank if they don't get scheduled (e.g., your accountant).</li>
        <li>Pick their <strong>Location(s)</strong> — at least one required. Multi-location staff can be assigned to several.</li>
        <li><strong>Link to POS Employee</strong> — pick their match from the dropdown so Auto-Schedule can use their history</li>
        <li>Owners only: pick <strong>Role</strong> (Staff or Manager)</li>
        <li>Click <strong>Add Team Member</strong> — they'll receive an invite email immediately</li>
      </ol>
      <HelpTip>The POS Employee dropdown auto-suggests matches by name. If you see someone you don't recognize, hover over the entry — it shows last-seen date and total days worked at the location.</HelpTip>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>POS link indicator</h3>
      <p>Each team member's row shows either a <strong>green "POS ✓" badge</strong> (linked) or an <strong>amber "POS ⚠" badge</strong> (not linked). Unlinked stylists get excluded from Auto-Schedule's history-based assignments.</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:18,marginBottom:8}}>Action buttons (right side of each row)</h3>
      <ul style={{marginLeft:20}}>
        <li>✎ <strong>Edit</strong> — change name, locations, positions, role, or POS link</li>
        <li>✉ <strong>Resend invite</strong> — sends a fresh password reset email (useful if they lost the original)</li>
        <li>⏸ <strong>Deactivate</strong> — removes from active scheduling but keeps history. Their login is also temporarily banned.</li>
        <li>↺ <strong>Reactivate</strong> — restores a deactivated user</li>
        <li>✕ <strong>Delete</strong> — permanently removes the user and their account. Cannot be undone.</li>
      </ul>
    </HelpSec>

    {/* ── Admin ── */}
    <HelpSec id="admin" title="Admin Settings" role="manager">
      <p>The Admin tab has six sub-tabs. Each controls a different aspect of how scheduling works.</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:14,marginBottom:8}}>Schedule Templates</h3>
      <p>Reusable shift presets that show as quick-pick chips when adding a shift. Common examples: <em>"Full Day"</em> (9:50a–6:00p), <em>"Half Day"</em> (12:50p–6:00p). Add a template once, use it forever.</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:14,marginBottom:8}}>Rules</h3>
      <p>Configurable scheduling constraints used by Auto-Schedule. Five types:</p>
      <ul style={{marginLeft:20}}>
        <li><strong>WEEKLY_HOURS_RANGE</strong> — e.g., "Part-time should be 15–25 hours/week" (param_1=min, param_2=max)</li>
        <li><strong>MAX_CONSECUTIVE_DAYS</strong> — e.g., "Audrey max 3 consecutive days" (param_1=days)</li>
        <li><strong>WEEKEND_OFF_PER_MONTH</strong> — e.g., "Try for 1 Saturday and 1 Sunday off each month" (param_1=Sat, param_2=Sun)</li>
        <li><strong>SHIFT_START_OFFSET</strong> — e.g., "Shifts start 10 minutes before opening" (param_1=offset minutes)</li>
        <li><strong>CUSTOM</strong> — free-form rules; description is read by the AI when polishing schedules. e.g., "Lanore works full shifts but only Tue/Wed/Sat"</li>
      </ul>
      <p>Each rule can be scoped to all locations (default) or one specific location, and to all employees or one specific user.</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:14,marginBottom:8}}>Closed Dates</h3>
      <p>Single days the salon is shut (holidays, deep cleaning, training). Schedule cells turn red and shifts can't be created on these dates. Optional location scope.</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:14,marginBottom:8}}>Blackouts</h3>
      <p>Date ranges where time-off requests are blocked. Doesn't close the salon — just rejects new time-off submissions during that range. Use for peak season (back-to-school, holiday rush) or critical events.</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:14,marginBottom:8}}>Announcements</h3>
      <p>Messages that appear on the Overview tab for everyone. Good for "Holiday party Saturday Dec 14!" or "Reminder: dress code starts Monday."</p>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:14,marginBottom:8}}>Business Hours</h3>
      <p>Per-location, per-day-of-week open and close times. Auto-Schedule uses these to set default shift bounds.</p>
    </HelpSec>

    {/* ── Permissions ── */}
    <HelpSec id="perms" title="Permissions Reference" role="staff">
      <p>Quick reference for what each role can do.</p>
      <div style={{overflowX:"auto"}}>
        <table style={{width:"100%",borderCollapse:"collapse",fontSize:13,marginTop:10}}>
          <thead>
            <tr style={{background:"#f9fafb",borderBottom:"2px solid #e5e7eb"}}>
              <th style={{textAlign:"left",padding:"10px 12px",fontWeight:600,color:"#374151"}}>Action</th>
              <th style={{textAlign:"center",padding:"10px 12px",fontWeight:600,color:"#374151"}}>Staff</th>
              <th style={{textAlign:"center",padding:"10px 12px",fontWeight:600,color:"#374151"}}>Manager</th>
              <th style={{textAlign:"center",padding:"10px 12px",fontWeight:600,color:"#374151"}}>Owner</th>
            </tr>
          </thead>
          <tbody>
            {[
              ["View their own published shifts","✓","✓","✓"],
              ["Request time off","✓","✓","✓"],
              ["View entire weekly schedule (own location)","Published only","✓","✓"],
              ["Add/edit/delete shifts","","✓","✓"],
              ["Publish/unpublish weekly schedule","","✓","✓"],
              ["Drag shifts to reorder","","✓","✓"],
              ["Approve/deny time-off requests","","✓","✓"],
              ["Add/edit team members","","✓","✓"],
              ["Promote staff to manager / change roles","","","✓"],
              ["Run Auto-Schedule (AI)","","✓","✓"],
              ["Edit Admin settings (rules, templates, etc.)","","✓","✓"],
              ["See all locations","","Their location(s)","✓"],
              ["Resend invite emails","","✓","✓"],
              ["Permanently delete users","","✓","✓"],
            ].map((row,i)=><tr key={i} style={{borderBottom:"1px solid #f3f4f6"}}>
              <td style={{padding:"8px 12px",color:"#374151"}}>{row[0]}</td>
              <td style={{padding:"8px 12px",textAlign:"center",color:row[1]==="✓"?"#10b981":"#9ca3af"}}>{row[1]||"—"}</td>
              <td style={{padding:"8px 12px",textAlign:"center",color:row[2]==="✓"?"#10b981":"#9ca3af"}}>{row[2]||"—"}</td>
              <td style={{padding:"8px 12px",textAlign:"center",color:row[3]==="✓"?"#10b981":"#9ca3af"}}>{row[3]||"—"}</td>
            </tr>)}
          </tbody>
        </table>
      </div>
    </HelpSec>

    {/* ── Best practices ── */}
    <HelpSec id="best" title="Best Practices" role="manager">
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:6,marginBottom:8}}>Onboarding new staff</h3>
      <ol style={{marginLeft:20}}>
        <li>Create their user in the People tab — they'll get an invite email</li>
        <li>Have them set their password from the email</li>
        <li>Once they sign in, they should bookmark the live URL and add it to their phone home screen</li>
        <li>Walk them through the Schedule view and Time Off tab — most staff only ever use those two</li>
      </ol>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:14,marginBottom:8}}>Monthly scheduling cadence</h3>
      <ol style={{marginLeft:20}}>
        <li><strong>15th–20th of the month:</strong> open Auto-Schedule, generate the next month's draft</li>
        <li>Review the proposal — check that hour totals look right per stylist, no warnings on critical days</li>
        <li>Save as Drafts</li>
        <li>Review individual shifts in the calendar — drag/edit as needed for known requests</li>
        <li><strong>~7 days before month-end:</strong> publish the new month's schedule</li>
        <li>Communicate publication to staff (text, post in break room, etc.)</li>
      </ol>
      <h3 style={{fontSize:15,fontWeight:600,color:"#111",marginTop:14,marginBottom:8}}>Common pitfalls</h3>
      <ul style={{marginLeft:20}}>
        <li>Forgetting to <strong>link new hires to POS</strong> means Auto-Schedule won't include them in history-based logic</li>
        <li>Setting blackouts too aggressively can frustrate staff — use them only for genuinely critical periods</li>
        <li>Publishing too late in the month leaves staff scrambling to plan their lives — publish early</li>
        <li>Never <strong>permanently delete</strong> a user with historical shifts unless you're sure — deactivate instead, which preserves history</li>
      </ul>
    </HelpSec>

    {/* ── Contact ── */}
    <HelpSec id="contact" title="Need More Help?" role="staff">
      <p>Reach out and we'll help you sort it out.</p>
      <ul style={{marginLeft:20}}>
        <li>Email: <a href="mailto:cdbruton@gmail.com" style={{color:"#3b82f6"}}>cdbruton@gmail.com</a></li>
        <li>For questions about your franchise's specific setup, contact your franchise owner first</li>
      </ul>
      <div style={{textAlign:"center",padding:24,color:"#9ca3af",fontSize:11,marginTop:24,borderTop:"1px solid #e5e7eb"}}>
        Bruton Intelligence · v3.0
      </div>
    </HelpSec>
  </div>;
}


// ═══ PLATFORM ADMIN VIEW ═══
// Cross-franchise dashboard for Bruton Intelligence platform admins.
// Shows every franchise with summary stats, lets you switch active scope,
// and onboards new franchises.
function PlatformView({me, activeFranchiseId, setActiveFranchiseId, tt, fetchAll}) {
  const[franchises,setFranchises]=useState([]);
  const[loading,setLoading]=useState(true);
  const[showCreate,setShowCreate]=useState(false);
  const[creating,setCreating]=useState(false);
  const[editingFranchise,setEditingFranchise]=useState(null);  // franchise object being edited
  const[newF,setNewF]=useState({
    name:"", slug:"", plan_tier:"starter",
    owner_email:"", owner_first_name:"", owner_last_name:"",
    location_name:"", location_city:"", location_state:"", location_pos_site_id:"",
  });

  const loadFranchises = async () => {
    setLoading(true);
    try {
      const r = await apiFetch("/api/scheduling/v3/platform/franchises");
      setFranchises(r);
    } catch(e) { tt(e.message); }
    finally { setLoading(false); }
  };
  useEffect(() => { loadFranchises(); }, []);

  const switchTo = (fid) => {
    if(fid === me?.franchise_id) {
      setActiveFranchiseId(null);   // back to home — clear override
    } else {
      setActiveFranchiseId(fid);
    }
    tt("Switched franchise — reloading...");
    // Reload all data so views reflect the new franchise scope
    setTimeout(() => { fetchAll(); }, 100);
  };

  const createFranchise = async () => {
    if(!newF.name.trim() || !newF.owner_email.trim() || !newF.owner_first_name.trim() || !newF.owner_last_name.trim()) {
      tt("Fill in franchise name and owner details");
      return;
    }
    setCreating(true);
    try {
      const body = {
        name: newF.name.trim(),
        slug: newF.slug.trim() || undefined,
        plan_tier: newF.plan_tier,
        owner_email: newF.owner_email.trim(),
        owner_first_name: newF.owner_first_name.trim(),
        owner_last_name: newF.owner_last_name.trim(),
        locations: newF.location_name.trim() ? [{
          name: newF.location_name.trim(),
          city: newF.location_city.trim() || null,
          state: newF.location_state.trim() || null,
          pos_site_id: newF.location_pos_site_id.trim() || null,
        }] : [],
      };
      const r = await apiFetch("/api/scheduling/v3/platform/franchises", {
        method: "POST", body: JSON.stringify(body),
      });
      tt(r.message || "Franchise created");
      setShowCreate(false);
      setNewF({name:"",slug:"",plan_tier:"starter",owner_email:"",owner_first_name:"",owner_last_name:"",location_name:"",location_city:"",location_state:"",location_pos_site_id:""});
      await loadFranchises();
    } catch(e) { tt(e.message); }
    finally { setCreating(false); }
  };

  return <div style={{paddingTop:16,paddingBottom:80}}>
    {/* Header */}
    <div style={{...CD,background:"linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%)",border:"none",color:"#fff",padding:"22px 24px"}}>
      <div style={{fontSize:11,fontWeight:700,letterSpacing:1.5,textTransform:"uppercase",opacity:.85}}>Bruton Intelligence</div>
      <div style={{fontSize:22,fontWeight:700,marginTop:4}}>Platform Admin</div>
      <div style={{fontSize:13,opacity:.9,marginTop:6}}>Cross-franchise operations — onboarding, support, troubleshooting.</div>
    </div>

    {/* Acting-as banner */}
    {activeFranchiseId && activeFranchiseId !== me?.franchise_id && (()=>{
      const af = franchises.find(f=>f.id===activeFranchiseId);
      return <div style={{...CD,background:"#fef3c7",borderColor:"#fcd34d",borderLeft:"4px solid #f59e0b",padding:"12px 16px"}}>
        <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",gap:12}}>
          <div>
            <div style={{fontWeight:600,fontSize:13,color:"#92400e"}}>Acting as: {af?.name || activeFranchiseId}</div>
            <div style={{fontSize:11,color:"#78350f"}}>You're viewing and editing this franchise's data. Switch back when you're done.</div>
          </div>
          <button onClick={()=>switchTo(me?.franchise_id)} style={{padding:"7px 14px",borderRadius:6,border:"1px solid #d97706",background:"#fff",color:"#92400e",fontSize:12,fontWeight:600,cursor:"pointer",whiteSpace:"nowrap"}}>← Back to my franchise</button>
        </div>
      </div>;
    })()}

    {/* Action bar */}
    <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:14}}>
      <h3 style={{...SH,margin:0}}>Franchises ({franchises.length})</h3>
      <button onClick={()=>setShowCreate(!showCreate)} style={{...BTN,padding:"7px 14px",fontSize:13,background:showCreate?"#f3f4f6":undefined,color:showCreate?"#374151":undefined}}>
        {showCreate?"Cancel":"+ New Franchise"}
      </button>
    </div>

    {/* Create-franchise form */}
    {showCreate && <div style={{...CD,borderColor:"#3b82f6",background:"#f0f7ff"}}>
      <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Franchise Name *</div>
      <input style={{...SI,marginBottom:10}} placeholder="e.g. Cookie Cutters Houston" value={newF.name} onChange={e=>setNewF({...newF,name:e.target.value})}/>
      <div style={{display:"flex",gap:10,marginBottom:10}}>
        <div style={{flex:1}}>
          <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Slug <span style={{fontWeight:400,textTransform:"none",color:"#9ca3af"}}>(optional)</span></div>
          <input style={SI} placeholder="auto-generated from name" value={newF.slug} onChange={e=>setNewF({...newF,slug:e.target.value})}/>
        </div>
        <div style={{flex:1}}>
          <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Plan Tier</div>
          <select style={SI} value={newF.plan_tier} onChange={e=>setNewF({...newF,plan_tier:e.target.value})}>
            <option value="starter">Starter</option>
            <option value="growth">Growth</option>
            <option value="enterprise">Enterprise</option>
          </select>
        </div>
      </div>
      <div style={{fontSize:11,fontWeight:700,color:"#6b7280",textTransform:"uppercase",letterSpacing:.5,marginTop:14,marginBottom:6,borderTop:"1px solid #d1d5db",paddingTop:14}}>First Owner</div>
      <div style={{display:"flex",gap:10,marginBottom:10}}>
        <div style={{flex:1}}>
          <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>First Name *</div>
          <input style={SI} placeholder="Jane" value={newF.owner_first_name} onChange={e=>setNewF({...newF,owner_first_name:e.target.value})}/>
        </div>
        <div style={{flex:1}}>
          <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Last Name *</div>
          <input style={SI} placeholder="Smith" value={newF.owner_last_name} onChange={e=>setNewF({...newF,owner_last_name:e.target.value})}/>
        </div>
      </div>
      <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Owner Email *</div>
      <input style={{...SI,marginBottom:10}} type="email" placeholder="jane@example.com" value={newF.owner_email} onChange={e=>setNewF({...newF,owner_email:e.target.value})}/>
      <div style={{fontSize:11,fontWeight:700,color:"#6b7280",textTransform:"uppercase",letterSpacing:.5,marginTop:14,marginBottom:6,borderTop:"1px solid #d1d5db",paddingTop:14}}>First Location <span style={{fontWeight:400,textTransform:"none",color:"#9ca3af"}}>(optional — owner can add later)</span></div>
      <input style={{...SI,marginBottom:10}} placeholder="Location name (e.g. Houston Galleria)" value={newF.location_name} onChange={e=>setNewF({...newF,location_name:e.target.value})}/>
      <div style={{display:"flex",gap:10,marginBottom:14}}>
        <input style={{...SI,flex:2}} placeholder="City" value={newF.location_city} onChange={e=>setNewF({...newF,location_city:e.target.value})}/>
        <input style={{...SI,flex:1}} placeholder="State (TX)" maxLength={2} value={newF.location_state} onChange={e=>setNewF({...newF,location_state:e.target.value.toUpperCase()})}/>
        <input style={{...SI,flex:1}} placeholder="POS Site ID" value={newF.location_pos_site_id} onChange={e=>setNewF({...newF,location_pos_site_id:e.target.value})}/>
      </div>
      <button onClick={createFranchise} disabled={creating} style={{...BTN,width:"100%",padding:"12px",opacity:creating?.7:1,background:"linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%)"}}>
        {creating?"Creating + sending invite...":"Create Franchise"}
      </button>
      <div style={{fontSize:11,color:"#9ca3af",marginTop:8,textAlign:"center"}}>The owner will receive an invite email immediately and can sign in once they set their password.</div>
    </div>}

    {/* Franchises list */}
    {loading ? <div style={{textAlign:"center",padding:40,color:"#9ca3af"}}>Loading franchises...</div>
    : franchises.length === 0 ? <div style={{textAlign:"center",padding:40,color:"#9ca3af"}}>No franchises yet. Click "+ New Franchise" to add the first one.</div>
    : <>{editingFranchise && <FranchiseEditModal
        franchise={editingFranchise}
        tt={tt}
        onClose={()=>setEditingFranchise(null)}
        onSaved={async()=>{ await loadFranchises(); /* keep modal open so they can keep editing */ }}
      />}{franchises.map(f => {
      const isHome = f.id === me?.franchise_id;
      const isActive = activeFranchiseId === f.id || (!activeFranchiseId && isHome);
      return <div key={f.id} style={{...CD,borderLeft:isActive?"4px solid #7c3aed":"4px solid #e5e7eb",position:"relative"}}>
        <div style={{display:"flex",justifyContent:"space-between",alignItems:"flex-start",gap:12}}>
          <div style={{flex:1,minWidth:0}}>
            <div style={{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"}}>
              <span style={{fontWeight:700,fontSize:15,color:"#111"}}>{f.name}</span>
              <span style={{fontSize:10,fontWeight:600,padding:"2px 8px",borderRadius:4,background:f.status==="active"?"#d1fae5":"#fee2e2",color:f.status==="active"?"#065f46":"#991b1b",textTransform:"uppercase"}}>{f.status}</span>
              <span style={{fontSize:10,fontWeight:600,padding:"2px 8px",borderRadius:4,background:"#e0e7ff",color:"#3730a3",textTransform:"uppercase"}}>{f.plan_tier}</span>
              {isHome && <span style={{fontSize:10,fontWeight:600,padding:"2px 8px",borderRadius:4,background:"#dbeafe",color:"#1e40af"}}>YOUR HOME</span>}
              {isActive && !isHome && <span style={{fontSize:10,fontWeight:600,padding:"2px 8px",borderRadius:4,background:"#fef3c7",color:"#92400e"}}>ACTING AS</span>}
            </div>
            <div style={{fontSize:11,color:"#9ca3af",marginTop:4,fontFamily:"monospace"}}>{f.slug}</div>
            <div style={{display:"flex",gap:14,marginTop:8,fontSize:12,color:"#6b7280",flexWrap:"wrap"}}>
              <span>👥 {f.user_count} {f.user_count===1?"user":"users"}</span>
              <span>📍 {f.location_count} {f.location_count===1?"location":"locations"}</span>
              <span>👑 {f.active_owner_count} owner{f.active_owner_count===1?"":"s"}</span>
              {f.last_shift_date && <span>📅 last shift {f.last_shift_date}</span>}
            </div>
          </div>
          <div style={{flexShrink:0,display:"flex",gap:6,flexDirection:"column",alignItems:"flex-end"}}>
            <button onClick={()=>setEditingFranchise(f)} style={{padding:"5px 10px",borderRadius:6,border:"1px solid #d1d5db",background:"#fff",color:"#374151",fontSize:11,fontWeight:600,cursor:"pointer"}}>✎ Edit</button>
            {!isActive && <button onClick={()=>switchTo(f.id)} style={{padding:"7px 12px",borderRadius:6,border:"1px solid #7c3aed",background:"#fff",color:"#7c3aed",fontSize:12,fontWeight:600,cursor:"pointer"}}>Switch into →</button>}
            {isActive && !isHome && <span style={{fontSize:11,color:"#92400e",fontWeight:600}}>You're here</span>}
          </div>
        </div>
      </div>;
    })}</>}
  </div>;
}


// ═══ FRANCHISE EDIT MODAL ═══
// Platform-admin tool for editing franchise details and managing locations.
// Locations are the billing unit, so only platform admins can add/remove them.
function FranchiseEditModal({franchise, tt, onClose, onSaved}) {
  const[f,setF]=useState({
    name: franchise.name || "",
    slug: franchise.slug || "",
    plan_tier: franchise.plan_tier || "starter",
    status: franchise.status || "active",
  });
  const[locations,setLocations]=useState([]);
  const[loading,setLoading]=useState(true);
  const[saving,setSaving]=useState(false);
  const[showAddLoc,setShowAddLoc]=useState(false);
  const[newLoc,setNewLoc]=useState({name:"",city:"",state:"",pos_site_id:"",address:"",zip:""});
  const[editingLocId,setEditingLocId]=useState(null);
  const[editLoc,setEditLoc]=useState({});

  const loadLocations = async () => {
    setLoading(true);
    try {
      const r = await apiFetch(`/api/scheduling/v3/platform/franchises/${franchise.id}/locations?include_inactive=true`);
      setLocations(r);
    } catch(e) { tt(e.message); }
    finally { setLoading(false); }
  };
  useEffect(() => { loadLocations(); }, [franchise.id]);

  const saveFranchise = async () => {
    setSaving(true);
    try {
      await apiFetch(`/api/scheduling/v3/platform/franchises/${franchise.id}`, {
        method: "PUT",
        body: JSON.stringify({
          name: f.name.trim() || null,
          slug: f.slug.trim() || null,
          plan_tier: f.plan_tier,
          status: f.status,
        }),
      });
      tt("Franchise saved");
      onSaved && onSaved();
    } catch(e) { tt(e.message); }
    finally { setSaving(false); }
  };

  const addLocation = async () => {
    if(!newLoc.name.trim()) { tt("Enter a location name"); return; }
    try {
      await apiFetch(`/api/scheduling/v3/platform/franchises/${franchise.id}/locations`, {
        method: "POST",
        body: JSON.stringify({
          name: newLoc.name.trim(),
          pos_site_id: newLoc.pos_site_id.trim() || null,
          address: newLoc.address.trim() || null,
          city: newLoc.city.trim() || null,
          state: newLoc.state.trim() || null,
          zip: newLoc.zip.trim() || null,
        }),
      });
      tt("Location added");
      setShowAddLoc(false);
      setNewLoc({name:"",city:"",state:"",pos_site_id:"",address:"",zip:""});
      await loadLocations();
      onSaved && onSaved();
    } catch(e) { tt(e.message); }
  };

  const startEditLoc = (loc) => {
    setEditingLocId(loc.id);
    setEditLoc({
      name: loc.name || "",
      pos_site_id: loc.pos_site_id || "",
      address: loc.address || "",
      city: loc.city || "",
      state: loc.state || "",
      zip: loc.zip || "",
      is_active: loc.is_active,
    });
  };

  const saveEditLoc = async () => {
    try {
      await apiFetch(`/api/scheduling/v3/platform/franchises/${franchise.id}/locations/${editingLocId}`, {
        method: "PUT",
        body: JSON.stringify({
          name: editLoc.name.trim() || null,
          pos_site_id: editLoc.pos_site_id.trim() || null,
          address: editLoc.address.trim() || null,
          city: editLoc.city.trim() || null,
          state: editLoc.state.trim() || null,
          zip: editLoc.zip.trim() || null,
          is_active: editLoc.is_active,
        }),
      });
      tt("Location saved");
      setEditingLocId(null);
      await loadLocations();
      onSaved && onSaved();
    } catch(e) { tt(e.message); }
  };

  const deactivateLoc = async (locId) => {
    if(!confirm("Deactivate this location? Historical shifts stay intact but no new shifts can be created.")) return;
    try {
      await apiFetch(`/api/scheduling/v3/platform/franchises/${franchise.id}/locations/${locId}`, {
        method: "DELETE",
      });
      tt("Location deactivated");
      await loadLocations();
      onSaved && onSaved();
    } catch(e) { tt(e.message); }
  };

  const reactivateLoc = async (locId) => {
    try {
      await apiFetch(`/api/scheduling/v3/platform/franchises/${franchise.id}/locations/${locId}`, {
        method: "PUT",
        body: JSON.stringify({is_active: true}),
      });
      tt("Location reactivated");
      await loadLocations();
      onSaved && onSaved();
    } catch(e) { tt(e.message); }
  };

  return <div onClick={onClose} style={{position:"fixed",inset:0,background:"rgba(0,0,0,.5)",zIndex:700,display:"flex",alignItems:"center",justifyContent:"center",padding:16,overflow:"auto"}}>
    <div onClick={e=>e.stopPropagation()} style={{background:"#fff",borderRadius:14,width:"100%",maxWidth:720,maxHeight:"92vh",boxShadow:"0 30px 80px rgba(0,0,0,.3)",overflow:"hidden",display:"flex",flexDirection:"column"}}>
      <div style={{padding:"18px 24px",background:"linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%)",color:"#fff",display:"flex",justifyContent:"space-between",alignItems:"center"}}>
        <div>
          <div style={{fontSize:11,fontWeight:700,letterSpacing:1.5,textTransform:"uppercase",opacity:.85}}>Edit Franchise</div>
          <div style={{fontSize:18,fontWeight:700,marginTop:2}}>{franchise.name}</div>
        </div>
        <button onClick={onClose} style={{background:"rgba(255,255,255,.2)",border:"none",color:"#fff",width:30,height:30,borderRadius:6,fontSize:16,cursor:"pointer"}}>✕</button>
      </div>

      <div style={{padding:24,overflowY:"auto",flex:1}}>
        {/* Franchise details */}
        <h3 style={{...SH,marginBottom:10}}>Details</h3>
        <div style={{display:"flex",gap:10,marginBottom:10}}>
          <div style={{flex:2}}>
            <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Name</div>
            <input style={SI} value={f.name} onChange={e=>setF({...f,name:e.target.value})}/>
          </div>
          <div style={{flex:1}}>
            <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Slug</div>
            <input style={SI} value={f.slug} onChange={e=>setF({...f,slug:e.target.value})}/>
          </div>
        </div>
        <div style={{display:"flex",gap:10,marginBottom:12}}>
          <div style={{flex:1}}>
            <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Plan Tier</div>
            <select style={SI} value={f.plan_tier} onChange={e=>setF({...f,plan_tier:e.target.value})}>
              <option value="starter">Starter</option>
              <option value="growth">Growth</option>
              <option value="enterprise">Enterprise</option>
            </select>
          </div>
          <div style={{flex:1}}>
            <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Status</div>
            <select style={SI} value={f.status} onChange={e=>setF({...f,status:e.target.value})}>
              <option value="active">Active</option>
              <option value="suspended">Suspended</option>
              <option value="cancelled">Cancelled</option>
            </select>
          </div>
        </div>
        <button onClick={saveFranchise} disabled={saving} style={{...BTN,padding:"8px 16px",fontSize:13,opacity:saving?.7:1}}>{saving?"Saving...":"Save details"}</button>

        {/* Locations */}
        <div style={{marginTop:24,borderTop:"1px solid #e5e7eb",paddingTop:18}}>
          <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:12}}>
            <h3 style={{...SH,margin:0}}>Locations ({locations.filter(l=>l.is_active).length} active)</h3>
            <button onClick={()=>setShowAddLoc(!showAddLoc)} style={{...BTN,padding:"6px 12px",fontSize:12}}>{showAddLoc?"Cancel":"+ Add location"}</button>
          </div>
          <div style={{fontSize:11,color:"#9ca3af",marginBottom:10}}>Each active location is a billing line item. Owners cannot add locations themselves — only platform admins.</div>

          {showAddLoc && <div style={{...CD,borderColor:"#3b82f6",background:"#f0f7ff",padding:14}}>
            <input style={{...SI,marginBottom:8}} placeholder="Location name (e.g. Houston Galleria)" value={newLoc.name} onChange={e=>setNewLoc({...newLoc,name:e.target.value})}/>
            <div style={{display:"flex",gap:8,marginBottom:8}}>
              <input style={{...SI,flex:2}} placeholder="City" value={newLoc.city} onChange={e=>setNewLoc({...newLoc,city:e.target.value})}/>
              <input style={{...SI,flex:1}} placeholder="State" maxLength={2} value={newLoc.state} onChange={e=>setNewLoc({...newLoc,state:e.target.value.toUpperCase()})}/>
              <input style={{...SI,flex:1}} placeholder="POS Site ID" value={newLoc.pos_site_id} onChange={e=>setNewLoc({...newLoc,pos_site_id:e.target.value})}/>
            </div>
            <input style={{...SI,marginBottom:8}} placeholder="Street address" value={newLoc.address} onChange={e=>setNewLoc({...newLoc,address:e.target.value})}/>
            <button onClick={addLocation} style={{...BTN,padding:"7px 14px",fontSize:13,background:"linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%)"}}>Add location + seed business hours</button>
          </div>}

          {loading ? <div style={{textAlign:"center",padding:20,color:"#9ca3af"}}>Loading locations...</div>
          : locations.length === 0 ? <div style={{textAlign:"center",padding:20,color:"#9ca3af"}}>No locations yet — add the first one above.</div>
          : locations.map(loc => editingLocId === loc.id ? (
            <div key={loc.id} style={{...CD,borderColor:"#3b82f6",background:"#f0f7ff",padding:14}}>
              <input style={{...SI,marginBottom:8}} placeholder="Name" value={editLoc.name} onChange={e=>setEditLoc({...editLoc,name:e.target.value})}/>
              <div style={{display:"flex",gap:8,marginBottom:8}}>
                <input style={{...SI,flex:2}} placeholder="City" value={editLoc.city} onChange={e=>setEditLoc({...editLoc,city:e.target.value})}/>
                <input style={{...SI,flex:1}} placeholder="State" maxLength={2} value={editLoc.state} onChange={e=>setEditLoc({...editLoc,state:e.target.value.toUpperCase()})}/>
                <input style={{...SI,flex:1}} placeholder="POS Site ID" value={editLoc.pos_site_id} onChange={e=>setEditLoc({...editLoc,pos_site_id:e.target.value})}/>
              </div>
              <input style={{...SI,marginBottom:8}} placeholder="Address" value={editLoc.address} onChange={e=>setEditLoc({...editLoc,address:e.target.value})}/>
              <div style={{display:"flex",gap:8}}>
                <button onClick={saveEditLoc} style={{...BTN,padding:"7px 14px",fontSize:13,flex:1}}>Save</button>
                <button onClick={()=>setEditingLocId(null)} style={{...BTN,padding:"7px 14px",fontSize:13,background:"#f3f4f6",color:"#374151"}}>Cancel</button>
              </div>
            </div>
          ) : (
            <div key={loc.id} style={{...CD,padding:"12px 14px",opacity:loc.is_active?1:.55,borderLeft:`3px solid ${loc.is_active?"#10b981":"#9ca3af"}`}}>
              <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",gap:12}}>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{display:"flex",alignItems:"center",gap:6,flexWrap:"wrap"}}>
                    <span style={{fontWeight:600,fontSize:14,color:"#111"}}>{loc.name}</span>
                    {!loc.is_active && <span style={{fontSize:10,fontWeight:600,padding:"1px 6px",borderRadius:4,background:"#fee2e2",color:"#991b1b"}}>INACTIVE</span>}
                    {loc.pos_site_id && <span style={{fontSize:10,fontWeight:500,padding:"1px 6px",borderRadius:4,background:"#f3f4f6",color:"#6b7280",fontFamily:"monospace"}}>POS {loc.pos_site_id}</span>}
                  </div>
                  <div style={{fontSize:11,color:"#9ca3af",marginTop:2}}>
                    {[loc.city,loc.state].filter(Boolean).join(", ") || "no location set"}
                  </div>
                </div>
                <div style={{display:"flex",gap:4,flexShrink:0}}>
                  <button onClick={()=>startEditLoc(loc)} style={{...IB,width:28,height:28}} title="Edit">✎</button>
                  {loc.is_active
                    ? <button onClick={()=>deactivateLoc(loc.id)} style={{...IB,width:28,height:28,color:"#f59e0b"}} title="Deactivate">⏸</button>
                    : <button onClick={()=>reactivateLoc(loc.id)} style={{...IB,width:28,height:28,color:"#059669"}} title="Reactivate">↺</button>
                  }
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  </div>;
}


// ═══ AUTO-SCHEDULE MODAL ═══
// Shows a proposed schedule generated by the v3 forecast/generate endpoint
// alongside per-stylist hour totals, day-by-day coverage, and any warnings.
// Click "Generate" to fetch the proposal (no save). Click "Save as Drafts"
// to commit matched stylists' shifts to the calendar.
function AutoScheduleModal({asMonth,setAsMonth,asLocId,setAsLocId,asPosSite,setAsPosSite,asProposal,setAsProposal,asLoading,runAutoSchedule,applyProposal,locs,onClose}){
  const fmtMonthLabel = m => {
    if(!m||m.length<7) return m;
    const [y,mo] = m.split("-");
    const d = new Date(parseInt(y), parseInt(mo)-1, 1);
    return d.toLocaleDateString(undefined,{month:"long",year:"numeric"});
  };
  // Build month options: this month + next 12
  const monthOpts = (()=>{const a=[];const now=new Date();for(let i=0;i<13;i++){const d=new Date(now.getFullYear(),now.getMonth()+i,1);a.push(`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}`);}return a;})();

  return <div onClick={onClose} style={{position:"fixed",inset:0,background:"rgba(0,0,0,.5)",zIndex:600,display:"flex",alignItems:"center",justifyContent:"center",padding:16,overflow:"auto"}}>
    <div onClick={e=>e.stopPropagation()} style={{background:"#fff",borderRadius:14,padding:0,width:"100%",maxWidth:780,maxHeight:"92vh",boxShadow:"0 30px 80px rgba(0,0,0,.3)",overflow:"hidden",display:"flex",flexDirection:"column"}}>
      {/* Header w/ AI gradient */}
      <div style={{padding:"18px 24px",background:"linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%)",color:"#fff",display:"flex",justifyContent:"space-between",alignItems:"center"}}>
        <div>
          <div style={{fontSize:11,fontWeight:700,letterSpacing:1.5,textTransform:"uppercase",opacity:.85}}>Bruton Intelligence</div>
          <div style={{fontSize:18,fontWeight:700,marginTop:2}}>✨ Auto-Schedule</div>
        </div>
        <button onClick={onClose} style={{background:"rgba(255,255,255,.2)",border:"none",color:"#fff",width:30,height:30,borderRadius:6,fontSize:16,cursor:"pointer"}}>✕</button>
      </div>

      <div style={{padding:24,overflowY:"auto",flex:1}}>
        {/* Step 1: configuration */}
        {!asProposal && <>
          <p style={{fontSize:14,color:"#374151",marginBottom:16,lineHeight:1.5}}>
            Generates a proposed monthly schedule using your last 13 months of POS data.
            Honors stylist hour caps (35hr target, 37hr cap), time-off requests, closed dates,
            and your custom rules. Returns a draft you can review before publishing.
          </p>

          <div style={{display:"flex",gap:12,marginBottom:14}}>
            <div style={{flex:1}}>
              <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Month</div>
              <select style={SI} value={asMonth} onChange={e=>setAsMonth(e.target.value)}>
                {monthOpts.map(m=><option key={m} value={m}>{fmtMonthLabel(m)}</option>)}
              </select>
            </div>
            <div style={{flex:1}}>
              <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Location</div>
              <select style={SI} value={asLocId} onChange={e=>setAsLocId(e.target.value)} disabled={!!asPosSite.trim()}>
                {locs.map(l=><option key={l.id} value={l.id}>{l.name}</option>)}
              </select>
            </div>
          </div>

          <details style={{marginBottom:16,border:"1px solid #e5e7eb",borderRadius:8,padding:"8px 12px"}}>
            <summary style={{cursor:"pointer",fontSize:12,fontWeight:600,color:"#6b7280"}}>Demo mode (advanced)</summary>
            <div style={{marginTop:10}}>
              <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>POS site_id override</div>
              <input style={SI} placeholder="e.g. 49 to demo South Jordan" value={asPosSite} onChange={e=>setAsPosSite(e.target.value)}/>
              <div style={{fontSize:11,color:"#9ca3af",marginTop:4}}>Use this to forecast against any Cookie Cutters store in the chain. Save is disabled in this mode (preview only).</div>
            </div>
          </details>

          <button onClick={runAutoSchedule} disabled={asLoading} style={{width:"100%",padding:"13px",borderRadius:10,border:"none",background:"linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%)",color:"#fff",fontSize:15,fontWeight:700,cursor:asLoading?"wait":"pointer",opacity:asLoading?.7:1,boxShadow:"0 2px 8px rgba(124,58,237,.3)"}}>
            {asLoading?"⏳ Analyzing 13 months of POS data...":`✨ Generate ${fmtMonthLabel(asMonth)} Schedule`}
          </button>
        </>}

        {/* Step 2: proposal results */}
        {asProposal && <ProposalView proposal={asProposal} onBack={()=>setAsProposal(null)}/>}
      </div>

      {/* Footer w/ actions when proposal is shown */}
      {asProposal && <div style={{padding:"14px 24px",borderTop:"1px solid #e5e7eb",background:"#f9fafb",display:"flex",justifyContent:"space-between",alignItems:"center",gap:12}}>
        <button onClick={()=>setAsProposal(null)} style={{padding:"10px 18px",borderRadius:8,border:"1px solid #d1d5db",background:"#fff",fontSize:13,fontWeight:600,color:"#374151",cursor:"pointer"}}>← Back</button>
        <div style={{display:"flex",gap:8}}>
          <button onClick={onClose} style={{padding:"10px 18px",borderRadius:8,border:"1px solid #d1d5db",background:"#fff",fontSize:13,fontWeight:600,color:"#374151",cursor:"pointer"}}>Discard</button>
          <button onClick={applyProposal} disabled={asLoading||!!asPosSite.trim()} style={{padding:"10px 22px",borderRadius:8,border:"none",background:asPosSite.trim()?"#9ca3af":"linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%)",color:"#fff",fontSize:13,fontWeight:700,cursor:asPosSite.trim()?"not-allowed":"pointer",opacity:asLoading?.7:1}}>
            {asLoading?"Saving...":"Save as Drafts →"}
          </button>
        </div>
      </div>}
    </div>
  </div>;
}

// ═══ PROPOSAL VIEW ═══
// Renders the GenerateResponseV3 — stats, per-stylist weekly hours, daily coverage, warnings.
function ProposalView({proposal,onBack}){
  const summary = proposal.coverage.reduce((acc,c)=>{
    acc.demand += c.demand_hours;
    acc.assigned += c.assigned_hours;
    return acc;
  },{demand:0,assigned:0});
  const fillPct = summary.demand>0 ? Math.round(100*summary.assigned/summary.demand) : 0;
  return <div>
    {/* Top stats */}
    <div style={{display:"grid",gridTemplateColumns:"1fr 1fr 1fr 1fr",gap:10,marginBottom:18}}>
      <div style={{...CD,padding:"12px 14px",margin:0,borderLeft:"3px solid #7c3aed"}}>
        <div style={{fontSize:22,fontWeight:700,color:"#111"}}>{proposal.shifts.length}</div>
        <div style={{fontSize:11,color:"#6b7280"}}>shifts proposed</div>
      </div>
      <div style={{...CD,padding:"12px 14px",margin:0,borderLeft:"3px solid #3b82f6"}}>
        <div style={{fontSize:22,fontWeight:700,color:"#111"}}>{proposal.stylists.length}</div>
        <div style={{fontSize:11,color:"#6b7280"}}>stylists scheduled</div>
      </div>
      <div style={{...CD,padding:"12px 14px",margin:0,borderLeft:"3px solid #10b981"}}>
        <div style={{fontSize:22,fontWeight:700,color:"#111"}}>{Math.round(summary.assigned)}h</div>
        <div style={{fontSize:11,color:"#6b7280"}}>total scheduled</div>
      </div>
      <div style={{...CD,padding:"12px 14px",margin:0,borderLeft:`3px solid ${fillPct>=95?"#10b981":fillPct>=85?"#f59e0b":"#dc2626"}`}}>
        <div style={{fontSize:22,fontWeight:700,color:"#111"}}>{fillPct}%</div>
        <div style={{fontSize:11,color:"#6b7280"}}>demand filled</div>
      </div>
    </div>

    {/* Warnings */}
    {proposal.warnings.length>0 && <div style={{background:"#fef3c7",border:"1px solid #fcd34d",borderRadius:8,padding:12,marginBottom:16}}>
      <div style={{fontWeight:600,fontSize:13,color:"#92400e",marginBottom:6}}>⚠ {proposal.warnings.length} day{proposal.warnings.length===1?"":"s"} understaffed vs forecast</div>
      <div style={{fontSize:12,color:"#78350f",maxHeight:80,overflowY:"auto"}}>
        {proposal.warnings.slice(0,5).map((w,i)=><div key={i} style={{padding:"2px 0"}}>{w}</div>)}
        {proposal.warnings.length>5 && <div style={{padding:"2px 0",fontStyle:"italic"}}>...and {proposal.warnings.length-5} more</div>}
      </div>
    </div>}

    {/* Stylists weekly hours */}
    <div style={{marginBottom:18}}>
      <h3 style={{fontSize:13,fontWeight:700,color:"#374151",marginBottom:8,textTransform:"uppercase",letterSpacing:.5}}>Per-Stylist Hours</h3>
      <div style={{border:"1px solid #e5e7eb",borderRadius:8,overflow:"hidden"}}>
        <table style={{width:"100%",borderCollapse:"collapse",fontSize:12}}>
          <thead>
            <tr style={{background:"#f9fafb",borderBottom:"1px solid #e5e7eb"}}>
              <th style={{padding:"8px 12px",textAlign:"left",fontWeight:600,color:"#6b7280"}}>Stylist</th>
              <th style={{padding:"8px 12px",textAlign:"left",fontWeight:600,color:"#6b7280"}}>Type</th>
              <th style={{padding:"8px 12px",textAlign:"right",fontWeight:600,color:"#6b7280"}}>Total Hrs</th>
              <th style={{padding:"8px 12px",textAlign:"right",fontWeight:600,color:"#6b7280"}}>Target</th>
              <th style={{padding:"8px 12px",textAlign:"right",fontWeight:600,color:"#6b7280"}}>Cap</th>
            </tr>
          </thead>
          <tbody>
            {proposal.weekly_hours.map(s=>{
              const stylist=proposal.stylists.find(x=>x.pos_employee_id===s.pos_employee_id);
              const overTarget=stylist&&s.total_hours>stylist.weekly_cap;
              const underTarget=stylist&&s.total_hours<stylist.weekly_target*0.8;
              return <tr key={s.pos_employee_id} style={{borderBottom:"1px solid #f3f4f6"}}>
                <td style={{padding:"8px 12px",fontWeight:500,color:"#111"}}>{s.stylist_name}</td>
                <td style={{padding:"8px 12px",color:"#6b7280"}}>{stylist?.employment_type||"UNKNOWN"}</td>
                <td style={{padding:"8px 12px",textAlign:"right",fontWeight:600,color:overTarget?"#dc2626":underTarget?"#f59e0b":"#10b981"}}>{s.total_hours}h</td>
                <td style={{padding:"8px 12px",textAlign:"right",color:"#9ca3af"}}>{stylist?.weekly_target||"—"}h/wk</td>
                <td style={{padding:"8px 12px",textAlign:"right",color:"#9ca3af"}}>{stylist?.weekly_cap||"—"}h/wk</td>
              </tr>;
            })}
          </tbody>
        </table>
      </div>
    </div>

    {/* Daily coverage */}
    <div>
      <h3 style={{fontSize:13,fontWeight:700,color:"#374151",marginBottom:8,textTransform:"uppercase",letterSpacing:.5}}>Daily Coverage</h3>
      <div style={{display:"grid",gridTemplateColumns:"repeat(7,1fr)",gap:4,fontSize:11}}>
        {proposal.coverage.map(c=>{
          const ratio = c.demand_hours>0 ? c.assigned_hours/c.demand_hours : 1;
          const bg = ratio>=0.95 ? "#d1fae5" : ratio>=0.8 ? "#fef3c7" : "#fee2e2";
          const border = ratio>=0.95 ? "#10b981" : ratio>=0.8 ? "#f59e0b" : "#dc2626";
          return <div key={c.date.toString()} style={{padding:"6px 4px",borderRadius:6,background:bg,borderLeft:`3px solid ${border}`,textAlign:"center"}}>
            <div style={{fontSize:9,fontWeight:600,color:"#6b7280",textTransform:"uppercase"}}>{c.day_of_week}</div>
            <div style={{fontSize:13,fontWeight:700,color:"#111"}}>{new Date(c.date).getDate()}</div>
            <div style={{fontSize:9,color:"#6b7280",marginTop:2}}>{c.assigned_hours}/{c.demand_hours}h</div>
            <div style={{fontSize:8,color:"#9ca3af"}}>{c.stylists_assigned} ppl</div>
          </div>;
        })}
      </div>
    </div>
  </div>;
}

// ═══ CALENDAR GRID ═══
function CalendarGrid({days,canE,vU,gs,isCl,getTOR,wh,setModal,drag,setDrag,moveShift,locs,vL}) {
  // Smart location default: use the currently viewed location if the user belongs to it, otherwise first
  const defLoc = (u) => {
    if(vL && vL!=="All") {
      const match = locs.find(l=>l.name===vL);
      if(match && u.location_ids?.includes(match.id)) return match.id;
    }
    return u.location_ids?.[0];
  };
  return <div style={{overflowX:"auto",marginBottom:16}}>
    <div style={{display:"grid",gridTemplateColumns:"100px repeat(7,1fr)",minWidth:680,borderRadius:8,overflow:"hidden",border:"1px solid #e5e7eb",background:"#fff"}}>
      <div style={GH}>Staff</div>
      {days.map((d,i)=>{
        const td=isSame(d,new Date()), cl=isCl(d);
        return <div key={i} style={{...GH,background:cl?"#fef2f2":td?"#eff6ff":"#f9fafb"}}>
          <div style={{fontSize:10,fontWeight:600,color:td?"#3b82f6":cl?"#dc2626":"#9ca3af",textTransform:"uppercase"}}>{DS[d.getDay()]}</div>
          <div style={{fontSize:16,fontWeight:700,color:td?"#fff":cl?"#dc2626":"#374151",
            ...(td?{background:"#3b82f6",width:26,height:26,borderRadius:"50%",display:"inline-flex",alignItems:"center",justifyContent:"center"}:{})}}>{d.getDate()}</div>
          {cl && <div style={{fontSize:8,fontWeight:700,color:"#dc2626"}}>CLOSED</div>}
        </div>;
      })}

      {vU.map(u => <div key={u.id} style={{display:"contents"}}>
        <div style={{padding:"6px 8px",borderBottom:"1px solid #f3f4f6",borderRight:"1px solid #e5e7eb",background:"#fff",display:"flex",flexDirection:"column",justifyContent:"center"}}>
          <div style={{fontSize:12,fontWeight:600,color:"#111",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"}}>{u.display_name}</div>
          <div style={{fontSize:10,color:"#9ca3af"}}>{fmtH(wh[u.id]||0)}</div>
        </div>
        {days.map((d,di)=>{
          const s=gs(u.id,d), cl=isCl(d), tr=getTOR(u.id,d);
          const rc=s?RC[s.role]||RC.Stylist:null, dr=s?.status==="DRAFT";
          return <div key={di}
            style={{padding:"4px 3px",borderBottom:"1px solid #f3f4f6",borderRight:"1px solid #f3f4f6",display:"flex",alignItems:"center",justifyContent:"center",minHeight:48,cursor:canE?"pointer":"default",background:cl?"#fef2f2":isSame(d,new Date())?"#f0f7ff":"#fff"}}
            onDragOver={canE?e=>{e.preventDefault();e.dataTransfer.dropEffect="move"}:undefined}
            onDrop={canE?e=>{e.preventDefault();if(drag?.uid===u.id&&drag.s?.id)moveShift(drag.s.id,dk(d));setDrag(null)}:undefined}
            onClick={()=>{
              if(cl)return;
              if(canE&&!s&&!tr) setModal({mode:"add",uid:u.id,dk:dk(d),day:`${DF[d.getDay()]}, ${MS[d.getMonth()]} ${d.getDate()}`,name:u.display_name,positions:u.positions,locId:defLoc(u),userLocs:u.location_ids});
              else if(canE&&s) setModal({mode:"edit",uid:u.id,dk:dk(d),day:`${DF[d.getDay()]}, ${MS[d.getMonth()]} ${d.getDate()}`,name:u.display_name,shift:s,positions:u.positions,locId:s.locId||u.location_ids?.[0],userLocs:u.location_ids});
            }}>
            {cl ? <span style={{fontSize:10,color:"#fca5a5"}}>✕</span>
            : tr&&!s ? <div style={{width:"100%",padding:"4px 2px",borderRadius:4,fontSize:8,fontWeight:600,textAlign:"center",textTransform:"uppercase",...(tr.status==="PENDING"?{background:"repeating-linear-gradient(45deg,#f3f4f6,#f3f4f6 2px,#e5e7eb 2px,#e5e7eb 4px)",color:"#6b7280"}:{background:"#e5e7eb",color:"#6b7280"})}}>{tr.status==="APPROVED"?"OFF":"REQ"}</div>
            : s ? <div className="sp" draggable={canE}
                onDragStart={()=>setDrag({uid:u.id,s})} onDragEnd={()=>setDrag(null)}
                style={{width:"100%",padding:"3px 2px",borderRadius:4,fontSize:9,fontWeight:600,textAlign:"center",lineHeight:1.3,background:rc.bg,color:rc.tx,border:`1px ${dr?"dashed":"solid"} ${rc.bd}`,opacity:dr?.7:1,position:"relative"}}>
                {dr && <div className="hatch" style={{position:"absolute",inset:0,borderRadius:3}}/>}
                <div style={{position:"relative"}}>{s.locName&&locs.length>1?(()=>{const lc=getLC(s.locId,locs);return <div style={{fontSize:8,fontWeight:800,textTransform:"uppercase",letterSpacing:.3,color:lc.tx}}>{s.locName}</div>;})():null}{s.start}<br/>{s.end}</div>
              </div>
            : canE ? <div style={{width:"100%",height:32,border:"1.5px dashed #d1d5db",borderRadius:4,display:"flex",alignItems:"center",justifyContent:"center",color:"#d1d5db",fontSize:14}}>+</div>
            : <span style={{fontSize:10,color:"#e5e7eb"}}>—</span>}
          </div>;
        })}
      </div>)}

      <div style={{...GH,fontSize:10,fontWeight:600}}>Hours</div>
      {days.map((d,i)=>{
        let h=0; vU.forEach(u=>{const s=gs(u.id,d);if(s)h+=pT(s.end)-pT(s.start)});
        return <div key={i} style={{...GH,fontSize:11,fontWeight:700,color:"#3b82f6"}}>{h>0?fmtH(h):""}</div>;
      })}
    </div>
  </div>;
}

// ═══ LIST VIEW ═══
function ListView({days,canE,vU,gs,isCl,getTOR,setModal,locs}) {
  return <div>{days.map(d=>{
    const cl=isCl(d), td=isSame(d,new Date());
    const ds=vU.map(u=>({u,s:gs(u.id,d),t:getTOR(u.id,d)})).filter(x=>x.s||x.t);
    return <div key={dk(d)} style={{marginBottom:16}}>
      <div style={{background:td?"#3b82f6":"#374151",color:"#fff",padding:"8px 14px",borderRadius:"8px 8px 0 0",fontSize:12,fontWeight:600,textTransform:"uppercase",letterSpacing:.5,display:"flex",justifyContent:"space-between"}}>
        <span>{DF[d.getDay()]}, {MS[d.getMonth()]} {d.getDate()}</span>
        {cl?<span style={{background:"#dc2626",padding:"1px 8px",borderRadius:4}}>CLOSED</span>:<span>{ds.filter(x=>x.s).length} shifts</span>}
      </div>
      {cl ? <div style={{...CD,borderRadius:"0 0 8px 8px",textAlign:"center",color:"#9ca3af",padding:20,marginBottom:0}}>Closed</div>
      : ds.length===0 ? <div style={{...CD,borderRadius:"0 0 8px 8px",textAlign:"center",color:"#9ca3af",padding:20,marginBottom:0}}>No shifts</div>
      : <div style={{background:"#fff",border:"1px solid #e5e7eb",borderTop:"none",borderRadius:"0 0 8px 8px"}}>
          {ds.map(({u,s,t})=>{
            if(t&&!s) return <div key={u.id} style={{display:"flex",alignItems:"center",gap:12,padding:"12px 14px",borderBottom:"1px solid #f3f4f6"}}>
              <div style={AV}>{u.avatar_initials}</div>
              <div style={{flex:1}}><div style={{fontWeight:600,fontSize:14,color:"#111"}}>{u.first_name} {u.last_name}</div><div style={{fontSize:13,color:"#9ca3af"}}>Time Off</div></div>
              <Badge s={t.status}/>
            </div>;
            const rc=RC[s?.role]||RC.Stylist, dr=s?.status==="DRAFT";
            return <div key={u.id}
              style={{display:"flex",alignItems:"center",gap:12,padding:"12px 14px",borderBottom:"1px solid #f3f4f6",cursor:canE?"pointer":"default",...(dr?{background:"#fefce8"}:{})}}
              onClick={()=>canE&&s&&setModal({mode:"edit",uid:u.id,dk:dk(d),day:`${DF[d.getDay()]}, ${MS[d.getMonth()]} ${d.getDate()}`,name:u.display_name,shift:s,positions:u.positions,locId:s.locId||u.location_ids?.[0],userLocs:u.location_ids})}>
              <div style={{...AV,borderLeft:`3px solid ${rc.bd}`}}>{u.avatar_initials}</div>
              <div style={{flex:1}}>
                <div style={{fontWeight:600,fontSize:14,color:"#111"}}>{u.first_name} {u.last_name}</div>
                <div style={{fontSize:15,fontWeight:700,color:"#374151"}}>{s.start} – {s.end}</div>
                <div style={{display:"flex",alignItems:"center",gap:6}}>
                  <span style={{fontSize:12,color:"#9ca3af"}}>{s.role}</span>
                  {s.locName&&locs.length>1&&(()=>{const lc=getLC(s.locId,locs);return <span style={{fontSize:10,fontWeight:600,color:lc.tx,background:lc.bg,padding:"1px 6px",borderRadius:4}}>{s.locName}</span>})()}
                </div>
              </div>
              {dr && <Badge s="DRAFT"/>}
              <span style={{color:"#d1d5db",fontSize:18}}>›</span>
            </div>;
          })}
        </div>}
    </div>;
  })}</div>;
}

// ═══ TIME OFF VIEW ═══
function TimeOffView({me,isM,tor,vL,fetchAll,tt}) {
  const[show,setShow]=useState(false);
  const[f,setF]=useState({start_date:"",end_date:"",reason:"",notes:""});
  const[deny,setDeny]=useState(null);
  const[dc,setDc]=useState("");
  const fl=isM?tor.filter(r=>vL==="All"||r.location_name===vL):tor.filter(r=>r.user_id===me.id);

  const submit=async()=>{
    if(!f.start_date){tt("Pick a start date");return}
    if(!f.reason){tt("Pick a reason");return}
    try{
      await apiFetch("/api/scheduling/v3/time-off",{method:"POST",body:JSON.stringify({
        start_date:f.start_date,
        end_date:f.end_date||f.start_date,
        reason:f.reason,
        notes:f.notes||null,
      })});
      await fetchAll();
      setF({start_date:"",end_date:"",reason:"",notes:""});
      setShow(false);
      tt("Submitted");
    }catch(e){tt(e.message)}
  };
  const review=async(id,st,cm)=>{ try{await apiFetch(`/api/scheduling/v3/time-off/${id}`,{method:"PUT",body:JSON.stringify({status:st,comment:cm})});await fetchAll();tt(st)}catch(e){tt(e.message)} };
  const del=async id=>{ try{await apiFetch(`/api/scheduling/v3/time-off/${id}`,{method:"DELETE"});await fetchAll();tt("Removed")}catch(e){tt(e.message)} };

  return <div style={{paddingTop:16}}>
    <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:16}}>
      <h2 style={{fontSize:18,fontWeight:700,color:"#111"}}>{isM?"Requests":"My Time Off"}</h2>
      {!isM&&<button onClick={()=>setShow(!show)} style={{...BTN,padding:"8px 14px",fontSize:13}}>{show?"Cancel":"+ Request"}</button>}
    </div>
    {show && <div style={{...CD,borderColor:"#3b82f6"}}>
      <div style={{display:"flex",gap:12,marginBottom:10}}>
        <div style={{flex:1}}><div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4}}>Start *</div><input type="date" style={SI} value={f.start_date} onChange={e=>setF({...f,start_date:e.target.value})}/></div>
        <div style={{flex:1}}><div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4}}>End</div><input type="date" style={SI} value={f.end_date} onChange={e=>setF({...f,end_date:e.target.value})}/></div>
      </div>
      <div style={{marginBottom:10}}>
        <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4}}>Reason *</div>
        <select style={SI} value={f.reason} onChange={e=>setF({...f,reason:e.target.value})}>
          <option value="">Select...</option>
          <option>Vacation</option>
          <option>Personal</option>
          <option>Medical</option>
          <option>Family</option>
        </select>
      </div>
      <div style={{marginBottom:10}}>
        <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4}}>Additional details <span style={{fontWeight:400,textTransform:"none",color:"#9ca3af"}}>(optional)</span></div>
        <textarea style={{...SI,minHeight:60,resize:"vertical",fontFamily:"inherit"}} placeholder="e.g. Sister's wedding, post-op recovery, family trip..." value={f.notes} onChange={e=>setF({...f,notes:e.target.value})} maxLength={500}/>
        <div style={{fontSize:11,color:"#9ca3af",marginTop:4}}>Give your manager any context that might help them approve the request.</div>
      </div>
      <button onClick={submit} style={{...BTN,width:"100%",textAlign:"center"}}>Submit</button>
    </div>}
    {fl.sort((a,b)=>a.status==="PENDING"?-1:1).map(r=><div key={r.id} style={CD}>
      <div style={{display:"flex",justifyContent:"space-between"}}>
        <div>
          {isM&&<div style={{fontWeight:600,fontSize:14,color:"#111",marginBottom:2}}>{r.display_name} <span style={{fontSize:11,color:"#9ca3af",fontWeight:400}}>{r.location_name}</span></div>}
          <div style={{fontWeight:600,fontSize:14,color:"#374151"}}>{r.start_date}{r.end_date!==r.start_date?` – ${r.end_date}`:""}</div>
          {r.reason&&<div style={{fontSize:13,color:"#6b7280",marginTop:2}}>{r.reason}</div>}
          {r.notes&&<div style={{fontSize:12,color:"#9ca3af",marginTop:2,fontStyle:"italic"}}>"{r.notes}"</div>}
        </div>
        <div style={{display:"flex",alignItems:"center",gap:6}}>
          <Badge s={r.status}/>
          {(isM||r.user_id===me?.id)&&<button onClick={()=>{if(confirm("Delete this time-off request?"))del(r.id)}} title="Delete request" style={{background:"none",border:"none",fontSize:14,color:"#d1d5db",cursor:"pointer"}}>✕</button>}
        </div>
      </div>
      {isM&&r.status==="PENDING"&&<div style={{marginTop:10}}>
        {deny===r.id ? <div>
          <input placeholder="Reason for denying..." style={{...SI,marginBottom:6}} value={dc} onChange={e=>setDc(e.target.value)}/>
          <div style={{display:"flex",gap:6}}>
            <button onClick={()=>{review(r.id,"DENIED",dc);setDeny(null);setDc("")}} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#fee2e2",color:"#991b1b"}}>Deny</button>
            <button onClick={()=>setDeny(null)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#f3f4f6",color:"#374151"}}>Cancel</button>
          </div>
        </div> : <div style={{display:"flex",gap:6}}>
          <button onClick={()=>review(r.id,"APPROVED")} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#d1fae5",color:"#065f46"}}>Approve</button>
          <button onClick={()=>setDeny(r.id)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#fee2e2",color:"#991b1b"}}>Deny</button>
        </div>}
      </div>}
    </div>)}
    {fl.length===0&&<div style={{textAlign:"center",padding:40,color:"#9ca3af"}}>No requests</div>}
  </div>;
}

// ═══ PEOPLE VIEW ═══
function PeopleView({users,locs,fetchAll,tt,isOwner}) {
  const[eu,setEu]=useState(null);
  const emptyF={firstName:"",lastName:"",email:"",phone:"",empType:"FULL_TIME",positions:[],locationIds:[],role:"staff",posEmployeeId:""};
  const[f,setF]=useState(emptyF);
  const[cd,setCd]=useState(null);
  const[showInactive,setShowInactive]=useState(false);
  const[posCands,setPosCands]=useState([]);

  // Fetch POS candidates whenever the form's locations change
  const locKey=f.locationIds.join(",");
  useEffect(()=>{
    if(!eu||!locKey){setPosCands([]);return;}
    apiFetch(`/api/scheduling/v3/pos-employees?location_ids=${locKey}`)
      .then(setPosCands).catch(()=>setPosCands([]));
  },[eu,locKey]);

  // Auto-suggest the best name match if no link is set yet
  useEffect(()=>{
    if(!posCands.length)return;
    setF(prev=>{
      if(prev.posEmployeeId||!prev.firstName||!prev.lastName)return prev;
      const fn=prev.firstName.trim().toLowerCase(), ln=prev.lastName.trim().toLowerCase();
      const m=posCands.find(c=>(c.first_name||"").toLowerCase()===fn&&(c.last_name||"").toLowerCase()===ln);
      return m?{...prev,posEmployeeId:m.employee_id}:prev;
    });
  },[posCands]);

  const roleOf=u=>u.is_owner?"owner":u.is_admin?"manager":"staff";
  const roleBadge={owner:{bg:"#dbeafe",tx:"#1e40af",label:"OWNER"},manager:{bg:"#f3e8ff",tx:"#7c3aed",label:"MANAGER"},staff:{bg:"#f3f4f6",tx:"#6b7280",label:"STAFF"}};
  const empLabel={FULL_TIME:"Full-Time",PART_TIME:"Part-Time"};

  const save=async()=>{
    if(!f.firstName||!f.lastName){tt("Enter name");return}
    if(!f.email){tt("Email is required");return}
    if(!f.locationIds.length){tt("Select at least one location");return}
    const payload={first_name:f.firstName,last_name:f.lastName,email:f.email,phone:f.phone||null,
      employment_type:f.empType,positions:f.positions,location_ids:f.locationIds,
      pos_employee_id:f.posEmployeeId||null};
    if(isOwner){payload.is_owner=f.role==="owner";payload.is_admin=f.role==="manager"||f.role==="owner";}
    try{
      if(eu==="new")await apiFetch("/api/scheduling/v3/users",{method:"POST",body:JSON.stringify(payload)});
      else await apiFetch(`/api/scheduling/v3/users/${eu.id}`,{method:"PUT",body:JSON.stringify(payload)});
      await fetchAll();setEu(null);tt("Saved");
    }catch(e){tt(e.message)}
  };
  const deactivate=async uid=>{try{await apiFetch(`/api/scheduling/v3/users/${uid}/deactivate`,{method:"PUT"});await fetchAll();tt("Deactivated")}catch(e){tt(e.message)}};
  const reactivate=async uid=>{try{await apiFetch(`/api/scheduling/v3/users/${uid}/reactivate`,{method:"PUT"});await fetchAll();tt("Reactivated")}catch(e){tt(e.message)}};
  const resend=async uid=>{try{const r=await apiFetch(`/api/scheduling/v3/users/${uid}/resend-invite`,{method:"POST"});tt(r.message||"Invite sent")}catch(e){tt(e.message)}};
  const del=async uid=>{try{await apiFetch(`/api/scheduling/v3/users/${uid}`,{method:"DELETE"});await fetchAll();setCd(null);tt("Deleted")}catch(e){tt(e.message)}};

  const visible=showInactive?users:users.filter(u=>u.is_active!==false);
  const inactiveCount=users.filter(u=>u.is_active===false).length;

  return <div style={{paddingTop:16}}>
    <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:16}}>
      <h2 style={{fontSize:18,fontWeight:700,color:"#111"}}>People</h2>
      <div style={{display:"flex",gap:8}}>
        {inactiveCount>0&&<button onClick={()=>setShowInactive(!showInactive)} style={{padding:"8px 14px",borderRadius:8,fontSize:12,fontWeight:500,border:"1px solid #d1d5db",background:showInactive?"#fef3c7":"#fff",color:showInactive?"#92400e":"#6b7280",cursor:"pointer"}}>{showInactive?`Hide inactive (${inactiveCount})`:`Show inactive (${inactiveCount})`}</button>}
        <button onClick={()=>{setEu("new");setF({...emptyF,locationIds:locs[0]?[locs[0].id]:[]})}} style={{...BTN,padding:"8px 14px",fontSize:13}}>+ Add</button>
      </div>
    </div>
    {eu&&<div style={{...CD,borderColor:"#3b82f6"}}>
      <div style={{display:"flex",gap:10,marginBottom:10}}>
        <input placeholder="First name" style={{...SI,flex:1}} value={f.firstName} onChange={e=>setF({...f,firstName:e.target.value})}/>
        <input placeholder="Last name" style={{...SI,flex:1}} value={f.lastName} onChange={e=>setF({...f,lastName:e.target.value})}/>
      </div>
      <input placeholder="Email (required)" type="email" style={{...SI,marginBottom:10}} value={f.email} onChange={e=>setF({...f,email:e.target.value})}/>
      <input placeholder="Phone (optional)" type="tel" style={{...SI,marginBottom:10}} value={f.phone} onChange={e=>setF({...f,phone:e.target.value})}/>
      <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Employment Type</div>
      <div style={{display:"flex",gap:6,marginBottom:10}}>
        {["FULL_TIME","PART_TIME"].map(t=><button key={t} onClick={()=>setF({...f,empType:t})}
          style={{padding:"6px 14px",borderRadius:6,fontSize:13,fontWeight:500,border:`1.5px solid ${f.empType===t?"#3b82f6":"#d1d5db"}`,background:f.empType===t?"#eff6ff":"#fff",color:f.empType===t?"#1d4ed8":"#6b7280",cursor:"pointer"}}>{empLabel[t]}</button>)}
      </div>
      <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Schedule Position (if applicable)</div>
      <div style={{display:"flex",gap:6,marginBottom:10}}>
        {["Stylist","Receptionist"].map(p=><button key={p} onClick={()=>{const h=f.positions.includes(p);setF({...f,positions:h?f.positions.filter(x=>x!==p):[...f.positions,p]})}}
          style={{padding:"6px 14px",borderRadius:6,fontSize:13,fontWeight:500,border:`1.5px solid ${f.positions.includes(p)?"#3b82f6":"#d1d5db"}`,background:f.positions.includes(p)?"#eff6ff":"#fff",color:f.positions.includes(p)?"#1d4ed8":"#6b7280",cursor:"pointer"}}>{f.positions.includes(p)?"✓ ":""}{p}</button>)}
      </div>
      <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Location(s)</div>
      <div style={{display:"flex",gap:6,marginBottom:10,flexWrap:"wrap"}}>
        {locs.map(l=><button key={l.id} onClick={()=>{const h=f.locationIds.includes(l.id);setF({...f,locationIds:h?f.locationIds.filter(x=>x!==l.id):[...f.locationIds,l.id]})}}
          style={{padding:"6px 14px",borderRadius:6,fontSize:13,fontWeight:500,border:`1.5px solid ${f.locationIds.includes(l.id)?"#10b981":"#d1d5db"}`,background:f.locationIds.includes(l.id)?"#ecfdf5":"#fff",color:f.locationIds.includes(l.id)?"#065f46":"#6b7280",cursor:"pointer"}}>{f.locationIds.includes(l.id)?"✓ ":""}{l.name}</button>)}
      </div>
      <div style={{marginBottom:10}}>
        <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Link to POS Employee</div>
        <select style={SI} value={f.posEmployeeId} onChange={e=>setF({...f,posEmployeeId:e.target.value})} disabled={!f.locationIds.length}>
          <option value="">{f.locationIds.length?"Not linked / new hire":"Select a location first"}</option>
          {posCands.map(c=><option key={`${c.employee_id}-${c.site_id}`} value={c.employee_id}>{c.first_name} {c.last_name} · {c.site_name} · last seen {c.last_seen} · {c.days_worked}d</option>)}
        </select>
        <div style={{fontSize:11,color:"#9ca3af",marginTop:4}}>Used to pull this stylist's service history for forecasting.{f.posEmployeeId&&posCands.length&&!posCands.find(c=>c.employee_id===f.posEmployeeId)?" (Currently linked employee not in this location's recent list — clear and re-pick if wrong.)":""}</div>
      </div>
      {isOwner&&<div style={{marginBottom:10}}>
        <div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>Role</div>
        <div style={{display:"flex",gap:6}}>
          {["staff","manager","owner"].map(r=><button key={r} onClick={()=>setF({...f,role:r})}
            style={{padding:"6px 14px",borderRadius:6,fontSize:13,fontWeight:500,border:`1.5px solid ${f.role===r?"#3b82f6":"#d1d5db"}`,background:f.role===r?"#eff6ff":"#fff",color:f.role===r?"#1d4ed8":"#6b7280",cursor:"pointer",textTransform:"capitalize"}}>{r}</button>)}
        </div>
        <div style={{fontSize:11,color:"#9ca3af",marginTop:4}}>{f.role==="owner"?"Full access to all locations, users, and admin settings in this franchise.":f.role==="manager"?"Can manage schedules, users, and time off requests.":"Can view schedule and request time off."}</div>
      </div>}
      {eu==="new"&&<div style={{background:"#eff6ff",borderRadius:6,padding:8,marginBottom:10,fontSize:12,color:"#1d4ed8"}}>An invite email will be sent to {f.email||"..."} to set their password.</div>}
      <div style={{display:"flex",gap:8}}>
        <button onClick={save} style={{...BTN,flex:1,textAlign:"center"}}>{eu==="new"?"Add Team Member":"Save"}</button>
        <button onClick={()=>setEu(null)} style={{...BTN,background:"#f3f4f6",color:"#374151"}}>Cancel</button>
      </div>
    </div>}
    {visible.map(u=>{const rl=roleOf(u);const rb=roleBadge[rl];return <div key={u.id} style={{...CD,...(u.is_active===false?{opacity:.6,borderColor:"#fca5a5",background:"#fef2f2"}:{})}}>
      {cd===u.id ? <div>
        <div style={{fontWeight:600,color:"#dc2626",marginBottom:4}}>Permanently delete {u.display_name}?</div>
        <div style={{fontSize:12,color:"#6b7280",marginBottom:8}}>This cannot be undone. Their login will also be removed.</div>
        <div style={{display:"flex",gap:6}}>
          <button onClick={()=>del(u.id)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#dc2626",color:"#fff"}}>Delete Forever</button>
          <button onClick={()=>setCd(null)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#f3f4f6",color:"#374151"}}>Cancel</button>
        </div>
      </div> : <div style={{display:"flex",alignItems:"center",gap:12}}>
        <div style={AV}>{u.avatar_initials}</div>
        <div style={{flex:1}}>
          <div style={{display:"flex",alignItems:"center",gap:6,flexWrap:"wrap"}}>
            <span style={{fontWeight:600,fontSize:14,color:"#111"}}>{u.display_name}</span>
            <span style={{fontSize:10,fontWeight:600,padding:"2px 6px",borderRadius:4,background:rb.bg,color:rb.tx}}>{rb.label}</span>
            <span style={{fontSize:10,fontWeight:500,padding:"2px 6px",borderRadius:4,background:"#f3f4f6",color:"#6b7280"}}>{empLabel[u.employment_type]||"Full-Time"}</span>
            {u.is_active===false&&<span style={{fontSize:10,fontWeight:600,padding:"2px 6px",borderRadius:4,background:"#fee2e2",color:"#991b1b"}}>INACTIVE</span>}
            {u.is_active!==false&&(u.pos_employee_id?<span title={`POS ID ${u.pos_employee_id}`} style={{fontSize:10,fontWeight:600,padding:"2px 6px",borderRadius:4,background:"#d1fae5",color:"#065f46"}}>POS ✓</span>:<span title="No POS employee linked" style={{fontSize:10,fontWeight:600,padding:"2px 6px",borderRadius:4,background:"#fef3c7",color:"#92400e"}}>POS ⚠</span>)}
          </div>
          {u.positions?.length>0&&<div style={{display:"flex",gap:4,marginTop:3}}>{u.positions.map(p=><span key={p} style={{fontSize:11,padding:"1px 6px",borderRadius:4,background:RC[p]?.bg||"#f3f4f6",color:RC[p]?.tx||"#374151",fontWeight:500}}>{p}</span>)}</div>}
          <div style={{fontSize:11,color:"#9ca3af",marginTop:2}}>{(u.location_names||[]).join(" · ")} · {u.email||"no email"}</div>
        </div>
        <div style={{display:"flex",gap:4}}>
          <button onClick={()=>{setEu(u);setF({firstName:u.first_name,lastName:u.last_name,email:u.email||"",phone:u.phone||"",empType:u.employment_type||"FULL_TIME",positions:[...(u.positions||[])],locationIds:[...(u.location_ids||[])],role:roleOf(u),posEmployeeId:u.pos_employee_id||""})}} style={IB} title="Edit">✎</button>
          {u.is_active!==false&&u.email&&<button onClick={()=>resend(u.id)} style={{...IB,color:"#3b82f6"}} title="Resend invite email">✉</button>}
          {u.is_active===false
            ? <button onClick={()=>reactivate(u.id)} style={{...IB,color:"#059669"}} title="Reactivate">↺</button>
            : <button onClick={()=>deactivate(u.id)} style={{...IB,color:"#f59e0b"}} title="Deactivate">⏸</button>}
          <button onClick={()=>setCd(u.id)} style={{...IB,color:"#dc2626"}} title="Delete">✕</button>
        </div>
      </div>}
    </div>;})}
    {visible.length===0&&<div style={{textAlign:"center",padding:40,color:"#9ca3af"}}>No team members</div>}
  </div>;
}

// ═══ ADMIN VIEW ═══
function AdminView({tpls,rules,bh,ann,closed,bo,locs,fetchAll,tt}) {
  const[adminTab,setAdminTab]=useState("templates");
  const[tplForm,setTplForm]=useState(null);
  const[tplF,setTplF]=useState({template_name:"",start:"9:50a",end:"6:00p"});
  const[tplDel,setTplDel]=useState(null);
  const saveTpl=async()=>{if(!tplF.template_name.trim()){tt("Enter a template name");return;}const st=to24(tplF.start),et=to24(tplF.end);if(pT(tplF.end)<=pT(tplF.start)){tt("End must be after start");return;}try{if(tplForm?.id){await apiFetch(`/api/scheduling/v3/templates/${tplForm.id}`,{method:"PUT",body:JSON.stringify({template_name:tplF.template_name,start_time:st,end_time:et})});}else{await apiFetch("/api/scheduling/v3/templates",{method:"POST",body:JSON.stringify({template_name:tplF.template_name,start_time:st,end_time:et,location_id:null})});}await fetchAll();setTplForm(null);tt("Saved");}catch(e){tt(e.message);}};
  const delTpl=async id=>{try{await apiFetch(`/api/scheduling/v3/templates/${id}`,{method:"DELETE"});await fetchAll();setTplDel(null);tt("Removed");}catch(e){tt(e.message);}};
  const openEditTpl=t=>{setTplF({template_name:t.template_name,start:apiTime(t.start_time),end:apiTime(t.end_time)});setTplForm(t);};
  const openNewTpl=()=>{setTplF({template_name:"",start:"9:50a",end:"6:00p"});setTplForm({});};
  const[ruleForm,setRuleForm]=useState(null);
  const emptyRuleF={rule_name:"",rule_description:"",rule_type:"GENERAL",param_1:"",param_2:"",user_id:"",location_id:""};
  const[ruleF,setRuleF]=useState(emptyRuleF);
  const[ruleDel,setRuleDel]=useState(null);
  const RULE_TYPES=["WEEKLY_HOURS_RANGE","MAX_CONSECUTIVE_DAYS","WEEKEND_OFF_PER_MONTH","SHIFT_START_OFFSET","CUSTOM"];
  const saveRule=async()=>{if(!ruleF.rule_name.trim()){tt("Enter a rule name");return;}try{if(ruleForm?.id){await apiFetch(`/api/scheduling/v3/admin/scheduling-rules/${ruleForm.id}`,{method:"PUT",body:JSON.stringify(ruleF)});}else{await apiFetch("/api/scheduling/v3/admin/scheduling-rules",{method:"POST",body:JSON.stringify(ruleF)});}await fetchAll();setRuleForm(null);tt("Saved");}catch(e){tt(e.message);}};
  const delRule=async id=>{try{await apiFetch(`/api/scheduling/v3/admin/scheduling-rules/${id}`,{method:"DELETE"});await fetchAll();setRuleDel(null);tt("Removed");}catch(e){tt(e.message);}};
  const openEditRule=r=>{setRuleF({rule_name:r.rule_name,rule_description:r.rule_description||"",rule_type:r.rule_type||"GENERAL",param_1:r.param_1||"",param_2:r.param_2||"",user_id:r.user_id||"",location_id:r.location_id||""});setRuleForm(r);};
  const[annForm,setAnnForm]=useState(false);
  const[annF,setAnnF]=useState({title:"",body:""});
  const[annDel,setAnnDel]=useState(null);
  const saveAnn=async()=>{if(!annF.title.trim()||!annF.body.trim()){tt("Fill in title and message");return;}try{await apiFetch("/api/scheduling/v3/admin/announcements",{method:"POST",body:JSON.stringify(annF)});await fetchAll();setAnnForm(false);setAnnF({title:"",body:""});tt("Posted");}catch(e){tt(e.message);}};
  const delAnn=async id=>{try{await apiFetch(`/api/scheduling/v3/admin/announcements/${id}`,{method:"DELETE"});await fetchAll();setAnnDel(null);tt("Removed");}catch(e){tt(e.message);}};
  const[bhEdit,setBhEdit]=useState(null);
  const[bhF,setBhF]=useState({open_time:"",close_time:"",is_open:true});
  const openEditBh=h=>{setBhF({open_time:h.open_time||"",close_time:h.close_time||"",is_open:h.is_open});setBhEdit(h.id);};
  const saveBh=async()=>{try{await apiFetch(`/api/scheduling/v3/admin/business-hours/${bhEdit}`,{method:"PUT",body:JSON.stringify(bhF)});await fetchAll();setBhEdit(null);tt("Saved");}catch(e){tt(e.message);}};

  // Closed dates (single days the salon is closed — holidays, deep clean, etc.)
  const[cdForm,setCdForm]=useState(false);
  const[cdF,setCdF]=useState({closed_date:"",reason:"",location_id:""});
  const[cdDel,setCdDel]=useState(null);
  const saveClosed=async()=>{
    if(!cdF.closed_date){tt("Pick a date");return;}
    try{
      await apiFetch("/api/scheduling/v3/admin/closed-dates",{method:"POST",body:JSON.stringify({closed_date:cdF.closed_date,reason:cdF.reason||null,location_id:cdF.location_id||null})});
      await fetchAll();setCdForm(false);setCdF({closed_date:"",reason:"",location_id:""});tt("Closed date added");
    }catch(e){tt(e.message);}
  };
  const delClosed=async id=>{try{await apiFetch(`/api/scheduling/v3/admin/closed-dates/${id}`,{method:"DELETE"});await fetchAll();setCdDel(null);tt("Removed");}catch(e){tt(e.message);}};

  // Blackout periods (date ranges where time-off requests are blocked)
  const[boForm,setBoForm]=useState(false);
  const[boF,setBoF]=useState({start_date:"",end_date:"",reason:"",location_id:""});
  const[boDel,setBoDel]=useState(null);
  const saveBlackout=async()=>{
    if(!boF.start_date||!boF.end_date){tt("Pick start and end dates");return;}
    if(boF.end_date<boF.start_date){tt("End must be on or after start");return;}
    try{
      await apiFetch("/api/scheduling/v3/admin/blackout-periods",{method:"POST",body:JSON.stringify({start_date:boF.start_date,end_date:boF.end_date,reason:boF.reason||null,location_id:boF.location_id||null})});
      await fetchAll();setBoForm(false);setBoF({start_date:"",end_date:"",reason:"",location_id:""});tt("Blackout added");
    }catch(e){tt(e.message);}
  };
  const delBlackout=async id=>{try{await apiFetch(`/api/scheduling/v3/admin/blackout-periods/${id}`,{method:"DELETE"});await fetchAll();setBoDel(null);tt("Removed");}catch(e){tt(e.message);}};

  const ATABS=[{id:"templates",l:"Schedule Templates"},{id:"rules",l:"Rules"},{id:"closed",l:"Closed Dates"},{id:"blackouts",l:"Blackouts"},{id:"announcements",l:"Announcements"},{id:"hours",l:"Business Hours"}];
  const formCard=children=><div style={{...CD,borderColor:"#3b82f6",background:"#f0f7ff",marginBottom:16}}>{children}</div>;
  const label=txt=><div style={{fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"}}>{txt}</div>;
  const editBtn=onClick=><button onClick={onClick} style={{...IB,marginLeft:4}}>✎</button>;
  const delBtn=onClick=><button onClick={onClick} style={{...IB,color:"#dc2626",marginLeft:4}}>✕</button>;
  const cancelBtn=onClick=><button onClick={onClick} style={{...BTN,background:"#f3f4f6",color:"#374151"}}>Cancel</button>;
  const saveBtn=onClick=><button onClick={onClick} style={{...BTN,flex:1}}>Save</button>;
  return <div style={{paddingTop:16}}>
    <h2 style={{fontSize:18,fontWeight:700,color:"#111",marginBottom:12}}>Admin</h2>
    <div style={{display:"flex",gap:6,marginBottom:16,flexWrap:"wrap"}}>
      {ATABS.map(t=><button key={t.id} onClick={()=>setAdminTab(t.id)} style={{padding:"6px 12px",borderRadius:6,fontSize:12,fontWeight:600,border:`1px solid ${adminTab===t.id?"#3b82f6":"#e5e7eb"}`,background:adminTab===t.id?"#3b82f6":"#fff",color:adminTab===t.id?"#fff":"#6b7280",cursor:"pointer"}}>{t.l}</button>)}
    </div>
    {adminTab==="templates"&&<div>
      <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:12}}>
        <h3 style={{...SH,margin:0}}>Schedule Templates</h3>
        <button onClick={openNewTpl} style={{...BTN,padding:"7px 14px",fontSize:13}}>+ Add</button>
      </div>
      {tplForm!==null&&formCard(<>
        {label("Template Name")}
        <input style={{...SI,marginBottom:10}} placeholder="e.g. Full Day" value={tplF.template_name} onChange={e=>setTplF({...tplF,template_name:e.target.value})}/>
        <div style={{display:"flex",gap:12,marginBottom:12}}>
          <div style={{flex:1}}>{label("Start")}<select style={SI} value={tplF.start} onChange={e=>setTplF({...tplF,start:e.target.value})}>{TOPTS.map(t=><option key={t}>{t}</option>)}</select></div>
          <div style={{flex:1}}>{label("End")}<select style={SI} value={tplF.end} onChange={e=>setTplF({...tplF,end:e.target.value})}>{TOPTS.map(t=><option key={t}>{t}</option>)}</select></div>
        </div>
        {pT(tplF.end)>pT(tplF.start)&&<div style={{background:"#f0fdf4",borderRadius:6,padding:8,textAlign:"center",fontSize:13,color:"#166534",fontWeight:500,marginBottom:12}}>{tplF.start} – {tplF.end} · {fmtH(pT(tplF.end)-pT(tplF.start))}</div>}
        <div style={{display:"flex",gap:8}}>{saveBtn(saveTpl)}{cancelBtn(()=>setTplForm(null))}</div>
      </>)}
      {tpls.map(t=><div key={t.id} style={CD}>
        {tplDel===t.id
          ?<div><div style={{fontWeight:600,color:"#dc2626",marginBottom:8,fontSize:14}}>Delete "{t.template_name}"?</div><div style={{display:"flex",gap:6}}><button onClick={()=>delTpl(t.id)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#fee2e2",color:"#991b1b"}}>Delete</button><button onClick={()=>setTplDel(null)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#f3f4f6",color:"#374151"}}>Cancel</button></div></div>
          :<div style={{display:"flex",alignItems:"center",justifyContent:"space-between"}}>
            <div><div style={{fontWeight:600,fontSize:14,color:"#111"}}>{t.template_name}</div><div style={{fontSize:13,color:"#6b7280",marginTop:2}}>{apiTime(t.start_time)} – {apiTime(t.end_time)} · {fmtH(t.hours_scheduled)}</div></div>
            <div style={{display:"flex"}}>{editBtn(()=>openEditTpl(t))}{delBtn(()=>setTplDel(t.id))}</div>
          </div>}
      </div>)}
      {tpls.length===0&&!tplForm&&<div style={{textAlign:"center",padding:32,color:"#9ca3af"}}>No templates yet</div>}
    </div>}
    {adminTab==="rules"&&<div>
      <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:12}}>
        <h3 style={{...SH,margin:0}}>Scheduling Rules</h3>
        <button onClick={()=>{setRuleF(emptyRuleF);setRuleForm({});}} style={{...BTN,padding:"7px 14px",fontSize:13}}>+ Add</button>
      </div>
      {ruleForm!==null&&formCard(<>
        {label("Rule Name")}<input style={{...SI,marginBottom:10}} placeholder="e.g. Max consecutive days" value={ruleF.rule_name} onChange={e=>setRuleF({...ruleF,rule_name:e.target.value})}/>
        {label("Description")}<input style={{...SI,marginBottom:10}} placeholder="Optional description" value={ruleF.rule_description} onChange={e=>setRuleF({...ruleF,rule_description:e.target.value})}/>
        <div style={{display:"flex",gap:10,marginBottom:10}}>
          <div style={{flex:1}}>{label("Location")}<select style={SI} value={ruleF.location_id} onChange={e=>setRuleF({...ruleF,location_id:e.target.value})}><option value="">All Locations</option>{locs.map(l=><option key={l.id} value={l.id}>{l.name}</option>)}</select></div>
          <div style={{flex:1}}>{label("Rule Type")}<select style={SI} value={ruleF.rule_type} onChange={e=>setRuleF({...ruleF,rule_type:e.target.value})}>{RULE_TYPES.map(r=><option key={r}>{r}</option>)}</select></div>
        </div>
        <div style={{display:"flex",gap:10,marginBottom:12}}>
          <div style={{flex:1}}>{label("Param 1")}<input style={SI} placeholder="e.g. 3" value={ruleF.param_1} onChange={e=>setRuleF({...ruleF,param_1:e.target.value})}/></div>
          <div style={{flex:1}}>{label("Param 2")}<input style={SI} placeholder="Optional" value={ruleF.param_2} onChange={e=>setRuleF({...ruleF,param_2:e.target.value})}/></div>
        </div>
        <div style={{display:"flex",gap:8}}>{saveBtn(saveRule)}{cancelBtn(()=>setRuleForm(null))}</div>
      </>)}
      {rules.map(r=><div key={r.id} style={{...CD,borderLeft:"3px solid #3b82f6"}}>
        {ruleDel===r.id
          ?<div><div style={{fontWeight:600,color:"#dc2626",marginBottom:8,fontSize:14}}>Delete "{r.rule_name}"?</div><div style={{display:"flex",gap:6}}><button onClick={()=>delRule(r.id)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#fee2e2",color:"#991b1b"}}>Delete</button><button onClick={()=>setRuleDel(null)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#f3f4f6",color:"#374151"}}>Cancel</button></div></div>
          :<div style={{display:"flex",alignItems:"flex-start",justifyContent:"space-between"}}>
            <div style={{flex:1}}><div style={{fontWeight:600,fontSize:14,color:"#111"}}>{r.rule_name}</div>{r.rule_description&&<div style={{fontSize:13,color:"#6b7280",marginTop:2}}>{r.rule_description}</div>}<div style={{display:"flex",alignItems:"center",gap:6,marginTop:4,flexWrap:"wrap"}}><span style={{fontSize:11,color:"#9ca3af"}}>{r.rule_type}{r.param_1?` · ${r.param_1}`:""}{r.user_name?` · ${r.user_name}`:""}</span>{(()=>{const lc=r.location_id?getLC(r.location_id,locs):null;return <span style={{fontSize:10,fontWeight:600,padding:"1px 6px",borderRadius:4,background:lc?lc.bg:"#f3f4f6",color:lc?lc.tx:"#6b7280"}}>{r.location_name||"All Locations"}</span>;})()}</div></div>
            <div style={{display:"flex",flexShrink:0}}>{editBtn(()=>openEditRule(r))}{delBtn(()=>setRuleDel(r.id))}</div>
          </div>}
      </div>)}
      {rules.length===0&&!ruleForm&&<div style={{textAlign:"center",padding:32,color:"#9ca3af"}}>No rules defined</div>}
    </div>}
    {adminTab==="closed"&&<div>
      <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:12}}>
        <h3 style={{...SH,margin:0}}>Closed Dates</h3>
        <button onClick={()=>{setCdF({closed_date:"",reason:"",location_id:""});setCdForm(true);}} style={{...BTN,padding:"7px 14px",fontSize:13}}>+ Add</button>
      </div>
      <div style={{fontSize:12,color:"#9ca3af",marginBottom:12}}>One-off days the salon is closed (holidays, training, etc.). Schedule cells turn red and shifts can't be created on these dates.</div>
      {cdForm&&formCard(<>
        {label("Date")}<input type="date" style={{...SI,marginBottom:10}} value={cdF.closed_date} onChange={e=>setCdF({...cdF,closed_date:e.target.value})}/>
        {label("Reason")}<input style={{...SI,marginBottom:10}} placeholder="e.g. Christmas Day" value={cdF.reason} onChange={e=>setCdF({...cdF,reason:e.target.value})}/>
        {label("Location")}<select style={{...SI,marginBottom:12}} value={cdF.location_id} onChange={e=>setCdF({...cdF,location_id:e.target.value})}><option value="">All Locations</option>{locs.map(l=><option key={l.id} value={l.id}>{l.name}</option>)}</select>
        <div style={{display:"flex",gap:8}}>{saveBtn(saveClosed)}{cancelBtn(()=>setCdForm(false))}</div>
      </>)}
      {closed.map(c=><div key={c.id} style={{...CD,borderLeft:"3px solid #dc2626"}}>
        {cdDel===c.id
          ?<div><div style={{fontWeight:600,color:"#dc2626",marginBottom:8,fontSize:14}}>Remove closed date for {c.closed_date}?</div><div style={{display:"flex",gap:6}}><button onClick={()=>delClosed(c.id)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#fee2e2",color:"#991b1b"}}>Remove</button><button onClick={()=>setCdDel(null)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#f3f4f6",color:"#374151"}}>Cancel</button></div></div>
          :<div style={{display:"flex",alignItems:"center",justifyContent:"space-between"}}>
            <div style={{flex:1}}>
              <div style={{fontWeight:600,fontSize:14,color:"#111"}}>{c.closed_date}</div>
              {c.reason&&<div style={{fontSize:13,color:"#6b7280",marginTop:2}}>{c.reason}</div>}
              <div style={{display:"flex",alignItems:"center",gap:6,marginTop:4}}>{(()=>{const lc=c.location_id?getLC(c.location_id,locs):null;return <span style={{fontSize:10,fontWeight:600,padding:"1px 6px",borderRadius:4,background:lc?lc.bg:"#f3f4f6",color:lc?lc.tx:"#6b7280"}}>{c.location_name||"All Locations"}</span>;})()}</div>
            </div>
            {delBtn(()=>setCdDel(c.id))}
          </div>}
      </div>)}
      {closed.length===0&&!cdForm&&<div style={{textAlign:"center",padding:32,color:"#9ca3af"}}>No closed dates</div>}
    </div>}
    {adminTab==="blackouts"&&<div>
      <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:12}}>
        <h3 style={{...SH,margin:0}}>Blackout Periods</h3>
        <button onClick={()=>{setBoF({start_date:"",end_date:"",reason:"",location_id:""});setBoForm(true);}} style={{...BTN,padding:"7px 14px",fontSize:13}}>+ Add</button>
      </div>
      <div style={{fontSize:12,color:"#9ca3af",marginBottom:12}}>Date ranges where staff can't request time off (peak season, holidays, etc.). Doesn't close the salon — just blocks new requests.</div>
      {boForm&&formCard(<>
        <div style={{display:"flex",gap:10,marginBottom:10}}>
          <div style={{flex:1}}>{label("Start")}<input type="date" style={SI} value={boF.start_date} onChange={e=>setBoF({...boF,start_date:e.target.value})}/></div>
          <div style={{flex:1}}>{label("End")}<input type="date" style={SI} value={boF.end_date} onChange={e=>setBoF({...boF,end_date:e.target.value})}/></div>
        </div>
        {label("Reason")}<input style={{...SI,marginBottom:10}} placeholder="e.g. Holiday rush — back-to-school" value={boF.reason} onChange={e=>setBoF({...boF,reason:e.target.value})}/>
        {label("Location")}<select style={{...SI,marginBottom:12}} value={boF.location_id} onChange={e=>setBoF({...boF,location_id:e.target.value})}><option value="">All Locations</option>{locs.map(l=><option key={l.id} value={l.id}>{l.name}</option>)}</select>
        <div style={{display:"flex",gap:8}}>{saveBtn(saveBlackout)}{cancelBtn(()=>setBoForm(false))}</div>
      </>)}
      {bo.map(b=><div key={b.id} style={{...CD,borderLeft:"3px solid #f59e0b"}}>
        {boDel===b.id
          ?<div><div style={{fontWeight:600,color:"#dc2626",marginBottom:8,fontSize:14}}>Remove this blackout?</div><div style={{display:"flex",gap:6}}><button onClick={()=>delBlackout(b.id)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#fee2e2",color:"#991b1b"}}>Remove</button><button onClick={()=>setBoDel(null)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#f3f4f6",color:"#374151"}}>Cancel</button></div></div>
          :<div style={{display:"flex",alignItems:"center",justifyContent:"space-between"}}>
            <div style={{flex:1}}>
              <div style={{fontWeight:600,fontSize:14,color:"#111"}}>{b.start_date}{b.end_date!==b.start_date?` – ${b.end_date}`:""}</div>
              {b.reason&&<div style={{fontSize:13,color:"#6b7280",marginTop:2}}>{b.reason}</div>}
              <div style={{display:"flex",alignItems:"center",gap:6,marginTop:4}}>{(()=>{const lc=b.location_id?getLC(b.location_id,locs):null;return <span style={{fontSize:10,fontWeight:600,padding:"1px 6px",borderRadius:4,background:lc?lc.bg:"#f3f4f6",color:lc?lc.tx:"#6b7280"}}>{b.location_name||"All Locations"}</span>;})()}</div>
            </div>
            {delBtn(()=>setBoDel(b.id))}
          </div>}
      </div>)}
      {bo.length===0&&!boForm&&<div style={{textAlign:"center",padding:32,color:"#9ca3af"}}>No blackout periods</div>}
    </div>}
    {adminTab==="announcements"&&<div>
      <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:12}}>
        <h3 style={{...SH,margin:0}}>Announcements</h3>
        <button onClick={()=>setAnnForm(true)} style={{...BTN,padding:"7px 14px",fontSize:13}}>+ Post</button>
      </div>
      {annForm&&formCard(<>
        {label("Title")}<input style={{...SI,marginBottom:10}} placeholder="Announcement title" value={annF.title} onChange={e=>setAnnF({...annF,title:e.target.value})}/>
        {label("Message")}<textarea style={{...SI,marginBottom:12,resize:"vertical",minHeight:80}} placeholder="Message body..." value={annF.body} onChange={e=>setAnnF({...annF,body:e.target.value})}/>
        <div style={{display:"flex",gap:8}}>{saveBtn(saveAnn)}{cancelBtn(()=>setAnnForm(false))}</div>
      </>)}
      {ann.map(a=><div key={a.id} style={{...CD,borderLeft:"3px solid #3b82f6"}}>
        {annDel===a.id
          ?<div><div style={{fontWeight:600,color:"#dc2626",marginBottom:8,fontSize:14}}>Remove "{a.title}"?</div><div style={{display:"flex",gap:6}}><button onClick={()=>delAnn(a.id)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#fee2e2",color:"#991b1b"}}>Remove</button><button onClick={()=>setAnnDel(null)} style={{...BTN,fontSize:12,padding:"6px 12px",background:"#f3f4f6",color:"#374151"}}>Cancel</button></div></div>
          :<div style={{display:"flex",alignItems:"flex-start",justifyContent:"space-between"}}>
            <div style={{flex:1}}><div style={{fontWeight:600,fontSize:14,color:"#111"}}>{a.title}</div><div style={{fontSize:13,color:"#6b7280",marginTop:4}}>{a.body}</div></div>
            {delBtn(()=>setAnnDel(a.id))}
          </div>}
      </div>)}
      {ann.length===0&&!annForm&&<div style={{textAlign:"center",padding:32,color:"#9ca3af"}}>No active announcements</div>}
    </div>}
    {adminTab==="hours"&&<div>
      <h3 style={{...SH,marginBottom:12}}>Business Hours</h3>
      {[...new Set(bh.map(h=>h.location_name))].map(l=><div key={l} style={{...CD,marginBottom:16}}>
        <div style={{fontWeight:600,fontSize:14,color:"#111",marginBottom:10}}>{l}</div>
        {bh.filter(h=>h.location_name===l).sort((a,b)=>a.day_of_week-b.day_of_week).map(h=>
          <div key={h.id}>
            {bhEdit===h.id
              ?<div style={{background:"#f0f7ff",borderRadius:8,padding:12,marginBottom:8}}>
                <div style={{fontWeight:600,fontSize:13,color:"#374151",marginBottom:8}}>{h.day_name}</div>
                <div style={{display:"flex",alignItems:"center",gap:8,marginBottom:10}}><label style={{display:"flex",alignItems:"center",gap:6,fontSize:13,cursor:"pointer"}}><input type="checkbox" checked={bhF.is_open} onChange={e=>setBhF({...bhF,is_open:e.target.checked})}/> Open</label></div>
                {bhF.is_open&&<div style={{display:"flex",gap:10,marginBottom:10}}>
                  <div style={{flex:1}}>{label("Open")}<input type="time" style={SI} value={bhF.open_time?.slice(0,5)||""} onChange={e=>setBhF({...bhF,open_time:e.target.value+":00"})}/></div>
                  <div style={{flex:1}}>{label("Close")}<input type="time" style={SI} value={bhF.close_time?.slice(0,5)||""} onChange={e=>setBhF({...bhF,close_time:e.target.value+":00"})}/></div>
                </div>}
                <div style={{display:"flex",gap:8}}><button onClick={saveBh} style={{...BTN,flex:1,padding:"8px"}}>Save</button><button onClick={()=>setBhEdit(null)} style={{...BTN,background:"#f3f4f6",color:"#374151",padding:"8px 14px"}}>Cancel</button></div>
              </div>
              :<div style={{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"6px 0",borderBottom:"1px solid #f3f4f6",fontSize:13}}>
                <span style={{color:"#374151"}}>{h.day_name}</span>
                <div style={{display:"flex",alignItems:"center",gap:8}}><span style={{color:"#6b7280"}}>{h.is_open?`${h.open_time} – ${h.close_time}`:"Closed"}</span>{editBtn(()=>openEditBh(h))}</div>
              </div>}
          </div>
        )}
      </div>)}
    </div>}
    <div style={{textAlign:"center",padding:24,color:"#9ca3af",fontSize:12}}>Cookie Cutters · v2.0</div>
  </div>;
}

// ══════════════════════════════════
//  MAIN APP COMPONENT
// ══════════════════════════════════
// ═══ AUTH SHARED ═══
const AUTH_CARD = {background:"#fff",borderRadius:16,padding:32,width:"100%",maxWidth:360,boxShadow:"0 4px 24px rgba(0,0,0,.08)"};
const AUTH_WRAP = {display:"flex",alignItems:"center",justifyContent:"center",minHeight:"100vh",background:"#f8f9fb",padding:24};
const ERROR_BOX = {background:"#fee2e2",color:"#991b1b",padding:"10px 14px",borderRadius:8,fontSize:13,marginBottom:16,fontWeight:500};
const SUCCESS_BOX = {background:"#d1fae5",color:"#065f46",padding:"10px 14px",borderRadius:8,fontSize:13,marginBottom:16,fontWeight:500};
const FIELD_LABEL = {display:"block",fontSize:11,fontWeight:600,color:"#6b7280",marginBottom:4,textTransform:"uppercase"};
// Eye-icon button for show/hide password — positioned absolutely inside the input wrapper
const EyeButton = ({on,onToggle}) =>
  <button type="button" onClick={onToggle} tabIndex={-1}
    title={on?"Hide password":"Show password"}
    style={{position:"absolute",right:6,top:"50%",transform:"translateY(-50%)",
            background:"none",border:"none",cursor:"pointer",fontSize:16,padding:"4px 8px",
            color:"#9ca3af",lineHeight:1}}>
    {on?"🙈":"👁"}
  </button>;

// Header used on every auth screen
const AuthHeader = ({subtitle}) =>
  <div style={{textAlign:"center",marginBottom:24}}>
    <div style={{fontSize:11,fontWeight:700,color:"#9ca3af",textTransform:"uppercase",letterSpacing:2,marginBottom:6}}>Cookie Cutters</div>
    <div style={{fontSize:22,fontWeight:700,color:"#111"}}>Staff Scheduling</div>
    {subtitle && <div style={{fontSize:13,color:"#9ca3af",marginTop:6}}>{subtitle}</div>}
  </div>;


// Translate Supabase auth error codes to user-friendly copy.
const friendlyAuthError = (info) => {
  const code = info?.errorCode || info?.error || "";
  const desc = info?.description || "";
  if(code === "otp_expired" || /expired/i.test(desc))
    return "Your invitation link has expired. Ask your manager to resend the invite.";
  if(code === "access_denied")
    return "That invitation link is no longer valid. Ask your manager to resend it.";
  if(desc) return desc;
  return "Something went wrong with that link. Please try again or ask your manager for a fresh invitation.";
};

// PKCE token exchange — shows a brief loading state while we swap the
// token_hash for a real access_token, then bubbles up to the parent.
function PkceExchange({tokenHash, type, onSuccess, onError}) {
  useEffect(() => {
    let cancelled = false;
    exchangePkceToken(tokenHash, type)
      .then(at => { if(!cancelled) onSuccess(at); })
      .catch(e => { if(!cancelled) onError(e.message); });
    return () => { cancelled = true; };
  }, [tokenHash, type]);
  return <div style={AUTH_WRAP}>
    <div style={{...AUTH_CARD, textAlign:"center"}}>
      <AuthHeader subtitle="Verifying your invitation..."/>
      <p style={{color:"#6b7280",fontSize:13,margin:"16px 0 0 0"}}>Just a moment.</p>
    </div>
  </div>;
}


// ═══ LOGIN SCREEN ═══
function LoginScreen({onLogin, initialError, onDismissError}) {
  const[email,setEmail]=useState("");
  const[password,setPassword]=useState("");
  const[showPw,setShowPw]=useState(false);
  const[loading,setLoading]=useState(false);
  const[error,setError]=useState(initialError || "");
  const[mode,setMode]=useState("login");          // "login" | "forgot"
  const[recoveryEmail,setRecoveryEmail]=useState("");
  const[recoverySent,setRecoverySent]=useState(false);
  // Clear initialError when the user starts typing
  useEffect(() => {
    if(error && initialError && (email || password)) {
      setError(""); onDismissError && onDismissError();
    }
  }, [email, password]);

  const login = async () => {
    if(!email.trim()||!password.trim()){setError("Enter your email and password");return;}
    setLoading(true);setError("");
    try {
      const session = await supabaseLogin(email.trim(), password);
      setSession(session);
      onLogin(session.access_token);
    } catch(e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  };

  const sendRecovery = async () => {
    if(!recoveryEmail.trim()){setError("Enter your email");return;}
    setLoading(true);setError("");
    try {
      const r = await fetch(`${SUPABASE_URL}/auth/v1/recover`, {
        method: "POST",
        headers: {"Content-Type":"application/json","apikey":SUPABASE_ANON_KEY},
        body: JSON.stringify({email: recoveryEmail.trim()})
      });
      // Treat any non-error response as success — Supabase intentionally
      // returns 200 even when the email is unknown, to avoid leaking which
      // addresses are registered.
      if(!r.ok) {
        const d = await r.json().catch(()=>({}));
        throw new Error(d.error_description || d.msg || `Couldn't send reset email (${r.status})`);
      }
      setRecoverySent(true);
    } catch(e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  };

  // ── Forgot-password success state ──
  if(mode==="forgot" && recoverySent) {
    return <div style={AUTH_WRAP}>
      <div style={AUTH_CARD}>
        <AuthHeader/>
        <div style={SUCCESS_BOX}>Reset link sent</div>
        <p style={{color:"#374151",fontSize:14,lineHeight:1.5,marginBottom:8}}>
          If <strong>{recoveryEmail}</strong> matches an account, we've sent a link to reset your password.
        </p>
        <p style={{color:"#9ca3af",fontSize:12,lineHeight:1.5,marginBottom:24}}>
          Check spam if it doesn't arrive in a couple minutes. The link expires after one hour.
        </p>
        <button onClick={()=>{setMode("login");setRecoverySent(false);setRecoveryEmail("");setError("");}}
          style={{...BTN,width:"100%",padding:"13px",fontSize:15}}>
          Back to sign in
        </button>
      </div>
    </div>;
  }

  // ── Forgot-password form ──
  if(mode==="forgot") {
    return <div style={AUTH_WRAP}>
      <form onSubmit={e=>{e.preventDefault();sendRecovery();}} method="post" action="#" style={AUTH_CARD}>
        <AuthHeader subtitle="Reset your password"/>
        {error && <div style={ERROR_BOX}>{error}</div>}
        <p style={{color:"#6b7280",fontSize:13,marginBottom:16,lineHeight:1.5}}>
          Enter the email you sign in with and we'll send you a reset link.
        </p>
        <div style={{marginBottom:20}}>
          <label htmlFor="cc-recover-email" style={FIELD_LABEL}>Email</label>
          <input id="cc-recover-email" name="email" type="email" autoComplete="username" inputMode="email" required
            style={{...SI,fontSize:15}} placeholder="you@example.com"
            value={recoveryEmail} onChange={e=>setRecoveryEmail(e.target.value)} autoFocus/>
        </div>
        <button type="submit" disabled={loading} style={{...BTN,width:"100%",padding:"13px",fontSize:15,opacity:loading?.7:1}}>
          {loading?"Sending...":"Send reset link"}
        </button>
        <button type="button" onClick={()=>{setMode("login");setError("");}}
          style={{...BTN,width:"100%",padding:"11px",fontSize:13,marginTop:10,background:"transparent",color:"#6b7280"}}>
          Back to sign in
        </button>
      </form>
    </div>;
  }

  // ── Login form (default) ──
  return <div style={AUTH_WRAP}>
    <form onSubmit={e=>{e.preventDefault();login();}} method="post" action="#" style={AUTH_CARD}>
      <AuthHeader subtitle="Sign in to your account"/>
      {error && <div style={ERROR_BOX}>{error}</div>}
      <div style={{marginBottom:14}}>
        <label htmlFor="cc-email" style={FIELD_LABEL}>Email</label>
        <input id="cc-email" name="email" type="email" autoComplete="username" inputMode="email" required
          style={{...SI,fontSize:15}} placeholder="you@example.com"
          value={email} onChange={e=>setEmail(e.target.value)} autoFocus/>
      </div>
      <div style={{marginBottom:20}}>
        <label htmlFor="cc-password" style={FIELD_LABEL}>Password</label>
        <div style={{position:"relative"}}>
          <input id="cc-password" name="password" type={showPw?"text":"password"} autoComplete="current-password" required
            style={{...SI,fontSize:15,paddingRight:40}} placeholder="••••••••"
            value={password} onChange={e=>setPassword(e.target.value)}/>
          <EyeButton on={showPw} onToggle={()=>setShowPw(!showPw)}/>
        </div>
      </div>
      <button type="submit" disabled={loading} style={{...BTN,width:"100%",padding:"13px",fontSize:15,opacity:loading?.7:1}}>
        {loading?"Signing in...":"Sign In"}
      </button>
      <div style={{textAlign:"center",marginTop:14}}>
        <button type="button" onClick={()=>{setMode("forgot");setError("");setRecoveryEmail(email);}}
          style={{background:"none",border:"none",cursor:"pointer",fontSize:13,color:"#3b82f6",fontWeight:500}}>
          Forgot password?
        </button>
      </div>
      <div style={{textAlign:"center",marginTop:16,fontSize:12,color:"#9ca3af"}}>
        Contact your manager if you need access
      </div>
    </form>
  </div>;
}


// ═══ SET PASSWORD SCREEN ═══
// Shown when the user clicks an email link with an access_token in the
// URL hash. Handles three flows that all need a password set:
//   - mode="invite"   → first-time setup after admin invite. Copy: "Welcome! Create your password"
//   - mode="signup"   → self-signup confirmation. Same UI as invite.
//   - mode="recovery" → forgot-password flow. Copy: "Reset your password"
// In all cases we PUT /auth/v1/user with the new password using the
// access token from the URL — Supabase upgrades it to a normal session.
function ResetPasswordScreen({recoveryToken,mode,onComplete}) {
  const[password,setPassword]=useState("");
  const[confirm,setConfirm]=useState("");
  const[showPw,setShowPw]=useState(false);
  const[loading,setLoading]=useState(false);
  const[error,setError]=useState("");

  const isInvite = mode === "invite" || mode === "signup";
  const copy = isInvite ? {
    subtitle: "Welcome — create your password",
    intro: "Set a password for your account. You'll use this every time you sign in to view your schedule.",
    pwLabel: "Choose a password",
    confirmLabel: "Confirm password",
    submit: "Create Account",
    submitting: "Setting up...",
  } : {
    subtitle: "Reset your password",
    intro: "Choose a new password for your scheduling account.",
    pwLabel: "New password",
    confirmLabel: "Confirm",
    submit: "Update Password",
    submitting: "Updating...",
  };

  const submit = async () => {
    if(password.length<8){setError("Password must be at least 8 characters");return;}
    if(password!==confirm){setError("Passwords don't match");return;}
    setLoading(true);setError("");
    try {
      const r = await fetch(`${SUPABASE_URL}/auth/v1/user`, {
        method: "PUT",
        headers: {
          "Content-Type":"application/json",
          "apikey":SUPABASE_ANON_KEY,
          "Authorization":`Bearer ${recoveryToken}`,
        },
        body: JSON.stringify({password})
      });
      if(!r.ok) {
        const d = await r.json().catch(()=>({}));
        throw new Error(d.error_description || d.msg || `Couldn't set password (${r.status})`);
      }
      // Token is now upgraded to a real session by Supabase. Stash it so
      // the app picks up the user.
      const data = await r.json();
      setSession({access_token:recoveryToken, refresh_token:data.refresh_token||"", user:data});
      onComplete(recoveryToken);
    } catch(e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  };

  return <div style={AUTH_WRAP}>
    <form onSubmit={e=>{e.preventDefault();submit();}} method="post" action="#" style={AUTH_CARD}>
      <AuthHeader subtitle={copy.subtitle}/>
      {error && <div style={ERROR_BOX}>{error}</div>}
      {isInvite && <p style={{color:"#6b7280",fontSize:13,marginTop:0,marginBottom:18,lineHeight:1.5}}>{copy.intro}</p>}
      <div style={{marginBottom:14}}>
        <label htmlFor="cc-newpw" style={FIELD_LABEL}>{copy.pwLabel}</label>
        <div style={{position:"relative"}}>
          <input id="cc-newpw" name="password" type={showPw?"text":"password"} autoComplete="new-password" required minLength={8}
            style={{...SI,fontSize:15,paddingRight:40}} placeholder="At least 8 characters"
            value={password} onChange={e=>setPassword(e.target.value)} autoFocus/>
          <EyeButton on={showPw} onToggle={()=>setShowPw(!showPw)}/>
        </div>
      </div>
      <div style={{marginBottom:20}}>
        <label htmlFor="cc-newpw2" style={FIELD_LABEL}>{copy.confirmLabel}</label>
        <input id="cc-newpw2" name="password-confirm" type={showPw?"text":"password"} autoComplete="new-password" required
          style={{...SI,fontSize:15}} placeholder="Re-enter the password"
          value={confirm} onChange={e=>setConfirm(e.target.value)}/>
      </div>
      <button type="submit" disabled={loading} style={{...BTN,width:"100%",padding:"13px",fontSize:15,opacity:loading?.7:1}}>
        {loading?copy.submitting:copy.submit}
      </button>
      {isInvite && <p style={{color:"#9ca3af",fontSize:11,marginTop:14,marginBottom:0,textAlign:"center",lineHeight:1.5}}>By creating an account, you agree to access this app for work purposes only.</p>}
    </form>
  </div>;
}

// Detect a Supabase auth redirect in the URL. Returns one of:
//   {flow:"implicit", token, type}              — hash has access_token
//   {flow:"pkce", tokenHash, type}              — query has token_hash (newer flow)
//   {flow:"error", error, errorCode, description}  — expired/invalid link
//   null                                         — no auth redirect detected
const consumeAuthToken = () => {
  // 1. Check hash (implicit flow — Supabase default)
  if(window.location.hash) {
    const hashStr = window.location.hash.replace(/^#/, "");
    const hp = new URLSearchParams(hashStr);
    const accessToken = hp.get("access_token");
    const type = hp.get("type");
    const error = hp.get("error") || hp.get("error_code");
    if(accessToken) {
      console.log("[Auth] Implicit-flow redirect detected:", type);
      window.history.replaceState(null, "", window.location.pathname + window.location.search);
      // Some flows omit type; default to recovery so we still show set-password
      return {flow:"implicit", token:accessToken, type: type || "recovery"};
    }
    if(error) {
      console.log("[Auth] Error in URL hash:", hp.get("error_code"), hp.get("error_description"));
      window.history.replaceState(null, "", window.location.pathname + window.location.search);
      return {flow:"error", error: hp.get("error"), errorCode: hp.get("error_code"), description: hp.get("error_description")};
    }
  }
  // 2. Check query (PKCE flow — newer Supabase configurations)
  if(window.location.search) {
    const qp = new URLSearchParams(window.location.search);
    const tokenHash = qp.get("token_hash") || qp.get("token");
    const qpType = qp.get("type");
    const qpError = qp.get("error") || qp.get("error_code");
    if(tokenHash && qpType) {
      console.log("[Auth] PKCE-flow redirect detected:", qpType);
      window.history.replaceState(null, "", window.location.pathname);
      return {flow:"pkce", tokenHash, type: qpType};
    }
    if(qpError) {
      console.log("[Auth] Error in URL query:", qp.get("error_code"), qp.get("error_description"));
      window.history.replaceState(null, "", window.location.pathname);
      return {flow:"error", error: qp.get("error"), errorCode: qp.get("error_code"), description: qp.get("error_description")};
    }
  }
  // Surface any unrecognized URL params for debugging
  if(window.location.hash || window.location.search.length > 1) {
    console.log("[Auth] URL has params but no recognized auth pattern. Hash:", window.location.hash, "Search:", window.location.search);
  }
  return null;
};

// Exchange a PKCE token_hash for an access_token (newer Supabase flow).
const exchangePkceToken = async (tokenHash, type) => {
  const r = await fetch(`${SUPABASE_URL}/auth/v1/verify`, {
    method: "POST",
    headers: {"Content-Type":"application/json","apikey":SUPABASE_ANON_KEY},
    body: JSON.stringify({type, token_hash: tokenHash})
  });
  const data = await r.json();
  if(!r.ok) throw new Error(data.error_description || data.msg || `Token exchange failed (${r.status})`);
  return data.access_token;
};

function App() {
  const[authRedirect,setAuthRedirect]=useState(()=>consumeAuthToken());
  const[token,setToken]=useState(()=>DEV_USER?null:getToken());
  const[view,setView]=useState("schedule");
  const[sv,setSv]=useState("calendar");
  const[ws,setWs]=useState(mondayOf(new Date()));
  const[loc,setLoc]=useState("All");
  const[toast,setToast]=useState(null);
  const[modal,setModal]=useState(null);
  const[loading,setLoading]=useState(true);
  const[jd,setJd]=useState("");
  const[drag,setDrag]=useState(null);
  const[me,setMe]=useState(null);
  // Platform-admin "acting as" — which franchise's data are we currently scoped to?
  // null = use the user's own home franchise. Set by PlatformView; persisted to
  // localStorage so apiFetch can forward it as X-Active-Franchise-Id on every call.
  const[activeFranchiseId, setAfIdState]=useState(()=>getActiveFranchiseId());
  const setActiveFranchise = (id) => { setActiveFranchiseId(id); setAfIdState(id||null); };
  const[users,setUsers]=useState([]);
  const[shifts,setShifts]=useState({});
  const[hasDrafts,setHasDrafts]=useState(false);
  const[tpls,setTpls]=useState([]);
  const[tor,setTor]=useState([]);
  const[closed,setClosed]=useState([]);
  const[bo,setBo]=useState([]);
  const[ann,setAnn]=useState([]);
  const[locs,setLocs]=useState([]);
  const[rules,setRules]=useState([]);
  const[bh,setBh]=useState([]);

  const tt=m=>{setToast(m);setTimeout(()=>setToast(null),3000)};
  const isOwner=me?.is_owner;
  const isMgr=me?.is_admin===true;
  const canE=isOwner||isMgr;
  const days=weekDays(ws);
  const wl=`${MS[days[0].getMonth()]} ${days[0].getDate()} – ${MS[days[6].getMonth()]} ${days[6].getDate()}, ${days[6].getFullYear()}`;
  const vL=isOwner?loc:me?.location_names?.[0]||"";
  const SCHEDULE_ROLES=["Stylist","Receptionist"];
  const vU=users.filter(u=>u.is_active!==false&&u.positions?.some(p=>SCHEDULE_ROLES.includes(p))&&(vL==="All"||u.location_names?.includes(vL)));
  const gs=(uid,d)=>shifts[`${uid}-${dk(d)}`]||null;
  const isCl=d=>closed.some(c=>c.closed_date===dk(d));
  const getTOR=(uid,d)=>tor.find(r=>r.user_id===uid&&dk(d)>=r.start_date&&dk(d)<=r.end_date);

  const fetchAll=useCallback(async()=>{
    try{
      // /me is NOT franchise-scoped, so it works for both regular users
      // (returns their own record) and platform admins (returns their home
      // record even when scoped into a different franchise)
      const[meData,u,locData]=await Promise.all([
        apiFetch("/api/scheduling/v3/users/me").catch(()=>null),
        apiFetch("/api/scheduling/v3/users?include_inactive=true"),
        apiFetch("/api/scheduling/v3/locations").catch(()=>[])
      ]);
      if(!u||!Array.isArray(u)){
        if(!DEV_USER){clearSession();setToken(null);setLoading(false);}
        return;
      }
      setUsers(u);
      setLocs(locData.map(l=>({id:l.location_id,name:l.location_name})));
      if(DEV_USER){
        setMe(u.find(x=>x.id==DEV_USER));
      } else if(meData){
        setMe(meData);
      } else {
        console.error("/me returned nothing — user not in v3 system");
        setMe(null);setLoading(false);
        return;
      }
      const[a,b,c,d,e,f,g]=await Promise.all([
        apiFetch("/api/scheduling/v3/templates").catch(()=>[]),apiFetch("/api/scheduling/v3/time-off").catch(()=>[]),
        apiFetch("/api/scheduling/v3/admin/closed-dates").catch(()=>[]),apiFetch("/api/scheduling/v3/admin/blackout-periods").catch(()=>[]),
        apiFetch("/api/scheduling/v3/admin/announcements").catch(()=>[]),apiFetch("/api/scheduling/v3/admin/scheduling-rules").catch(()=>[]),
        apiFetch("/api/scheduling/v3/admin/business-hours").catch(()=>[])
      ]);
      setTpls(a);setTor(b);setClosed(c);setBo(d);setAnn(e);setRules(f);setBh(g);
    }catch(e){console.error(e)}
  },[]);

  const fetchShifts=useCallback(async()=>{
    try{
      const data=await apiFetch(`/api/scheduling/v3/shifts?week_start=${dk(ws)}`);
      const map={};let dr=false;
      data.forEach(s=>{map[`${s.user_id}-${s.shift_date}`]={id:s.id,start:apiTime(s.start_time),end:apiTime(s.end_time),role:s.position_name,hours:s.hours_scheduled,status:s.status||"DRAFT",locId:s.location_id,locName:s.location_name};if((s.status||"DRAFT")==="DRAFT")dr=true});
      setShifts(map);setHasDrafts(dr);
    }catch(e){console.error(e)}
  },[ws]);

  useEffect(()=>{if(!DEV_USER&&!token){setLoading(false);return;}(async()=>{setLoading(true);await fetchAll();setLoading(false)})();},[token]);
  useEffect(()=>{if(!DEV_USER&&!token)return;fetchShifts();},[ws,token]);

  // Actions
  const saveShift=async(uid,dateStr,data,locId)=>{const pm={Stylist:1,Receptionist:2};const ex=shifts[`${uid}-${dateStr}`];try{if(ex?.id)await apiFetch(`/api/scheduling/v3/shifts/${ex.id}`,{method:"PUT",body:JSON.stringify({start_time:to24(data.start),end_time:to24(data.end),position_id:pm[data.role]||1})});else await apiFetch("/api/scheduling/v3/shifts",{method:"POST",body:JSON.stringify({user_id:uid,location_id:locId,shift_date:dateStr,start_time:to24(data.start),end_time:to24(data.end),position_id:pm[data.role]||1})});setModal(null);await fetchShifts();tt("Saved")}catch(e){tt(e.message)}};
  const delShift=async(uid,ds)=>{const ex=shifts[`${uid}-${ds}`];if(!ex?.id)return;try{await apiFetch(`/api/scheduling/v3/shifts/${ex.id}`,{method:"DELETE"});setModal(null);await fetchShifts();tt("Removed")}catch(e){tt(e.message)}};
  const moveShift=async(sid,nd)=>{try{await apiFetch(`/api/scheduling/v3/shifts/${sid}/move`,{method:"PUT",body:JSON.stringify({new_date:nd})});await fetchShifts();tt("Moved")}catch(e){tt(e.message)}};
  const pub=async()=>{const li=locs.find(l=>l.name===loc)?.id;try{await apiFetch("/api/scheduling/v3/shifts/publish",{method:"POST",body:JSON.stringify({week_start:dk(ws),location_id:li||undefined})});await fetchShifts();tt("Published!")}catch(e){tt(e.message)}};
  const unpub=async()=>{const li=locs.find(l=>l.name===loc)?.id;try{await apiFetch("/api/scheduling/v3/shifts/unpublish",{method:"POST",body:JSON.stringify({week_start:dk(ws),location_id:li||undefined})});await fetchShifts();tt("Unpublished")}catch(e){tt(e.message)}};
  // ─── Auto-Schedule (AI) ────────────────────────────────────────────────
  const[asOpen,setAsOpen]=useState(false);
  const[asProposal,setAsProposal]=useState(null);
  const[asLoading,setAsLoading]=useState(false);
  const[asMonth,setAsMonth]=useState(()=>{const d=new Date(ws);d.setMonth(d.getMonth()+1);return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}`;});
  const[asLocId,setAsLocId]=useState("");        // location chosen INSIDE the modal
  const[asPosSite,setAsPosSite]=useState("");    // optional override for demo mode
  // When the modal opens, default location to whatever's in the top selector,
  // or the first location if "All" is chosen
  useEffect(()=>{
    if(!asOpen) return;
    const cur=locs.find(l=>l.name===loc);
    setAsLocId(cur?.id || locs[0]?.id || "");
  },[asOpen,loc,locs]);
  const runAutoSchedule=async()=>{
    if(!asLocId){tt("Pick a location");return;}
    const posSite=asPosSite.trim()||asLocId;     // location.id == pos_site_id in Cookie Cutters
    setAsLoading(true);setAsProposal(null);
    try{
      const r=await apiFetch("/api/scheduling/v3/forecast/generate",{method:"POST",body:JSON.stringify({
        pos_site_id:posSite, month:asMonth,
      })});
      setAsProposal(r);
    }catch(e){tt(e.message);}
    finally{setAsLoading(false);}
  };
  const applyProposal=async()=>{
    if(!asLocId){tt("Pick a location");return;}
    if(asPosSite.trim()&&asPosSite.trim()!==asLocId){
      tt("Demo-mode previews can't be saved (different store than your franchise).");return;
    }
    setAsLoading(true);
    try{
      const r=await apiFetch("/api/scheduling/v3/forecast/generate",{method:"POST",body:JSON.stringify({
        pos_site_id:asLocId, month:asMonth, save_to_location_id:asLocId,
      })});
      tt(`Saved ${r.saved_count} draft shifts to your calendar`);
      setAsOpen(false);setAsProposal(null);
      await fetchShifts();
    }catch(e){tt(e.message);}
    finally{setAsLoading(false);}
  };
  const shiftW=n=>{const d=new Date(ws);d.setDate(d.getDate()+n*7);setWs(d)};

  // Weekly hours
  const wh={};let th=0;vU.forEach(u=>{let h=0;days.forEach(d=>{const s=gs(u.id,d);if(s)h+=pT(s.end)-pT(s.start)});wh[u.id]=h;th+=h});

  // Recovery / invite / signup flow: user clicked an email link.
  // Handle each flow separately:
  if(authRedirect?.flow === "implicit") return <ResetPasswordScreen
    recoveryToken={authRedirect.token}
    mode={authRedirect.type}
    onComplete={t=>{setAuthRedirect(null);setToken(t);}}/>;
  if(authRedirect?.flow === "pkce") return <PkceExchange
    tokenHash={authRedirect.tokenHash}
    type={authRedirect.type}
    onSuccess={accessToken=>setAuthRedirect({flow:"implicit", token:accessToken, type:authRedirect.type})}
    onError={msg=>setAuthRedirect({flow:"error", error:"exchange_failed", description:msg})}/>;
  // Errors (expired link, etc.) fall through to LoginScreen with a banner

  // Show login screen if no token and not using dev bypass.
  // If the user just hit an expired/invalid invite link, surface that here.
  if(!DEV_USER && !token) return <LoginScreen
    onLogin={t=>{setToken(t);}}
    initialError={authRedirect?.flow === "error" ? friendlyAuthError(authRedirect) : ""}
    onDismissError={()=>setAuthRedirect(null)}/>;

  if(loading||!me) return <div style={{display:"flex",alignItems:"center",justifyContent:"center",minHeight:"100vh"}}><div style={{textAlign:"center"}}><div style={{fontSize:18,fontWeight:700,color:"#111"}}>Cookie Cutters</div>{loading?<div style={{color:"#999",fontSize:14,marginTop:4}}>Loading...</div>:<div style={{marginTop:12}}><div style={{color:"#dc2626",fontSize:14,fontWeight:500}}>Account not linked</div><div style={{color:"#6b7280",fontSize:13,marginTop:6,maxWidth:280}}>Your login email doesn't match any user in the system. Ask your manager to update your email in the People tab.</div><button onClick={()=>{clearSession();setToken(null);}} style={{...BTN,marginTop:16,fontSize:13}}>Sign Out</button></div>}</div></div>;

  const canAdmin=canE;
  const isPlatformAdmin = me?.is_platform_admin === true;
  const tabs=[
    {id:"home",l:"Overview",i:"◷"},
    {id:"schedule",l:"Schedule",i:"▦"},
    {id:"timeoff",l:"Time Off",i:"☰"},
    ...(canAdmin?[{id:"team",l:"People",i:"◎"},{id:"admin",l:"Admin",i:"⚙"}]:[]),
    ...(isPlatformAdmin?[{id:"platform",l:"Platform",i:"✦"}]:[]),
  ];

  return <div style={{background:"#f8f9fb",minHeight:"100vh",maxWidth:960,margin:"0 auto"}}>
    <Toast msg={toast}/>
    {modal&&<ShiftModal {...modal} templates={tpls} locs={locs} onSave={(d,loc)=>saveShift(modal.uid,modal.dk,d,loc||modal.locId)} onDelete={()=>delShift(modal.uid,modal.dk)} onClose={()=>setModal(null)}/>}
    {asOpen&&<AutoScheduleModal {...{
      asMonth,setAsMonth,asLocId,setAsLocId,asPosSite,setAsPosSite,asProposal,setAsProposal,asLoading,
      runAutoSchedule,applyProposal,locs,
      onClose:()=>{setAsOpen(false);setAsProposal(null);setAsPosSite("");}
    }}/>}

    {/* Header */}
    <div style={{background:"#fff",borderBottom:"1px solid #e5e7eb",padding:"12px 16px",display:"flex",justifyContent:"space-between",alignItems:"center",position:"sticky",top:0,zIndex:100}}>
      <div>
        <div style={{fontSize:10,fontWeight:600,color:"#9ca3af",textTransform:"uppercase",letterSpacing:1}}>Cookie Cutters</div>
        <div style={{fontSize:15,fontWeight:700,color:"#111"}}>{me.display_name} {isOwner&&<span style={{fontSize:10,background:"#dbeafe",color:"#1e40af",padding:"2px 6px",borderRadius:4,marginLeft:4,fontWeight:600}}>OWNER</span>}{isMgr&&!isOwner&&<span style={{fontSize:10,background:"#f3e8ff",color:"#7c3aed",padding:"2px 6px",borderRadius:4,marginLeft:4,fontWeight:600}}>MANAGER</span>}</div>
      </div>
      <div style={{display:"flex",alignItems:"center",gap:8}}>
        {isOwner&&<select value={loc} onChange={e=>setLoc(e.target.value)} style={{padding:"6px 10px",borderRadius:6,border:"1px solid #d1d5db",fontSize:13,fontWeight:500,color:"#374151",background:"#fff"}}><option value="All">All Locations</option>{locs.map(l=><option key={l.id} value={l.name}>{l.name}</option>)}</select>}
        <button onClick={()=>setView("help")} title="Help / How-to guide" style={{width:30,height:30,borderRadius:"50%",border:"1px solid #e5e7eb",background:"#fff",fontSize:14,fontWeight:700,color:"#6b7280",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center"}}>?</button>
        {!DEV_USER&&<button onClick={()=>{clearSession();setToken(null);}} style={{padding:"6px 12px",borderRadius:6,border:"1px solid #e5e7eb",background:"#fff",fontSize:12,color:"#6b7280",cursor:"pointer"}}>Sign Out</button>}
      </div>
    </div>

    {/* Acting-as banner — visible whenever a platform admin is scoped into a different franchise */}
    {isPlatformAdmin && activeFranchiseId && activeFranchiseId !== me.franchise_id && <div style={{background:"#fef3c7",borderBottom:"1px solid #fcd34d",padding:"8px 16px",display:"flex",justifyContent:"space-between",alignItems:"center",gap:12,position:"sticky",top:60,zIndex:99}}>
      <div style={{fontSize:12,color:"#92400e",fontWeight:500}}>
        <span style={{fontWeight:700}}>⚠ Acting as another franchise.</span> All changes you make affect that franchise's data.
      </div>
      <button onClick={()=>{setActiveFranchise(null);fetchAll();}} style={{padding:"5px 10px",borderRadius:5,border:"1px solid #d97706",background:"#fff",color:"#92400e",fontSize:11,fontWeight:600,cursor:"pointer",whiteSpace:"nowrap"}}>← My franchise</button>
    </div>}

    <div style={{padding:"0 16px 100px"}}>
      {/* Overview */}
      {view==="home"&&<div style={{paddingTop:16}}>
        {ann.length>0&&<><h3 style={SH}>Announcements</h3>{ann.map(a=><div key={a.id} style={{...CD,borderLeft:"3px solid #3b82f6"}}><div style={{fontWeight:600,fontSize:14,color:"#111"}}>{a.title}</div><div style={{fontSize:13,color:"#6b7280",marginTop:4}}>{a.body}</div></div>)}</>}
        {(()=>{
          const today=dk(new Date());
          const upcoming=(bo||[]).filter(b=>b.end_date>=today).sort((a,b)=>a.start_date.localeCompare(b.start_date));
          if(!upcoming.length) return null;
          return <><h3 style={SH}>Upcoming Blackouts</h3>
            <div style={{fontSize:11,color:"#9ca3af",marginBottom:8,marginTop:-4}}>Time-off requests can't overlap these dates.</div>
            {upcoming.map(b=><div key={b.id} style={{...CD,borderLeft:"3px solid #f59e0b",padding:"10px 14px"}}>
              <div style={{fontWeight:600,fontSize:14,color:"#111"}}>{b.start_date}{b.end_date!==b.start_date?` – ${b.end_date}`:""}</div>
              {b.reason&&<div style={{fontSize:13,color:"#6b7280",marginTop:2}}>{b.reason}</div>}
              <div style={{display:"flex",alignItems:"center",gap:6,marginTop:4}}>{(()=>{const lc=b.location_id?getLC(b.location_id,locs):null;return <span style={{fontSize:10,fontWeight:600,padding:"1px 6px",borderRadius:4,background:lc?lc.bg:"#f3f4f6",color:lc?lc.tx:"#6b7280"}}>{b.location_name||"All Locations"}</span>;})()}</div>
            </div>)}
          </>;
        })()}
        <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:12,marginTop:16}}>
          <div style={CD}><div style={{fontSize:28,fontWeight:700,color:"#3b82f6"}}>{tor.filter(r=>r.status==="PENDING").length}</div><div style={{fontSize:12,color:"#6b7280"}}>Pending Requests</div></div>
          <div style={CD}><div style={{fontSize:28,fontWeight:700,color:"#3b82f6"}}>{vU.length}</div><div style={{fontSize:12,color:"#6b7280"}}>Team Members</div></div>
        </div>
        <button onClick={()=>setView("schedule")} style={{...BTN,width:"100%",marginTop:16,textAlign:"center"}}>View Schedule →</button>
      </div>}

      {/* Schedule */}
      {view==="schedule"&&<div style={{paddingTop:16}}>
        <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:12}}>
          <button onClick={()=>shiftW(-1)} style={NB}>‹</button>
          <div style={{textAlign:"center"}}>
            <div style={{fontSize:16,fontWeight:700,color:"#111"}}>{wl}</div>
            <div style={{display:"flex",gap:8,justifyContent:"center",marginTop:6}}>
              <button onClick={()=>setWs(mondayOf(new Date()))} style={{fontSize:12,fontWeight:500,color:"#3b82f6",background:"none",border:"1px solid #3b82f6",borderRadius:4,padding:"2px 8px",cursor:"pointer"}}>Today</button>
              <input type="date" value={jd} onChange={e=>{setJd(e.target.value);if(e.target.value){setWs(mondayOf(new Date(e.target.value+"T12:00")));setJd("")}}} style={{fontSize:12,border:"1px solid #d1d5db",borderRadius:4,padding:"2px 6px",color:"#374151"}}/>
            </div>
          </div>
          <button onClick={()=>shiftW(1)} style={NB}>›</button>
        </div>
        <div style={{display:"flex",gap:6,marginBottom:12,alignItems:"center"}}>
          <button onClick={()=>setSv("calendar")} style={{padding:"6px 14px",borderRadius:6,fontSize:12,fontWeight:600,border:`1px solid ${sv==="calendar"?"#3b82f6":"#e5e7eb"}`,background:sv==="calendar"?"#3b82f6":"#fff",color:sv==="calendar"?"#fff":"#6b7280",cursor:"pointer"}}>▦ Calendar</button>
          <button onClick={()=>setSv("list")} style={{padding:"6px 14px",borderRadius:6,fontSize:12,fontWeight:600,border:`1px solid ${sv==="list"?"#3b82f6":"#e5e7eb"}`,background:sv==="list"?"#3b82f6":"#fff",color:sv==="list"?"#fff":"#6b7280",cursor:"pointer"}}>☰ List</button>
          {canE&&<button onClick={()=>setAsOpen(true)} style={{
            marginLeft:"auto",
            padding:"6px 14px",borderRadius:6,fontSize:12,fontWeight:700,border:"none",
            background:"linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%)",
            color:"#fff",cursor:"pointer",
            boxShadow:"0 1px 4px rgba(124,58,237,.35)",
            display:"flex",alignItems:"center",gap:4,
          }}>✨ Auto-Schedule</button>}
        </div>
        {sv==="calendar"&&<CalendarGrid {...{days,canE,vU,gs,isCl,getTOR,wh,setModal,drag,setDrag,moveShift,locs,vL}}/>}
        {sv==="list"&&<ListView {...{days,canE,vU,gs,isCl,getTOR,setModal,locs}}/>}

        {canE&&Object.keys(shifts).length>0&&<div style={{...CD,display:"flex",alignItems:"center",justifyContent:"space-between",marginTop:12,...(hasDrafts?{borderColor:"#fbbf24",background:"#fffbeb"}:{borderColor:"#6ee7b7",background:"#ecfdf5"})}}>
          <div style={{display:"flex",alignItems:"center",gap:8}}><Badge s={hasDrafts?"DRAFT":"PUBLISHED"}/><span style={{fontSize:13,color:"#374151"}}>{hasDrafts?"Unpublished changes":"Published"}</span></div>
          {hasDrafts?<button onClick={pub} style={{...BTN,padding:"8px 16px",fontSize:13}}>Publish</button>:<button onClick={unpub} style={{...BTN,padding:"8px 16px",fontSize:13,background:"#f3f4f6",color:"#374151"}}>Unpublish</button>}
        </div>}

        <div style={{...CD,marginTop:8}}>
          <div style={{fontWeight:600,fontSize:14,color:"#111",marginBottom:10}}>Weekly Summary</div>
          {vU.map(u=><div key={u.id} style={{display:"flex",justifyContent:"space-between",padding:"5px 0",borderBottom:"1px solid #f3f4f6",fontSize:13}}><span style={{color:"#374151"}}>{u.display_name}</span><span style={{fontWeight:600,color:"#111"}}>{fmtH(wh[u.id]||0)}</span></div>)}
          <div style={{display:"flex",justifyContent:"flex-end",paddingTop:8,borderTop:"2px solid #e5e7eb",marginTop:6}}><span style={{fontWeight:700,color:"#3b82f6",fontSize:14}}>Total: {fmtH(th)}</span></div>
        </div>
      </div>}

      {view==="timeoff"&&<TimeOffView {...{me,isM:canE,tor,vL,fetchAll,tt}}/>}
      {view==="team"&&canAdmin&&<PeopleView {...{users,locs,fetchAll,tt,isOwner}}/>}
      {view==="admin"&&canAdmin&&<AdminView {...{tpls,rules,bh,ann,closed,bo,locs,fetchAll,tt}}/>}
      {view==="help"&&<HelpView {...{me,isOwner,isMgr}}/>}
      {view==="platform"&&isPlatformAdmin&&<PlatformView {...{me,activeFranchiseId,setActiveFranchiseId:setActiveFranchise,tt,fetchAll}}/>}
    </div>

    {/* Bottom Nav */}
    <div style={{position:"fixed",bottom:0,left:"50%",transform:"translateX(-50%)",width:"100%",maxWidth:960,background:"#fff",borderTop:"1px solid #e5e7eb",display:"flex",justifyContent:"space-around",padding:"6px 0 env(safe-area-inset-bottom, 10px)",zIndex:100}}>
      {tabs.map(t=><button key={t.id} onClick={()=>setView(t.id)} style={{background:"none",border:"none",cursor:"pointer",display:"flex",flexDirection:"column",alignItems:"center",gap:2,padding:"4px 10px",fontSize:10,fontWeight:view===t.id?600:400,color:view===t.id?"#3b82f6":"#9ca3af",fontFamily:"inherit"}}><span style={{fontSize:17}}>{t.i}</span>{t.l}</button>)}
    </div>
  </div>;
}
