// Preview screen + Admin panel

const { useState: usP, useMemo: umP } = React;

// ================ PREVIEW / SUBMIT ================
function PreviewScreen({ users, userId, shirts, customLib, onBack, onSubmit }) {
  const user = users.find(u => u.id === userId);
  const myShirts = user.shirts.map(id => shirts[id]).filter(Boolean);
  const allDone = myShirts.every(s => s.status === 'done');
  const [submitted, setSubmitted] = usP(false);

  if (submitted) {
    return (
      <div className="app-shell">
        <TopBar user={user} title="ОТПРАВЛЕНО" subtitle="2026 SEASON KIT"/>
        <div className="submit-success">
          <div className="success-mark">
            <svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="44" fill="none" stroke="var(--accent)" strokeWidth="3"/><path d="M30 52 L46 66 L72 38" fill="none" stroke="var(--accent)" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"/></svg>
          </div>
          <div className="success-h">Комплект отправлен на печать</div>
          <div className="success-p">
            Координатор клуба получил {myShirts.length} макета. Дизайн заблокирован.<br/>
            Уведомление отправлено на <b>{user.email}</b>.
          </div>
          <button className="btn ghost" onClick={onBack}>← НА ДАШБОРД</button>
        </div>
      </div>
    );
  }

  return (
    <div className="app-shell">
      <TopBar user={user} title="ПРОВЕРКА КОМПЛЕКТА" subtitle={`${myShirts.length} ФУТБОЛОК · ОТПРАВКА`} onBack={onBack}/>
      <div className="preview-wrap">
        <div className="preview-h">
          <div>
            <div className="preview-eyebrow">ФИНАЛЬНАЯ ПРОВЕРКА</div>
            <div className="preview-title">Просмотрите все макеты перед отправкой</div>
            <div className="preview-sub">После сабмита дизайн заблокирован — изменения только через координатора.</div>
          </div>
          <button className="btn primary big" disabled={!allDone} onClick={() => { onSubmit(); setSubmitted(true); }}>
            <span>СОХРАНИТЬ И ОТПРАВИТЬ</span><span className="btn-arrow">→</span>
          </button>
        </div>
        <div className="preview-grid">
          {myShirts.map((s, i) => (
            <div key={s.id} className="preview-card">
              <div className="preview-card-h">
                <div>
                  <span className="preview-card-num">#{String(i+1).padStart(2,'0')}</span>
                  <span className="preview-card-meta"> · {s.color.toUpperCase()} · {s.size}</span>
                </div>
                <span className={`pill ${s.status}`}>{s.status === 'done' ? '✓ ГОТОВО' : '◌ НЕ ГОТОВО'}</span>
              </div>
              <div className="preview-views">
                <div className="preview-view">
                  <ShirtFront shirtColor={s.color} showZoneOutline={false}>
                    {(s.designs.front || []).map(d => (
                      <DesignItem key={d.id} item={d} zoneRect={ZONE_RECTS.front} selected={false} onSelect={() => {}} onChange={() => {}} locked snap={false} customLib={customLib}/>
                    ))}
                  </ShirtFront>
                  <div className="preview-view-label">Спереди</div>
                </div>
                <div className="preview-view">
                  <ShirtBack shirtColor={s.color} showZoneOutline={false}>
                    {(s.designs.back || []).map(d => (
                      <DesignItem key={d.id} item={d} zoneRect={ZONE_RECTS.back} selected={false} onSelect={() => {}} onChange={() => {}} locked snap={false} customLib={customLib}/>
                    ))}
                  </ShirtBack>
                  <div className="preview-view-label">Сзади</div>
                </div>
                <div className="preview-view">
                  <ShirtSleeve shirtColor={s.color} showZoneOutline={false}>
                    {(s.designs.sleeve || []).map(d => (
                      <DesignItem key={d.id} item={d} zoneRect={{ ...ZONE_RECTS.sleeve, x: ZONE_RECTS.sleeve.x + 30, y: ZONE_RECTS.sleeve.y + 20 }} selected={false} onSelect={() => {}} onChange={() => {}} locked snap={false} customLib={customLib}/>
                    ))}
                  </ShirtSleeve>
                  <div className="preview-view-label">Рукав</div>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ================ ADMIN PANEL ================
function AdminPanel({
  users, shirts, onLogout, onImpersonate, defaultTemplate, templates, onApplyTemplate,
  onSaveTemplate, onDeleteTemplate, customLib,
  onAddUser, onAddShirt, onUpdateUser, onAddLogo, onImportState, onResetState,
}) {
  const [tab, setTab] = usP(() => {
    const h = window.location.hash.replace('#', '');
    return ['users', 'template', 'logos', 'export'].includes(h) ? h : 'users';
  });
  const [userView, setUserView] = usP('table'); // table | cards | kanban
  const [selUser, setSelUser] = usP(null);
  const [showPdfExport, setShowPdfExport] = usP(false);

  const stats = umP(() => {
    let totalShirts = 0, doneShirts = 0;
    users.forEach(u => u.shirts.forEach(sid => {
      totalShirts++;
      if (shirts[sid]?.status === 'done') doneShirts++;
    }));
    return {
      totalUsers: users.length,
      finished: users.filter(u => u.status === 'finished').length,
      inProgress: users.filter(u => u.status === 'in-progress' || u.status === 'ready').length,
      notStarted: users.filter(u => u.status === 'not-started').length,
      totalShirts, doneShirts,
    };
  }, [users, shirts]);

  return (
    <div className="app-shell admin">
      <div className="topbar admin-topbar">
        <div className="topbar-left">
          <div className="brand-mini"><Sticker src="stickers/30_circle_outline.svg" color="var(--accent)"/></div>
          <div className="topbar-title">
            <div className="t-eyebrow">ADMIN · 2026 SEASON KIT</div>
            <div className="t-h">NORDICREW · KIT BUILDER</div>
          </div>
        </div>
        <div className="admin-tabs">
          <button className={tab === 'users' ? 'active' : ''} onClick={() => setTab('users')}>ПОЛЬЗОВАТЕЛИ</button>
          <button className={tab === 'template' ? 'active' : ''} onClick={() => setTab('template')}>ШАБЛОН</button>
          <button className={tab === 'logos' ? 'active' : ''} onClick={() => setTab('logos')}>ЛОГОТИПЫ</button>
          <button className={tab === 'export' ? 'active' : ''} onClick={() => setTab('export')}>ЭКСПОРТ</button>
        </div>
        <div className="topbar-right">
          <ThemeToggle/>
          <div className="user-chip">
            <div className="user-chip-num">A</div>
            <div className="user-chip-text">
              <div>ADMIN</div>
              <div className="user-chip-email">admin@nordicrew.club</div>
            </div>
            <button className="icon-btn" onClick={onLogout}>⏻</button>
          </div>
        </div>
      </div>

      <div className="admin-stats">
        <StatCard label="Пользователей" value={stats.totalUsers}/>
        <StatCard label="Закончили" value={stats.finished} accent="ok"/>
        <StatCard label="В процессе" value={stats.inProgress} accent="warn"/>
        <StatCard label="Не начали" value={stats.notStarted} accent="bad"/>
        <StatCard label="Футболок готово" value={`${stats.doneShirts}/${stats.totalShirts}`}/>
        <StatCard label="К печати" value={`${stats.doneShirts} принт-листов`}/>
      </div>

      {tab === 'users' && (
        <AdminUsers users={users} shirts={shirts} customLib={customLib}
                    view={userView} setView={setUserView} onImpersonate={onImpersonate}
                    selUser={selUser} setSelUser={setSelUser}
                    onAddUser={onAddUser} onAddShirt={onAddShirt} onUpdateUser={onUpdateUser}/>
      )}
      {tab === 'template' && <AdminTemplate users={users} templates={templates} onApply={onApplyTemplate}
                                            onSaveTemplate={onSaveTemplate} onDeleteTemplate={onDeleteTemplate}
                                            shirts={shirts} customLib={customLib}/>}
      {tab === 'logos' && <AdminLogos customLib={customLib} onAddLogo={onAddLogo}/>}
      {tab === 'export' && <AdminExport users={users} shirts={shirts} customLib={customLib}
                                         onShowPdf={() => setShowPdfExport(true)}
                                         onImportState={onImportState} onResetState={onResetState}/>}

      {showPdfExport && <PdfExportModal users={users} shirts={shirts} customLib={customLib} onClose={() => setShowPdfExport(false)}/>}
    </div>
  );
}

function StatCard({ label, value, accent }) {
  return (
    <div className={`stat-card ${accent || ''}`}>
      <div className="stat-value">{value}</div>
      <div className="stat-label">{label}</div>
    </div>
  );
}

// === Admin Users — supports table / cards / kanban
function AdminUsers({ users, shirts, customLib, view, setView, onImpersonate, selUser, setSelUser, onAddUser, onAddShirt, onUpdateUser }) {
  const [showAdd, setShowAdd] = usP(false);
  return (
    <div className="admin-content">
      <div className="admin-sub-h">
        <div>
          <div className="ah-title">УЧАСТНИКИ КЛУБА · {users.length}</div>
          <div className="ah-sub">Управление доступами и комплектами футболок</div>
        </div>
        <div className="admin-controls">
          <div className="segmented">
            <button className={view === 'table' ? 'active' : ''} onClick={() => setView('table')}>ТАБЛИЦА</button>
            <button className={view === 'cards' ? 'active' : ''} onClick={() => setView('cards')}>КАРТОЧКИ</button>
            <button className={view === 'kanban' ? 'active' : ''} onClick={() => setView('kanban')}>КАНБАН</button>
          </div>
          <button className="btn primary small" onClick={() => setShowAdd(true)}>+ ДОБАВИТЬ УЧАСТНИКА</button>
        </div>
      </div>

      {view === 'table' && <UsersTable users={users} shirts={shirts} onImpersonate={onImpersonate} setSelUser={setSelUser}/>}
      {view === 'cards' && <UsersCards users={users} shirts={shirts} onImpersonate={onImpersonate} setSelUser={setSelUser}/>}
      {view === 'kanban' && <UsersKanban users={users} shirts={shirts} onImpersonate={onImpersonate}/>}

      {selUser && <UserDrawer users={users} customLib={customLib} userId={selUser} shirts={shirts}
                              onClose={() => setSelUser(null)} onImpersonate={onImpersonate}
                              onAddShirt={onAddShirt} onUpdateUser={onUpdateUser}/>}
      {showAdd && <AddUserModal onClose={() => setShowAdd(false)} onAdd={(data) => { onAddUser(data); setShowAdd(false); }}/>}
    </div>
  );
}

function ShirtsBar({ user, shirts }) {
  return (
    <div className="shirts-bar">
      {user.shirts.map((sid) => {
        const s = shirts[sid];
        return <span key={sid} className={`shirt-pip ${s.status} ${s.color}`} title={`${s.color} ${s.size}`}/>;
      })}
    </div>
  );
}

function UsersTable({ users, shirts, onImpersonate, setSelUser }) {
  return (
    <div className="users-table">
      <div className="ut-head">
        <div>УЧАСТНИК</div>
        <div>E-MAIL</div>
        <div>КОМПЛЕКТ</div>
        <div>ПРОГРЕСС</div>
        <div>СТАТУС</div>
        <div>ДЕЙСТВИЯ</div>
      </div>
      {users.map(u => {
        const my = u.shirts.map(id => shirts[id]).filter(Boolean);
        const done = my.filter(s => s.status === 'done').length;
        return (
          <div key={u.id} className="ut-row" onClick={() => setSelUser(u.id)}>
            <div className="ut-user">
              <div className="ut-num">{u.carNumber}</div>
              <div>
                <div className="ut-nick">{u.nickname}</div>
                <div className="ut-name">{u.name}</div>
              </div>
            </div>
            <div className="ut-email">{u.email}</div>
            <div><ShirtsBar user={u} shirts={shirts}/></div>
            <div className="ut-progress">
              <div className="ut-prog-bar"><div style={{ width: `${(done/my.length)*100}%` }}/></div>
              <span>{done}/{my.length}</span>
            </div>
            <div><span className={`pill ${u.status}`}>{statusLabel(u.status)}</span></div>
            <div className="ut-actions" onClick={e => e.stopPropagation()}>
              <span className="invite-code">{u.inviteCode}</span>
              <button className="btn ghost xsmall" onClick={() => onImpersonate(u.id)}>ВОЙТИ КАК</button>
              <button className="btn ghost xsmall">✎</button>
            </div>
          </div>
        );
      })}
    </div>
  );
}

function UsersCards({ users, shirts, onImpersonate, setSelUser }) {
  return (
    <div className="users-cards">
      {users.map(u => {
        const my = u.shirts.map(id => shirts[id]).filter(Boolean);
        const done = my.filter(s => s.status === 'done').length;
        return (
          <div key={u.id} className="user-card" onClick={() => setSelUser(u.id)}>
            <div className="uc-head">
              <div className="ut-num big">{u.carNumber}</div>
              <span className={`pill ${u.status}`}>{statusLabel(u.status)}</span>
            </div>
            <div className="uc-name">{u.nickname}</div>
            <div className="uc-fullname">{u.name}</div>
            <div className="uc-email">{u.email}</div>
            <ShirtsBar user={u} shirts={shirts}/>
            <div className="uc-progress">
              <div className="ut-prog-bar"><div style={{ width: `${(done/my.length)*100}%` }}/></div>
              <span>{done}/{my.length} готово</span>
            </div>
            <div className="uc-actions" onClick={e => e.stopPropagation()}>
              <span className="invite-code">{u.inviteCode}</span>
              <button className="btn ghost small" onClick={() => onImpersonate(u.id)}>👁 ВОЙТИ КАК</button>
              <button className="btn ghost small">✎ ИЗМЕНИТЬ</button>
            </div>
          </div>
        );
      })}
    </div>
  );
}

function UsersKanban({ users, shirts, onImpersonate }) {
  const cols = [
    { id: 'not-started', label: 'НЕ НАЧАЛИ', accent: 'bad' },
    { id: 'in-progress', label: 'В ПРОЦЕССЕ', accent: 'warn' },
    { id: 'finished', label: 'ЗАКОНЧИЛИ', accent: 'ok' },
  ];
  return (
    <div className="kanban">
      {cols.map(col => {
        const colUsers = users.filter(u => col.id === 'in-progress' ? (u.status === 'in-progress' || u.status === 'ready') : u.status === col.id);
        return (
          <div key={col.id} className={`kanban-col ${col.accent}`}>
            <div className="kanban-h">
              <span>{col.label}</span>
              <span className="kanban-count">{colUsers.length}</span>
            </div>
            <div className="kanban-cards">
              {colUsers.map(u => {
                const my = u.shirts.map(id => shirts[id]).filter(Boolean);
                const done = my.filter(s => s.status === 'done').length;
                return (
                  <div key={u.id} className="kanban-card">
                    <div className="kc-head">
                      <span className="ut-num">{u.carNumber}</span>
                      <span className="kc-nick">{u.nickname}</span>
                    </div>
                    <div className="kc-email">{u.email}</div>
                    <ShirtsBar user={u} shirts={shirts}/>
                    <div className="kc-prog">{done}/{my.length} футболок</div>
                    <button className="btn ghost xsmall" onClick={() => onImpersonate(u.id)}>👁 ВОЙТИ КАК</button>
                  </div>
                );
              })}
            </div>
          </div>
        );
      })}
    </div>
  );
}

function UserDrawer({ users, customLib, userId, shirts, onClose, onImpersonate, onAddShirt, onUpdateUser }) {
  const u = users.find(x => x.id === userId);
  const my = u.shirts.map(id => shirts[id]).filter(Boolean);
  const [shirtDraft, setShirtDraft] = usP({ color: 'black', size: 'L', qty: 1 });
  const [editOpen, setEditOpen] = usP(false);
  return (
    <div className="drawer-overlay" onClick={onClose}>
      <div className="drawer" onClick={e => e.stopPropagation()}>
        <div className="drawer-h">
          <div>
            <div className="ut-num big">{u.carNumber}</div>
            <div className="drawer-nick">{u.nickname}</div>
            <div className="drawer-name">{u.name} · {u.email}</div>
            <div className="drawer-name">Источник: строки Excel {u.sourceRows?.join(', ') || '—'} · {u.paid ? 'оплачено/заказано' : 'не оплачено'}</div>
          </div>
          <button className="icon-btn" onClick={onClose}>✕</button>
        </div>
        <div className="drawer-section">
          <div className="panel-h-small">КОМПЛЕКТ ФУТБОЛОК</div>
          {u.designNote && <div className="design-note">{u.designNote}</div>}
          <div className="drawer-shirts">
            {my.map((s, i) => (
              <div key={s.id} className="drawer-shirt">
                <div className="ds-mini">
                  <ShirtFront shirtColor={s.color} showZoneOutline={false}>
                    {(s.designs.front || []).map(d => (
                      <DesignItem key={d.id} item={d} zoneRect={ZONE_RECTS.front} selected={false} onSelect={() => {}} onChange={() => {}} locked snap={false} customLib={customLib}/>
                    ))}
                  </ShirtFront>
                </div>
                <div className="ds-meta">
                  <div>#{String(i+1).padStart(2,'0')} · {s.color.toUpperCase()} · {s.size}</div>
                  <span className={`pill ${s.status}`}>{s.status === 'done' ? '✓ ГОТОВО' : '◌ В РАБОТЕ'}</span>
                  <span className="drawer-shirt-qty">×{s.qty || 1}</span>
                </div>
              </div>
            ))}
            <div className="add-shirt-form">
              <select value={shirtDraft.color} onChange={e => setShirtDraft({ ...shirtDraft, color: e.target.value })}>
                <option value="black">BLACK</option>
                <option value="white">WHITE</option>
              </select>
              <select value={shirtDraft.size} onChange={e => setShirtDraft({ ...shirtDraft, size: e.target.value })}>
                {['XS','S','M','L','XL','XXL','XXXL'].map(size => <option key={size} value={size}>{size}</option>)}
              </select>
              <input type="number" min="1" value={shirtDraft.qty} onChange={e => setShirtDraft({ ...shirtDraft, qty: e.target.value })}/>
              <button className="add-shirt-btn" onClick={() => onAddShirt(u.id, shirtDraft)}>+ ДОБАВИТЬ</button>
            </div>
          </div>
        </div>
        <div className="drawer-section">
          <div className="panel-h-small">ДЕЙСТВИЯ</div>
          <div className="drawer-actions">
            <div className="invite-box">
              <span>Код приглашения</span>
              <b>{u.inviteCode}</b>
            </div>
            <button className="btn primary" onClick={() => onImpersonate(u.id)}>👁 ВОЙТИ КАК ПОЛЬЗОВАТЕЛЬ</button>
            <button className="btn ghost" onClick={() => setEditOpen(s => !s)}>✎ ИЗМЕНИТЬ ПРОФИЛЬ</button>
            {editOpen && (
              <div className="drawer-edit">
                <input className="text-input" defaultValue={u.name} onBlur={e => onUpdateUser(u.id, { name: e.target.value })}/>
                <input className="text-input" defaultValue={u.nickname} onBlur={e => onUpdateUser(u.id, { nickname: e.target.value })}/>
                <input className="text-input" defaultValue={u.carNumber} onBlur={e => onUpdateUser(u.id, { carNumber: e.target.value })}/>
              </div>
            )}
            <button className="btn danger ghost">🗑 УДАЛИТЬ ПОЛЬЗОВАТЕЛЯ</button>
          </div>
        </div>
      </div>
    </div>
  );
}

function statusLabel(s) {
  return NordicrewCore.userStatusLabel(s);
}

function AddUserModal({ onClose, onAdd }) {
  const [form, setForm] = usP({
    email: '',
    name: '',
    nickname: '',
    carNumber: '',
    shirts: [{ color: 'black', size: 'L', qty: 1 }],
  });
  const canSave = /\S+@\S+\.\S+/.test(form.email) && form.nickname.trim();
  const updateShirt = (idx, patch) => {
    setForm(prev => ({
      ...prev,
      shirts: prev.shirts.map((shirt, i) => i === idx ? { ...shirt, ...patch } : shirt),
    }));
  };
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-h">Новый участник</div>
        <div className="form-grid">
          <label className="field compact"><span>E-MAIL</span><input value={form.email} onChange={e => setForm({ ...form, email: e.target.value })}/></label>
          <label className="field compact"><span>ИМЯ</span><input value={form.name} onChange={e => setForm({ ...form, name: e.target.value })}/></label>
          <label className="field compact"><span>NICKNAME</span><input value={form.nickname} onChange={e => setForm({ ...form, nickname: e.target.value.toUpperCase() })}/></label>
          <label className="field compact"><span>НОМЕР</span><input value={form.carNumber} onChange={e => setForm({ ...form, carNumber: e.target.value })}/></label>
        </div>
        <div className="panel-h-small" style={{ marginTop: 18 }}>ФУТБОЛКИ</div>
        <div className="shirt-form-list">
          {form.shirts.map((shirt, idx) => (
            <div key={idx} className="shirt-form-row">
              <select value={shirt.color} onChange={e => updateShirt(idx, { color: e.target.value })}>
                <option value="black">BLACK</option>
                <option value="white">WHITE</option>
              </select>
              <select value={shirt.size} onChange={e => updateShirt(idx, { size: e.target.value })}>
                {['XS','S','M','L','XL','XXL','XXXL'].map(size => <option key={size} value={size}>{size}</option>)}
              </select>
              <input type="number" min="1" value={shirt.qty} onChange={e => updateShirt(idx, { qty: e.target.value })}/>
            </div>
          ))}
          <button className="btn ghost small" onClick={() => setForm({ ...form, shirts: [...form.shirts, { color: 'black', size: 'L', qty: 1 }] })}>+ ЕЩЁ ФУТБОЛКА</button>
        </div>
        <div className="modal-actions">
          <button className="btn ghost" onClick={onClose}>ОТМЕНА</button>
          <button className="btn primary" disabled={!canSave} onClick={() => onAdd(form)}>СОЗДАТЬ</button>
        </div>
      </div>
    </div>
  );
}

// === Admin Template — admin-editable template library
function AdminTemplate({ users, templates, onApply, onSaveTemplate, onDeleteTemplate, shirts, customLib }) {
  const safeTemplates = templates && templates.length ? templates : DEFAULT_TEMPLATES;
  const [activeId, setActiveId] = usP(safeTemplates[0]?.id);
  const activeTemplate = safeTemplates.find(t => t.id === activeId) || safeTemplates[0];
  const [draft, setDraft] = usP(activeTemplate?.designs || DEFAULT_TEMPLATE);
  const [templateName, setTemplateName] = usP(activeTemplate?.name || 'Шаблон');
  const [templateDescription, setTemplateDescription] = usP(activeTemplate?.description || '');
  const [zone, setZone] = usP('back');
  const [selected, setSelected] = usP(null);
  const [confirmStage, setConfirmStage] = usP(null); // null | 'confirm' | 'done'
  const [logoSearch, setLogoSearch] = usP('');

  React.useEffect(() => {
    const tpl = safeTemplates.find(t => t.id === activeId) || safeTemplates[0];
    if (!tpl) return;
    setDraft(NordicrewCore.clone(tpl.designs));
    setTemplateName(tpl.name);
    setTemplateDescription(tpl.description || '');
    setSelected(null);
  }, [activeId, templates?.length]);

  const designs = draft[zone] || [];
  const ZONE_LABELS = { front: 'Грудь', back: 'Спина', sleeve: 'Рукав' };

  // Преview shirts: black + white side by side, with auto-inverted ink
  const sampleUser = users[0];
  const previewShirts = ['black', 'white'].map(color => ({
    id: 'preview-'+color, color, status: 'empty',
    designs: applyTemplate(draft, sampleUser, color),
  }));

  const updateItem = (id, patch) => {
    setDraft(prev => ({
      ...prev,
      [zone]: prev[zone].map(d => d.id === id ? { ...d, ...patch } : d),
    }));
  };
  const removeItem = (id) => {
    setDraft(prev => ({ ...prev, [zone]: prev[zone].filter(d => d.id !== id) }));
    setSelected(null);
  };
  const addItem = (logoId, extras = {}) => {
    if ((draft[zone] || []).length >= ZONE_LIMITS[zone]) return;
    const aspectRatio = getLogoAspectRatio(logoId, extras.customText, customLib);
    const zoneRect = zone === 'sleeve'
      ? { ...ZONE_RECTS.sleeve, x: ZONE_RECTS.sleeve.x + 30, y: ZONE_RECTS.sleeve.y + 20 }
      : ZONE_RECTS[zone];
    const newItem = NordicrewGeometry.normalizeItemAspect({
      id: 'tpl-' + zone + '-' + Date.now(),
      logoId, x: 50, y: 50, w: 30, h: 30, rotation: 0, color: 'auto',
      aspectRatio,
      ...extras,
    }, zoneRect, aspectRatio);
    setDraft(prev => ({ ...prev, [zone]: [...(prev[zone]||[]), newItem] }));
    setSelected(newItem.id);
  };

  const sel = designs.find(d => d.id === selected);
  const usedCount = (draft.front?.length || 0) + (draft.back?.length || 0) + (draft.sleeve?.length || 0);
  const templateLogoLibrary = [...BUILT_IN_STICKERS, ...(customLib || [])].filter(l => {
    if (!logoSearch) return true;
    return String(l.name || '').toLowerCase().includes(logoSearch.toLowerCase());
  });

  const duplicateItem = () => {
    if (!sel || (draft[zone] || []).length >= ZONE_LIMITS[zone]) return;
    const aspectRatio = sel.aspectRatio || getLogoAspectRatio(sel.logoId, sel.customText, customLib);
    const zoneRect = zone === 'sleeve'
      ? { ...ZONE_RECTS.sleeve, x: ZONE_RECTS.sleeve.x + 30, y: ZONE_RECTS.sleeve.y + 20 }
      : ZONE_RECTS[zone];
    const copy = NordicrewGeometry.normalizeItemAspect(
      NordicrewCore.cloneDesignItem(sel, zone, (draft[zone] || []).length),
      zoneRect,
      aspectRatio
    );
    setDraft(prev => ({ ...prev, [zone]: [...(prev[zone] || []), copy] }));
    setSelected(copy.id);
  };

  const mirrorItem = (axis) => {
    if (!sel) return;
    updateItem(sel.id, axis === 'y'
      ? { flipY: !sel.flipY }
      : { flipX: !sel.flipX });
  };

  const saveCurrentTemplate = () => {
    onSaveTemplate({
      id: activeTemplate.id,
      name: templateName,
      description: templateDescription,
      designs: draft,
      updatedAt: new Date().toISOString(),
    });
  };

  const duplicateTemplate = () => {
    const next = {
      id: 'tpl-' + Date.now(),
      name: templateName + ' copy',
      description: templateDescription,
      designs: NordicrewCore.clone(draft),
      updatedAt: new Date().toISOString(),
    };
    onSaveTemplate(next);
    setActiveId(next.id);
  };

  return (
    <div className="admin-content">
      <div className="admin-sub-h">
        <div>
          <div className="ah-title">БИБЛИОТЕКА ШАБЛОНОВ</div>
          <div className="ah-sub">Админ готовит несколько стартовых вариантов. Пользователь выбирает один перед ручной доработкой.</div>
        </div>
        <div className="admin-controls">
          <button className="btn ghost small" onClick={duplicateTemplate}>⧉ КОПИЯ</button>
          <button className="btn ghost small danger" disabled={safeTemplates.length <= 1} onClick={() => {
            onDeleteTemplate(activeTemplate.id);
            setActiveId(safeTemplates.find(t => t.id !== activeTemplate.id)?.id || safeTemplates[0]?.id);
          }}>УДАЛИТЬ</button>
          <button className="btn ghost small" onClick={saveCurrentTemplate}>СОХРАНИТЬ</button>
          <button className="btn primary small" onClick={() => setConfirmStage('confirm')} disabled={usedCount === 0}>
            ↗ ПРИМЕНИТЬ КО ВСЕМ
          </button>
        </div>
      </div>

      <div className="template-library-bar">
        <select value={activeTemplate?.id || ''} onChange={e => setActiveId(e.target.value)}>
          {safeTemplates.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
        </select>
        <input value={templateName} onChange={e => setTemplateName(e.target.value)} placeholder="Название шаблона"/>
        <input value={templateDescription} onChange={e => setTemplateDescription(e.target.value)} placeholder="Короткое описание для пользователя"/>
      </div>

      <div className="template-editor">
        <div className="template-stage">
          <div className="zone-tabs">
            {ZONES.map(z => {
              const count = (draft[z.id] || []).length;
              return (
                <button key={z.id} className={`zone-tab ${zone === z.id ? 'active' : ''}`} onClick={() => { setZone(z.id); setSelected(null); }}>
                  <div className="zone-tab-label">{z.viewLabel}</div>
                  <div className="zone-tab-meta">{count}/{ZONE_LIMITS[z.id]}</div>
                </button>
              );
            })}
          </div>

          <div className="template-previews">
            {previewShirts.map(s => (
              <div key={s.id} className="template-preview-shirt">
                <div className="tps-label">{s.color === 'black' ? 'НА ЧЁРНОМ' : 'НА БЕЛОМ'}</div>
                <ShirtCanvas
                  shirt={s} zoneId={zone}
                  designs={(s.designs[zone] || []).map((d, i) => ({ ...d, id: draft[zone][i]?.id || d.id }))}
                  onChange={(id, patch) => {
                    // Map back to draft (strip auto-converted color from patch)
                    const cleanPatch = { ...patch };
                    if (cleanPatch.color === inkFor(s.color)) delete cleanPatch.color;
                    updateItem(id, cleanPatch);
                  }}
                  selectedId={selected} onSelect={setSelected}
                  snap={true} locked={false} customLib={customLib}/>
              </div>
            ))}
          </div>
        </div>

        <div className="template-inspector">
          <div className="panel-section">
            <div className="panel-h-small">ДОБАВИТЬ В «{ZONE_LABELS[zone].toUpperCase()}»</div>
            <input className="text-input small" placeholder="Поиск по всем SVG..." value={logoSearch} onChange={e => setLogoSearch(e.target.value)}/>
            <div className="lib-grid template-lib">
              {templateLogoLibrary.map(l => (
                <button key={l.id} className="lib-item" disabled={(draft[zone]||[]).length >= ZONE_LIMITS[zone]}
                        onClick={() => addItem(l.id)}>
                  <div className="lib-item-preview" style={{ color: 'var(--text)' }}>
                    {l.dataUri || l.inlineMarkup || l.id.startsWith('user-')
                      ? <LogoRender logoId={l.id} color="currentColor" customLib={customLib}/>
                      : <Sticker src={l.src} color="currentColor"/>}
                  </div>
                  <div className="lib-item-name">{l.name}</div>
                </button>
              ))}
              {templateLogoLibrary.length === 0 && <div className="lib-empty">Ничего не найдено</div>}
            </div>
          </div>

          <div className="panel-section">
            <div className="panel-h-small">ТЕКСТ-ПЛЕЙСХОЛДЕР</div>
            <div className="tweak-nav">
              <button className="btn ghost xsmall" onClick={() => addItem('user-text', { customText: '{{nickname}}', w: 50, h: 9 })}>+ {'{{nickname}}'}</button>
              <button className="btn ghost xsmall" onClick={() => addItem('user-text', { customText: '#{{number}}', w: 30, h: 16 })}>+ #{'{{number}}'}</button>
              <button className="btn ghost xsmall" onClick={() => addItem('user-text', { customText: '{{name}}', w: 60, h: 8 })}>+ {'{{name}}'}</button>
            </div>
            <div className="tweak-theme-desc">Подставится из профиля при применении.</div>
          </div>

          {sel && (
            <div className="panel-section">
              <div className="panel-h-small">ВЫБРАН ЭЛЕМЕНТ</div>
              {sel.logoId === 'user-text' && (
                <input className="text-input" value={sel.customText || ''}
                       onChange={e => updateItem(sel.id, { customText: e.target.value })}/>
              )}
              <div className="tweak-theme-desc" style={{ marginTop: 8 }}>
                Цвет: <b>авто-инверсия</b> — будет тёмным на белой и светлым на чёрной.
              </div>
              <div className="i-actions" style={{ marginTop: 10 }}>
                <button className={`btn ghost small ${sel.flipX ? 'active' : ''}`} onClick={() => mirrorItem('x')}>⇋ Зеркало X</button>
                <button className={`btn ghost small ${sel.flipY ? 'active' : ''}`} onClick={() => mirrorItem('y')}>⇵ Зеркало Y</button>
                <button className="btn ghost small" disabled={(draft[zone] || []).length >= ZONE_LIMITS[zone]} onClick={duplicateItem}>⧉ Копия</button>
              </div>
              <button className="btn danger small full" style={{ marginTop: 10 }} onClick={() => removeItem(sel.id)}>🗑 Удалить</button>
            </div>
          )}
        </div>
      </div>

      {confirmStage === 'confirm' && (
        <div className="modal-overlay" onClick={() => setConfirmStage(null)}>
          <div className="modal" onClick={e => e.stopPropagation()}>
            <div className="modal-h">Применить шаблон ко всем?</div>
            <div className="modal-p">Шаблон будет применён ко всем <b>пустым</b> футболкам. Уже завершённые комплекты не изменятся. Цвет печати автоматически инвертируется под цвет каждой футболки.</div>
            <div className="modal-actions">
              <button className="btn ghost" onClick={() => setConfirmStage(null)}>ОТМЕНА</button>
              <button className="btn primary" onClick={() => { onApply(draft); setConfirmStage('done'); setTimeout(() => setConfirmStage(null), 1400); }}>↗ ПРИМЕНИТЬ</button>
            </div>
          </div>
        </div>
      )}
      {confirmStage === 'done' && (
        <div className="modal-overlay">
          <div className="modal" style={{ textAlign: 'center' }}>
            <div className="success-mark" style={{ margin: '0 auto 12px' }}>
              <svg viewBox="0 0 100 100" width="56" height="56"><circle cx="50" cy="50" r="44" fill="none" stroke="var(--accent)" strokeWidth="3"/><path d="M30 52 L46 66 L72 38" fill="none" stroke="var(--accent)" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"/></svg>
            </div>
            <div className="modal-h">Шаблон применён</div>
            <div className="modal-p">Все пустые футболки получили дефолтный дизайн.</div>
          </div>
        </div>
      )}
    </div>
  );
}

// === Admin Logos
function AdminLogos({ customLib, onAddLogo }) {
  const [error, setError] = usP('');
  const uploadRef = React.useRef(null);
  const addFiles = async (files) => {
    setError('');
    for (const file of Array.from(files || [])) {
      if (!/image\/(svg\+xml|png|jpeg)/.test(file.type) && !/\.svg$/i.test(file.name)) {
        setError('Поддерживаются SVG, PNG и JPG.');
        continue;
      }
      const id = 'user-logo-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7);
      const name = file.name.replace(/\.[^.]+$/, '');
      if (/\.svg$/i.test(file.name) || file.type === 'image/svg+xml') {
        const text = await file.text();
        const vbMatch = text.match(/viewBox="([^"]+)"/);
        const innerMatch = text.match(/<svg[^>]*>([\s\S]*?)<\/svg>/);
        onAddLogo({
          id,
          name,
          type: 'custom',
          viewBox: vbMatch ? vbMatch[1] : '0 0 200 200',
          aspect: aspectFromViewBox(vbMatch ? vbMatch[1] : '0 0 200 200'),
          inlineMarkup: tintSvgContent(innerMatch ? innerMatch[1] : text),
        });
      } else {
        const dataUri = await new Promise((resolve) => {
          const reader = new FileReader();
          reader.onload = () => resolve(reader.result);
          reader.readAsDataURL(file);
        });
        const aspect = await new Promise((resolve) => {
          const img = new Image();
          img.onload = () => resolve(img.naturalWidth && img.naturalHeight ? img.naturalWidth / img.naturalHeight : 1);
          img.onerror = () => resolve(1);
          img.src = dataUri;
        });
        onAddLogo({ id, name, type: 'custom', dataUri, aspect });
      }
    }
    if (uploadRef.current) uploadRef.current.value = '';
  };
  const allLogos = [...BUILT_IN_STICKERS, ...(customLib || [])];
  return (
    <div className="admin-content">
      <div className="admin-sub-h">
        <div>
          <div className="ah-title">БИБЛИОТЕКА ЛОГОТИПОВ</div>
          <div className="ah-sub">Доступны всем участникам клуба. Загружайте SVG для лучшего качества печати.</div>
        </div>
        <div className="admin-controls">
          <input ref={uploadRef} type="file" accept=".svg,image/svg+xml,image/png,image/jpeg" multiple hidden onChange={e => addFiles(e.target.files)}/>
          <button className="btn primary small" onClick={() => uploadRef.current?.click()}>+ ЗАГРУЗИТЬ ЛОГОТИП</button>
        </div>
      </div>
      {error && <div className="form-error">{error}</div>}
      <div className="logo-admin-grid">
        {allLogos.map(l => (
          <div key={l.id} className="logo-admin-card">
            <div className="lac-preview light">
              {l.dataUri || l.inlineMarkup ? <LogoRender logoId={l.id} color="#0a0a0c" customLib={customLib}/> : <Sticker src={l.src} color="#0a0a0c"/>}
            </div>
            <div className="lac-preview dark">
              {l.dataUri || l.inlineMarkup ? <LogoRender logoId={l.id} color="#fff" customLib={customLib}/> : <Sticker src={l.src} color="#fff"/>}
            </div>
            <div className="lac-meta">
              <div className="lac-name">{l.name}</div>
              <div className={`lac-tag ${l.type}`}>{l.type}</div>
            </div>
            <div className="lac-actions">
              <button className="btn ghost xsmall">✎</button>
              <button className="btn ghost xsmall danger">🗑</button>
            </div>
          </div>
        ))}
        <button className="logo-admin-card add">
          <div className="add-icon">+</div>
          <div>Загрузить новый</div>
          <div className="add-hint">SVG · PNG · до 5 МБ</div>
        </button>
      </div>
    </div>
  );
}

function escapeHtml(value) {
  return String(value ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

async function renderStickerForPrint(row, customLib) {
  const color = row.color || '#111';
  if (row.logoId === 'user-text') {
    return `<svg viewBox="0 0 240 60" style="width:100%;height:100%;color:${escapeHtml(color)}"><text x="120" y="44" text-anchor="middle" font-family="Arial, sans-serif" font-weight="800" font-size="36" fill="currentColor" letter-spacing="2">${escapeHtml(row.customText || 'TEXT')}</text></svg>`;
  }
  if (row.logoId && row.logoId.startsWith('user-')) {
    const found = (customLib || []).find(l => l.id === row.logoId);
    if (found?.inlineMarkup) {
      return `<svg viewBox="${escapeHtml(found.viewBox || '0 0 200 200')}" style="width:100%;height:100%;color:${escapeHtml(color)}">${found.inlineMarkup}</svg>`;
    }
    if (found?.dataUri) {
      return `<img src="${found.dataUri}" style="width:100%;height:100%;object-fit:contain"/>`;
    }
  }
  const builtIn = BUILT_IN_STICKERS.find(s => s.id === row.logoId);
  if (builtIn) {
    const data = await fetchSticker(builtIn.src);
    return `<svg viewBox="${escapeHtml(data.viewBox)}" style="width:100%;height:100%;color:${escapeHtml(color)}">${tintSvgContent(data.inner)}</svg>`;
  }
  return `<div class="missing-logo">?</div>`;
}

function groupedBy(rows, keyFn) {
  return rows.reduce((acc, row) => {
    const key = keyFn(row);
    if (!acc[key]) acc[key] = [];
    acc[key].push(row);
    return acc;
  }, {});
}

function zoneBoxHtml(zoneId, rows) {
  const title = zoneId === 'front' ? 'Спереди' : zoneId === 'back' ? 'Сзади' : 'Рукав';
  const className = zoneId === 'sleeve' ? 'zone-box sleeve' : 'zone-box';
  const items = rows.map(row => `
    <div class="overview-item" style="left:${row.xMm / (zoneByPrint(row.zone).printW) * 100}%;top:${row.yMm / (zoneByPrint(row.zone).printH) * 100}%;width:${Math.max(8, row.wMm / zoneByPrint(row.zone).printW * 100)}%;height:${Math.max(6, row.hMm / zoneByPrint(row.zone).printH * 100)}%;transform:translate(-50%,-50%) rotate(${row.rotation}deg)">
      <span>${escapeHtml(row.printCode)}</span>
    </div>
  `).join('');
  return `<div class="${className}"><div class="zone-title">${title}</div>${items}</div>`;
}

function zoneByPrint(zoneId) {
  return ZONES.find(z => z.id === zoneId) || { printW: 100, printH: 100 };
}

async function openPrintLayout({ users, shirts, customLib }) {
  const rows = NordicrewCore.buildPrintRows({ users, shirts, zones: ZONES });
  const byUser = groupedBy(rows, row => row.userId);
  const overviewPages = users
    .filter(user => byUser[user.id]?.length)
    .map(user => {
      const userRows = byUser[user.id];
      const byShirtCopy = groupedBy(userRows, row => `${row.shirtId}-${row.shirtCopy}`);
      const shirtsHtml = Object.entries(byShirtCopy).map(([key, shirtRows]) => {
        const sample = shirtRows[0];
        const byZone = groupedBy(shirtRows, row => row.zone);
        return `
          <section class="overview-shirt">
            <header>
              <strong>${escapeHtml(sample.shirtId)} · ${escapeHtml(sample.shirtColor.toUpperCase())} · ${escapeHtml(sample.shirtSize)} · экз. ${sample.shirtCopy}/${sample.shirtQty}</strong>
              <span>${shirtRows.map(r => r.printCode).join(', ')}</span>
            </header>
            <div class="overview-zones">
              ${zoneBoxHtml('front', byZone.front || [])}
              ${zoneBoxHtml('back', byZone.back || [])}
              ${zoneBoxHtml('sleeve', byZone.sleeve || [])}
            </div>
          </section>
        `;
      }).join('');
      return `
        <section class="print-page overview-page">
          <h1>${escapeHtml(user.nickname)} · ${escapeHtml(user.email)}</h1>
          <p>Обзор расположения термопереносов. Коды на схеме совпадают с кодами на рулонной раскладке.</p>
          ${shirtsHtml}
        </section>
      `;
    }).join('');

  const stickerTiles = [];
  for (const row of rows) {
    const sticker = await renderStickerForPrint(row, customLib);
    const angle = Math.abs((row.rotation || 0) % 180) * Math.PI / 180;
    const boxW = Math.abs(row.wMm * Math.cos(angle)) + Math.abs(row.hMm * Math.sin(angle)) + 10;
    const boxH = Math.abs(row.wMm * Math.sin(angle)) + Math.abs(row.hMm * Math.cos(angle)) + 10;
    stickerTiles.push(`
      <article class="print-sticker" style="width:${Math.max(18, Math.round(boxW))}mm;height:${Math.max(16, Math.round(boxH))}mm">
        <div class="sticker-code">${escapeHtml(row.printCode)}</div>
        <div class="sticker-art" style="width:${row.wMm}mm;height:${row.hMm}mm;transform:rotate(${row.rotation}deg) scaleX(${row.flipX ? 1 : -1}) scaleY(${row.flipY ? -1 : 1})">
          ${sticker}
        </div>
        <div class="sticker-meta">${escapeHtml(row.nickname)} · ${escapeHtml(row.shirtId)} · ${escapeHtml(row.zoneLabel)}</div>
      </article>
    `);
  }

  const html = `<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8"/>
  <title>Nordicrew print layout</title>
  <style>
    @page { size: A3 landscape; margin: 8mm; }
    * { box-sizing: border-box; }
    body { margin: 0; font-family: Arial, sans-serif; color: #111; background: #fff; }
    .print-page { page-break-after: always; padding: 0; }
    h1 { font-size: 16pt; margin: 0 0 3mm; letter-spacing: .5pt; }
    p { margin: 0 0 5mm; font-size: 9pt; color: #555; }
    .overview-shirt { border: .3mm solid #222; margin: 0 0 5mm; padding: 3mm; break-inside: avoid; }
    .overview-shirt header { display: flex; justify-content: space-between; gap: 5mm; font-size: 8pt; margin-bottom: 3mm; }
    .overview-zones { display: grid; grid-template-columns: 1fr 1fr .7fr; gap: 4mm; }
    .zone-box { position: relative; height: 58mm; border: .25mm dashed #999; background: #f8f8f8; overflow: hidden; }
    .zone-box.sleeve { height: 58mm; }
    .zone-title { position: absolute; left: 2mm; top: 2mm; font-size: 7pt; color: #666; }
    .overview-item { position: absolute; border: .35mm solid #111; display: grid; place-items: center; font-size: 8pt; font-weight: 700; background: rgba(255,255,255,.72); }
    .roll-page { width: 560mm; min-height: 900mm; }
    .roll-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 5mm; }
    .roll-head h1 { margin: 0; }
    .roll-grid { display: flex; flex-wrap: wrap; align-content: flex-start; gap: 3mm; }
    .print-sticker { position: relative; border: .25mm dashed #aaa; display: grid; place-items: center; padding: 5mm 2mm 4mm; break-inside: avoid; }
    .sticker-code { position: absolute; left: 1.5mm; top: 1mm; font-size: 8pt; font-weight: 800; border: .25mm solid #111; padding: .4mm 1mm; background: #fff; }
    .sticker-art { display: grid; place-items: center; transform-origin: 50% 50%; }
    .sticker-meta { position: absolute; left: 1.5mm; right: 1.5mm; bottom: 1mm; font-size: 5.5pt; color: #555; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .missing-logo { width: 100%; height: 100%; display: grid; place-items: center; border: .25mm solid #777; }
    @media print { .no-print { display: none; } body { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }
  </style>
</head>
<body>
  <button class="no-print" onclick="window.print()" style="position:fixed;right:12px;top:12px;z-index:5">Сохранить в PDF / печать</button>
  ${overviewPages || '<section class="print-page"><h1>Нет готовых наклеек</h1></section>'}
  <section class="print-page roll-page">
    <div class="roll-head">
      <h1>NORDICREW · РУЛОННАЯ РАСКЛАДКА</h1>
      <p>${rows.length} наклеек · логотипы зеркалированы для термопереноса</p>
    </div>
    <div class="roll-grid">${stickerTiles.join('')}</div>
  </section>
  <script>setTimeout(() => window.print(), 500);</script>
</body>
</html>`;

  const win = window.open('', '_blank');
  if (!win) return;
  win.document.open();
  win.document.write(html);
  win.document.close();
}

// === Admin Export
function AdminExport({ users, shirts, customLib, onShowPdf, onImportState, onResetState }) {
  const fileRef = React.useRef(null);
  const readyUsers = users.filter(u => u.status === 'finished').length;
  const printRows = NordicrewCore.buildPrintRows({ users, shirts, zones: ZONES });
  const downloadCsv = () => {
    NordicrewCore.downloadText(`nordicrew_print_${new Date().toISOString().slice(0,10)}.csv`, 'text/csv', NordicrewCore.toCsv(printRows));
  };
  const downloadJson = () => {
    NordicrewCore.downloadText(`nordicrew_state_${new Date().toISOString().slice(0,10)}.json`, 'application/json', NordicrewCore.exportStateJson({ users, shirts, customLogos: customLib || [] }));
  };
  const importFile = async (file) => {
    if (!file) return;
    const text = await file.text();
    onImportState(text);
    if (fileRef.current) fileRef.current.value = '';
  };
  return (
    <div className="admin-content">
      <div className="admin-sub-h">
        <div>
          <div className="ah-title">ЭКСПОРТ В ПЕЧАТЬ</div>
          <div className="ah-sub">Сборка PDF-раскройки термопереводов с привязкой к участникам</div>
        </div>
      </div>
      <div className="export-grid">
        <div className="export-card">
          <div className="export-h">PDF · РАСКРОЙКА</div>
          <div className="export-num">{printRows.length}</div>
          <div className="export-sub">наклеек готовы к рулонной раскладке</div>
          <button className="btn primary full" onClick={onShowPdf}>📄 СГЕНЕРИРОВАТЬ PDF</button>
          <div className="export-hint">Обзорные листы + плотная рулонная раскладка с кодами A1/B1</div>
        </div>
        <div className="export-card">
          <div className="export-h">CSV · ПРИВЯЗКА</div>
          <div className="export-num">{readyUsers}</div>
          <div className="export-sub">готовых комплектов</div>
          <button className="btn ghost full" onClick={downloadCsv}>📊 СКАЧАТЬ CSV</button>
          <div className="export-hint">Код наклейки · пользователь · футболка · зона · координаты</div>
        </div>
        <div className="export-card">
          <div className="export-h">JSON · ДАННЫЕ</div>
          <div className="export-num">{users.length}</div>
          <div className="export-sub">участников в базе</div>
          <button className="btn ghost full" onClick={downloadJson}>🗂 СКАЧАТЬ JSON</button>
          <input ref={fileRef} type="file" accept=".json,application/json" hidden onChange={e => importFile(e.target.files?.[0])}/>
          <button className="btn ghost full" onClick={() => fileRef.current?.click()}>↥ ИМПОРТ JSON</button>
          <button className="btn danger ghost full" onClick={() => window.confirm('Сбросить локальную базу к данным из Excel?') && onResetState()}>СБРОСИТЬ К EXCEL</button>
          <div className="export-hint">Позже сюда можно загрузить точный roster пользователей</div>
        </div>
      </div>

      <div className="export-table">
        <div className="et-h">
          <div>УЧАСТНИК</div>
          <div>ФУТБОЛКА</div>
          <div>ПРИНТЫ</div>
          <div>СТАТУС</div>
        </div>
        {users.slice(0, 8).flatMap(u => u.shirts.slice(0,2).map(sid => {
          const s = shirts[sid];
          if (!s) return null;
          const prints = ['front','back','sleeve'].reduce((a,k) => a + (s.designs[k]?.length||0), 0);
          return (
            <div key={sid} className="et-row">
              <div><b>#{u.carNumber} {u.nickname}</b></div>
              <div>{s.color.toUpperCase()} · {s.size}</div>
              <div>{prints} принтов</div>
              <div><span className={`pill ${s.status}`}>{s.status === 'done' ? '✓ ГОТОВО' : '◌ ОЖИДАЕТ'}</span></div>
            </div>
          );
        }))}
      </div>
    </div>
  );
}

function PdfExportModal({ users, shirts, customLib, onClose }) {
  const [stage, setStage] = usP('confirm'); // confirm | gen | done
  const printRows = NordicrewCore.buildPrintRows({ users, shirts, zones: ZONES });
  const generate = () => {
    setStage('gen');
    setTimeout(() => setStage('done'), 800);
  };
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        {stage === 'confirm' && (
          <>
            <div className="modal-h">Генерация PDF-раскройки</div>
            <div className="modal-p">
              Будет открыт печатный макет: обзорные листы по участникам и общая плотная рулонная раскладка. Логотипы зеркалируются для термопереноса, коды наклеек остаются читаемыми.
            </div>
            <div className="modal-meta">
              <div><span>Готовых комплектов</span><b>{users.filter(u=>u.status==='finished').length}</b></div>
              <div><span>Всего наклеек</span><b>{printRows.length}</b></div>
              <div><span>Раскладка</span><b>Рулон · плотная сетка</b></div>
              <div><span>Зеркалирование</span><b>ВКЛ · код не зеркалится</b></div>
            </div>
            <div className="modal-actions">
              <button className="btn ghost" onClick={onClose}>ОТМЕНА</button>
              <button className="btn primary" onClick={generate}>📄 СГЕНЕРИРОВАТЬ</button>
            </div>
          </>
        )}
        {stage === 'gen' && (
          <div className="modal-gen">
            <div className="login-pulse"/>
            <div className="modal-h">Раскладка принтов…</div>
            <div className="modal-p">Привязываем дизайны к участникам и оптимизируем cutting plan.</div>
          </div>
        )}
        {stage === 'done' && (
          <>
            <div className="modal-h">PDF готов</div>
            <div className="modal-p">Макет подготовлен. Браузер откроет отдельную вкладку, где можно выбрать принтер или сохранить в PDF.</div>
            <div className="pdf-preview">
              <div className="pdf-page"><div className="pdf-page-h">NORDICREW · 2026 · CUTTING PLAN · 1/12</div><div className="pdf-strips"><div/><div/><div/><div/></div></div>
              <div className="pdf-page"><div className="pdf-page-h">A · УЧАСТНИК · ОБЗОР</div><div className="pdf-strips alt"><div/><div/><div/></div></div>
              <div className="pdf-page"><div className="pdf-page-h">B · УЧАСТНИК · ОБЗОР</div><div className="pdf-strips"><div/><div/></div></div>
            </div>
            <div className="modal-actions">
              <button className="btn ghost" onClick={onClose}>ЗАКРЫТЬ</button>
              <button className="btn primary" onClick={() => openPrintLayout({ users, shirts, customLib })}>ОТКРЫТЬ ПЕЧАТНЫЙ МАКЕТ</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { PreviewScreen, AdminPanel });
