const { Icon, Avatar, StatusBadge, Button, Input, Select, Dropzone, VoiceRecorder, BottomSheet, ActionBar, PillToggle } = window.DeterTechDesignSystem_3262d8;

// --- AUTH / API ---------------------------------------------------------------

let authToken = null;
try {
  const stored = JSON.parse(localStorage.getItem('dtfc_session') || 'null');
  authToken = (stored && stored.token) || null;
} catch (e) {}

function authHeaders(extra) {
  const h = { ...(extra || {}) };
  if (authToken) h['Authorization'] = 'Bearer ' + authToken;
  return h;
}

async function apiFetch(path, opts) {
  const r = await fetch('/api' + path, opts);
  let data = null;
  try { data = await r.json(); } catch (e) {}
  if (!r.ok) {
    if (r.status === 401 && API.onUnauthorized) API.onUnauthorized();
    throw new Error((data && data.error) || ('API-Fehler: ' + path));
  }
  return data;
}

const API = {
  onUnauthorized: null,
  setAuthToken(token) { authToken = token; },
  login(username, password) {
    return apiFetch('/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password }),
    });
  },
  get(path) { return apiFetch(path, { headers: authHeaders() }); },
  post(path, formData) { return apiFetch(path, { method: 'POST', headers: authHeaders(), body: formData }); },
  postJSON(path, body) {
    return apiFetch(path, { method: 'POST', headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify(body) });
  },
  patch(path, body) {
    return apiFetch(path, { method: 'PATCH', headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify(body) });
  },
  delete(path) { return apiFetch(path, { method: 'DELETE', headers: authHeaders() }); },
};

function relTime(iso) {
  const d = new Date(iso);
  const diffMin = Math.round((Date.now() - d.getTime()) / 60000);
  if (diffMin < 1) return 'jetzt';
  if (diffMin < 60) return `vor ${diffMin} Min.`;
  const diffH = Math.round(diffMin / 60);
  if (diffH < 24) return `vor ${diffH} Std.`;
  const diffD = Math.round(diffH / 24);
  if (diffD === 1) return 'gestern';
  return `vor ${diffD} Tagen`;
}

function statusLabel(s) {
  return { review: 'In Prüfung', published: 'Veröffentlicht', archived: 'Archiviert' }[s] || s;
}

function roleLabel(r) {
  return { admin: 'Admin', techniker: 'Techniker', team: 'Team' }[r] || r;
}

// Erkennt Desktop-Breite (Sidebar + angedockte Detail-Panels statt oberem
// Umschalter + Bottom-Sheets). Reagiert live auf Fenstergröße-Änderungen.
function useIsDesktop(breakpoint = 960) {
  const [isDesktop, setIsDesktop] = React.useState(
    () => window.matchMedia(`(min-width: ${breakpoint}px)`).matches
  );
  React.useEffect(() => {
    const mq = window.matchMedia(`(min-width: ${breakpoint}px)`);
    const handler = () => setIsDesktop(mq.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, [breakpoint]);
  return isDesktop;
}

// Kleiner lokaler Helfer: Input aus dem Design-System reicht kein `type` durch,
// es gibt also keine Passwort-Variante. Optisch identisch zu Input, gleiche Tokens.
function PasswordField({ label, placeholder, value, onChange }) {
  const [focus, setFocus] = React.useState(false);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {label && (
        <label style={{ fontSize: 'var(--text-label)', fontWeight: 700, letterSpacing: 'var(--ls-label)', textTransform: 'uppercase', color: 'var(--text-tertiary)' }}>{label}</label>
      )}
      <input
        type="password"
        placeholder={placeholder}
        value={value}
        onChange={e => onChange && onChange(e.target.value)}
        onFocus={() => setFocus(true)}
        onBlur={() => setFocus(false)}
        style={{
          width: '100%', boxSizing: 'border-box', background: 'var(--surface-input)',
          border: focus ? '1px solid var(--accent)' : '1px solid var(--border-subtle)',
          borderRadius: 'var(--radius-sm)', color: 'var(--text-primary)', fontFamily: 'var(--font-sans)',
          fontSize: 'var(--text-body)', padding: '14px 16px', outline: 'none',
        }}
      />
    </div>
  );
}

function RolePill({ role }) {
  const colors = {
    admin: { bg: 'rgba(214,242,60,0.14)', fg: 'var(--lime-500)' },
    techniker: { bg: 'rgba(255,255,255,0.10)', fg: 'var(--white)' },
    team: { bg: 'rgba(255,255,255,0.06)', fg: 'var(--text-tertiary)' },
  };
  const c = colors[role] || colors.techniker;
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', padding: '5px 12px', borderRadius: 'var(--radius-pill)', fontSize: 'var(--text-label)', fontWeight: 700, letterSpacing: 'var(--ls-label)', textTransform: 'uppercase', background: c.bg, color: c.fg, whiteSpace: 'nowrap' }}>
      {roleLabel(role)}
    </span>
  );
}

// Dropdown-Menü am Profilbild: öffnet sich beim Klick auf den Avatar, schließt
// bei Klick außerhalb. Bewusst als einfache Liste gehalten, damit später
// leicht weitere Punkte ergänzt werden können. `dropUp` lässt das Menü nach
// oben statt unten aufklappen (für die Sidebar, wo der Avatar unten sitzt).
function ProfileMenu({ user, onOpenTrash, onManageLocations, onViewArchived, onLogout, dropUp, align = 'right', avatarSize }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    if (!open) return;
    const handler = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [open]);

  const itemStyle = {
    display: 'flex', alignItems: 'center', gap: 10, width: '100%', border: 'none',
    background: 'none', cursor: 'pointer', padding: '12px 16px', textAlign: 'left',
    fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 13, color: 'var(--text-primary)',
  };

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <Avatar name={user.name} size={avatarSize} onClick={() => setOpen(o => !o)} />
      {open && (
        <div style={{
          position: 'absolute', [dropUp ? 'bottom' : 'top']: '100%', [align]: 0,
          [dropUp ? 'marginBottom' : 'marginTop']: 8, minWidth: 200,
          background: 'var(--navy-700)', border: '1px solid var(--border-strong)',
          borderRadius: 'var(--radius-sm)', overflow: 'hidden', zIndex: 40,
          boxShadow: 'var(--shadow-modal)',
        }}>
          <button style={itemStyle} onClick={() => { setOpen(false); onOpenTrash(); }}>
            <Icon name="trash-2" size={16} color="var(--text-tertiary)" /> Zuletzt gelöscht
          </button>
          {onViewArchived && (
            <button style={itemStyle} onClick={() => { setOpen(false); onViewArchived(); }}>
              <Icon name="archive" size={16} color="var(--text-tertiary)" /> Archiviert
            </button>
          )}
          {onManageLocations && (
            <button style={itemStyle} onClick={() => { setOpen(false); onManageLocations(); }}>
              <Icon name="map-pin" size={16} color="var(--text-tertiary)" /> Standorte verwalten
            </button>
          )}
          <button style={{ ...itemStyle, color: 'var(--danger)' }} onClick={() => { setOpen(false); onLogout(); }}>
            <Icon name="log-out" size={16} /> Ausloggen
          </button>
        </div>
      )}
    </div>
  );
}

// Gemeinsame Chrome für alle Listen+Detail-Stellen (Beitrag bearbeiten,
// Team-Freigabe, Nutzer-Details): auf Mobile ein Bottom-Sheet-Overlay wie
// bisher, auf Desktop ein angedocktes rechtes Panel neben der Liste.
// Vollbild-Player im Browser für Fotos/Videos aus einem Beitrag, statt die
// Datei in einem neuen Tab zu öffnen. `item` = { url, type }.
function MediaLightbox({ item, onClose }) {
  if (!item) return null;
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.92)', zIndex: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <button onClick={onClose} style={{ position: 'absolute', top: 20, right: 20, background: 'none', border: 'none', cursor: 'pointer', color: 'var(--white)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <line x1="18" y1="6" x2="6" y2="18" />
          <line x1="6" y1="6" x2="18" y2="18" />
        </svg>
      </button>
      <div onClick={e => e.stopPropagation()} style={{ maxWidth: '100%', maxHeight: '100%', display: 'flex' }}>
        {item.type === 'video'
          ? <video src={item.url} controls autoPlay style={{ maxWidth: '100%', maxHeight: '85vh', borderRadius: 'var(--radius-md)' }} />
          : <img src={item.url} style={{ maxWidth: '100%', maxHeight: '85vh', borderRadius: 'var(--radius-md)', objectFit: 'contain' }} />}
      </div>
    </div>
  );
}

function DetailChrome({ open, title, onClose, children }) {
  const isDesktop = useIsDesktop();
  if (!open) return null;
  if (!isDesktop) {
    return <BottomSheet open={open} onClose={onClose} title={title}>{children}</BottomSheet>;
  }
  return (
    <div style={{
      width: 420, flexShrink: 0, borderLeft: '1px solid var(--border-subtle)',
      background: 'var(--navy-800)', position: 'sticky', top: 0, height: '100vh',
      overflowY: 'auto', padding: 'var(--space-6)', boxSizing: 'border-box',
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 'var(--space-5)' }}>
        <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-h2)', fontWeight: 800, color: 'var(--text-primary)' }}>{title}</div>
        <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-tertiary)', display: 'flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, flexShrink: 0 }}>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <line x1="18" y1="6" x2="6" y2="18" />
            <line x1="6" y1="6" x2="18" y2="18" />
          </svg>
        </button>
      </div>
      {children}
    </div>
  );
}

// Standort-Auswahl mit Möglichkeit, direkt einen neuen Standort anzulegen
// (POST /api/projects), statt auf die feste Projektliste beschränkt zu sein.
function ProjectPicker({ projects, value, onChange, onProjectsChanged }) {
  const [adding, setAdding] = React.useState(false);
  const [newName, setNewName] = React.useState('');
  const [creating, setCreating] = React.useState(false);
  const [error, setError] = React.useState('');

  const projectOptions = projects.map(p => ({ value: String(p.id), label: p.name }));

  const create = async () => {
    if (!newName.trim()) return;
    setCreating(true);
    setError('');
    try {
      const p = await API.postJSON('/projects', { name: newName.trim() });
      await onProjectsChanged();
      onChange(String(p.id));
      setNewName('');
      setAdding(false);
    } catch (e) {
      setError(e.message || 'Standort konnte nicht angelegt werden.');
    } finally {
      setCreating(false);
    }
  };

  if (adding) {
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        <Input label="Neuer Standort" placeholder="Name eingeben" value={newName} onChange={setNewName} />
        {error && <div style={{ color: 'var(--danger)', fontFamily: 'var(--font-sans)', fontSize: 13 }}>{error}</div>}
        <div style={{ display: 'flex', gap: 8 }}>
          <Button size="sm" variant="secondary" onClick={() => { setAdding(false); setNewName(''); setError(''); }} style={{ flex: 1 }}>Abbrechen</Button>
          <Button size="sm" disabled={!newName.trim() || creating} onClick={create} style={{ flex: 1 }}>{creating ? 'Wird angelegt…' : 'Anlegen'}</Button>
        </div>
      </div>
    );
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      <Select label="Standort / Projekt" placeholder="Standort wählen" options={projectOptions} value={value} onChange={onChange} />
      <button onClick={() => setAdding(true)} style={{ alignSelf: 'flex-start', background: 'none', border: 'none', color: 'var(--accent)', fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 700, cursor: 'pointer', textDecoration: 'underline' }}>
        + Neuen Standort anlegen
      </button>
    </div>
  );
}

// Mehrfach-Datei-Auswahl (Foto/Video) mit Vorschau-Raster + Entfernen vor dem
// Absenden. Analog zum bestehenden Dropzone aus dem Design-System, aber mit
// `multiple`-Datei-Input statt nur einer Datei.
function MultiDropzone({ files, onFilesChange }) {
  const [drag, setDrag] = React.useState(false);
  const inputRef = React.useRef(null);

  const addFiles = fileList => {
    const arr = Array.from(fileList || []).map(f => ({
      file: f,
      url: URL.createObjectURL(f),
      type: f.type.startsWith('video') ? 'video' : 'image',
    }));
    if (arr.length) onFilesChange([...files, ...arr]);
  };

  const removeAt = i => {
    if (!window.confirm('Diese Datei wirklich entfernen?')) return;
    onFilesChange(files.filter((_, idx) => idx !== i));
  };

  return (
    <div>
      <div
        onDragOver={e => { e.preventDefault(); setDrag(true); }}
        onDragLeave={() => setDrag(false)}
        onDrop={e => { e.preventDefault(); setDrag(false); addFiles(e.dataTransfer.files); }}
        onClick={() => inputRef.current && inputRef.current.click()}
        style={{
          border: `1.5px dashed ${drag ? 'var(--accent)' : 'var(--border-strong)'}`,
          borderRadius: 'var(--radius-md)',
          background: drag ? 'rgba(214,242,60,0.06)' : 'var(--surface-input)',
          minHeight: 120, display: 'flex', flexDirection: 'column', alignItems: 'center',
          justifyContent: 'center', gap: 8, cursor: 'pointer', padding: 16,
        }}
      >
        <Icon name="image-plus" size={28} color="var(--text-tertiary)" />
        <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-body-sm)', color: 'var(--text-secondary)', fontWeight: 600, textAlign: 'center' }}>
          Fotos oder Videos hierher ziehen (mehrere möglich)
        </div>
        <input ref={inputRef} type="file" multiple accept="image/jpeg,image/png,video/mp4,video/quicktime" style={{ display: 'none' }} onChange={e => { addFiles(e.target.files); e.target.value = ''; }} />
      </div>

      {files.length > 0 && (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(84px, 1fr))', gap: 8, marginTop: 10 }}>
          {files.map((f, i) => (
            <div key={i} style={{ position: 'relative', width: '100%', paddingTop: '100%', borderRadius: 'var(--radius-sm)', overflow: 'hidden', background: 'var(--navy-700)' }}>
              {f.type === 'video'
                ? <video src={f.url} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} />
                : <img src={f.url} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} />}
              <button onClick={e => { e.stopPropagation(); removeAt(i); }} style={{ position: 'absolute', top: 4, right: 4, width: 22, height: 22, borderRadius: '50%', border: 'none', background: 'rgba(10,22,38,0.85)', color: 'var(--white)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14, lineHeight: 1 }}>
                ×
              </button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function LoginScreen({ onLogin }) {
  const [username, setUsername] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState('');
  const brand = window.BRANDING || {};

  const submit = async () => {
    if (!username.trim() || !password) return;
    setLoading(true);
    setError('');
    try {
      const session = await API.login(username.trim(), password);
      onLogin(session);
    } catch (e) {
      setError(e.message || 'Login fehlgeschlagen.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{ padding: '48px 24px', display: 'flex', flexDirection: 'column', gap: 32, minHeight: '100vh', justifyContent: 'center' }}>
      <div style={{ maxWidth: 420, width: '100%', margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 32 }}>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }}>
          <img src={brand.logo || 'branding/logo.png'} alt={brand.companyName || 'Logo'} style={{ height: 34 }} />
          <div style={{ fontFamily: 'var(--font-sans)', fontSize: 14, color: 'var(--text-tertiary)', textAlign: 'center' }}>{brand.appName || 'Field Content'} – Anmeldung</div>
        </div>

        {error && <div style={{ color: 'var(--danger)', fontFamily: 'var(--font-sans)', fontSize: 13, textAlign: 'center' }}>{error}</div>}

        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <Input label="Benutzername" placeholder="Benutzername eingeben" value={username} onChange={setUsername} />
          <PasswordField label="Passwort" placeholder="Passwort eingeben" value={password} onChange={setPassword} />
          <Button size="lg" disabled={!username.trim() || !password || loading} onClick={submit}>{loading ? 'Wird geprüft…' : 'Anmelden'}</Button>
        </div>
      </div>
    </div>
  );
}

function EditUploadPanel({ post, user, projects, onClose, onSave, onDelete, onUpdate, onPostsChanged, onProjectsChanged }) {
  const [desc, setDesc] = React.useState(post.description);
  const [projectId, setProjectId] = React.useState(String(post.project_id));
  const [coOwnerId, setCoOwnerId] = React.useState(post.co_owner_id ? String(post.co_owner_id) : '');
  const [directory, setDirectory] = React.useState([]);
  const [saving, setSaving] = React.useState(false);
  const [deleting, setDeleting] = React.useState(false);
  const [uploadingMedia, setUploadingMedia] = React.useState(false);
  const [lightbox, setLightbox] = React.useState(null);
  const fileInputRef = React.useRef(null);

  React.useEffect(() => {
    setDesc(post.description);
    setProjectId(String(post.project_id));
    setCoOwnerId(post.co_owner_id ? String(post.co_owner_id) : '');
  }, [post.id]);

  React.useEffect(() => {
    API.get('/users/directory').then(setDirectory).catch(() => {});
  }, []);

  const save = async () => {
    setSaving(true);
    try {
      await onSave(post.id, { description: desc, project_id: Number(projectId), co_owner_id: coOwnerId ? Number(coOwnerId) : null });
      onClose();
    } finally { setSaving(false); }
  };
  const remove = async () => {
    if (!window.confirm('Diesen Beitrag wirklich unwiderruflich löschen?')) return;
    setDeleting(true);
    try { await onDelete(post.id); onClose(); }
    finally { setDeleting(false); }
  };

  const addFiles = async fileList => {
    const files = Array.from(fileList || []);
    if (!files.length) return;
    setUploadingMedia(true);
    try {
      const fd = new FormData();
      files.forEach(f => fd.append('media', f));
      const { media } = await API.post(`/posts/${post.id}/media`, fd);
      onUpdate(prev => ({ ...prev, media: [...(prev.media || []), ...media] }));
      onPostsChanged();
    } finally {
      setUploadingMedia(false);
    }
  };

  const removeMedia = async mediaId => {
    if (!window.confirm('Diese Datei wirklich löschen?')) return;
    await API.delete(`/posts/${post.id}/media/${mediaId}`);
    onUpdate(prev => ({ ...prev, media: (prev.media || []).filter(m => m.id !== mediaId) }));
    onPostsChanged();
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div>
        <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-label)', fontWeight: 700, letterSpacing: 'var(--ls-label)', textTransform: 'uppercase', color: 'var(--text-tertiary)', marginBottom: 8 }}>Fotos & Videos</div>
        {post.media && post.media.length > 0 ? (
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(84px, 1fr))', gap: 8 }}>
            {post.media.map(m => (
              <div key={m.id} style={{ position: 'relative', width: '100%', paddingTop: '100%', borderRadius: 'var(--radius-sm)', overflow: 'hidden', background: 'var(--navy-700)' }}>
                <button onClick={() => setLightbox(m)} style={{ position: 'absolute', inset: 0, display: 'block', background: 'none', border: 'none', padding: 0, cursor: 'pointer' }}>
                  {m.type === 'video'
                    ? <video src={m.url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
                    : <img src={m.url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />}
                </button>
                <button onClick={() => removeMedia(m.id)} style={{ position: 'absolute', top: 4, right: 4, width: 22, height: 22, borderRadius: '50%', border: 'none', background: 'rgba(10,22,38,0.85)', color: 'var(--white)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14, lineHeight: 1 }}>
                  ×
                </button>
              </div>
            ))}
          </div>
        ) : (
          <div style={{ width: '100%', height: 100, borderRadius: 'var(--radius-md)', background: 'var(--navy-700)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <Icon name="mic" size={30} color="var(--text-tertiary)" />
          </div>
        )}
        <button onClick={() => fileInputRef.current && fileInputRef.current.click()} disabled={uploadingMedia} style={{ marginTop: 10, background: 'none', border: '1px dashed var(--border-strong)', borderRadius: 'var(--radius-sm)', color: 'var(--text-secondary)', fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 700, padding: '10px 14px', cursor: uploadingMedia ? 'default' : 'pointer', width: '100%' }}>
          {uploadingMedia ? 'Wird hochgeladen…' : '+ Datei hinzufügen'}
        </button>
        <input ref={fileInputRef} type="file" multiple accept="image/jpeg,image/png,video/mp4,video/quicktime" style={{ display: 'none' }} onChange={e => { addFiles(e.target.files); e.target.value = ''; }} />
        <MediaLightbox item={lightbox} onClose={() => setLightbox(null)} />
      </div>

      {post.voice_path && <audio src={post.voice_path} controls style={{ width: '100%' }} />}

      <ProjectPicker projects={projects} value={projectId} onChange={setProjectId} onProjectsChanged={onProjectsChanged} />
      <Input label="Kurzbeschreibung" placeholder="Was ist zu sehen?" value={desc} onChange={setDesc} />

      <Select
        label="Mitbesitzer (hat exakt dieselben Rechte wie du an diesem Beitrag)"
        placeholder="Kein Mitbesitzer"
        options={directory.filter(u => u.id !== post.user_id).map(u => ({ value: String(u.id), label: u.name }))}
        value={coOwnerId}
        onChange={setCoOwnerId}
      />

      <div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 12 }}>
        <StatusBadge status={post.status} /> · {relTime(post.created_at)}
      </div>

      <Button size="lg" disabled={!projectId || saving} onClick={save}>{saving ? 'Wird gespeichert…' : 'Änderungen speichern'}</Button>

      <Button size="lg" variant="ghost" disabled={deleting} onClick={remove} style={{ color: 'var(--danger)' }}>{deleting ? 'Löscht…' : 'Beitrag löschen'}</Button>
    </div>
  );
}

function TechnikerDashboard({ user, onLogout, onOpenTrash, posts, projects, onNewUpload, onSaveEdit, onDelete, onPostsChanged, onProjectsChanged }) {
  const mine = posts.filter(p => p.user_id === user.id || p.co_owner_id === user.id);
  const [editing, setEditing] = React.useState(null);
  return (
    <div style={{ display: 'flex' }}>
      <div style={{ flex: 1, minWidth: 0, maxWidth: 720, padding: '24px 20px 100px', display: 'flex', flexDirection: 'column', gap: 24 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-h1)', fontWeight: 800, color: 'var(--text-primary)' }}>Hallo, {user.name.split(' ')[0]}</div>
          <ProfileMenu user={user} onOpenTrash={onOpenTrash} onLogout={onLogout} />
        </div>

        <Button size="lg" onClick={onNewUpload} style={{ width: '100%' }}>+ Neuen Beitrag hochladen</Button>

        <div>
          <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-label)', fontWeight: 700, letterSpacing: 'var(--ls-label)', textTransform: 'uppercase', color: 'var(--text-tertiary)', marginBottom: 12 }}>Deine letzten Uploads</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {mine.length === 0 && <div style={{ color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 14 }}>Noch keine Uploads.</div>}
            {mine.map(p => (
              <div key={p.id} role="button" tabIndex={0} onClick={() => setEditing(p)} onKeyDown={e => { if (e.key === 'Enter') setEditing(p); }} style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', padding: 12, cursor: 'pointer' }}>
                <div style={{ width: 56, height: 56, borderRadius: 'var(--radius-sm)', background: 'var(--navy-700)', flexShrink: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' }}>
                  {p.media && p.media.length > 0
                    ? (p.media[0].type === 'video'
                        ? <video src={p.media[0].url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
                        : <img src={p.media[0].url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />)
                    : <Icon name="mic" size={22} color="var(--text-tertiary)" />}
                  {p.media && p.media.length > 1 && (
                    <span style={{ position: 'absolute', bottom: 2, right: 2, background: 'rgba(10,22,38,0.85)', color: 'var(--text-primary)', fontFamily: 'var(--font-sans)', fontSize: 10, fontWeight: 700, borderRadius: 'var(--radius-pill)', padding: '1px 5px' }}>+{p.media.length - 1}</span>
                  )}
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, color: 'var(--text-primary)', fontSize: 14 }}>{p.project_name}</div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 4, color: 'var(--text-tertiary)' }}>
                    <Icon name="image" size={14} />
                    {p.media && p.media.length > 0 && <span style={{ fontFamily: 'var(--font-sans)', fontSize: 12 }}>{p.media.length}</span>}
                    {p.voice_path && <Icon name="mic" size={14} />}
                    <span style={{ fontFamily: 'var(--font-sans)', fontSize: 12 }}>{relTime(p.created_at)}</span>
                    {p.co_owner_id === user.id && (
                      <span style={{ fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 700, color: 'var(--accent)' }}>· Mitbesitzer</span>
                    )}
                  </div>
                </div>
                <StatusBadge status={p.status} />
                <Icon name="chevron-right" size={18} color="var(--text-tertiary)" />
              </div>
            ))}
          </div>
        </div>
      </div>

      <DetailChrome open={!!editing} title="Beitrag bearbeiten" onClose={() => setEditing(null)}>
        {editing && (
          <EditUploadPanel post={editing} user={user} projects={projects} onClose={() => setEditing(null)} onSave={onSaveEdit} onDelete={onDelete}
            onUpdate={fn => setEditing(e => fn(e))} onPostsChanged={onPostsChanged} onProjectsChanged={onProjectsChanged} />
        )}
      </DetailChrome>
    </div>
  );
}

function UploadFlow({ user, projects, onCancel, onSubmitted, onProjectsChanged }) {
  const [mode, setMode] = React.useState('media');
  const [projectId, setProjectId] = React.useState('');
  const [desc, setDesc] = React.useState('');
  const [coOwnerId, setCoOwnerId] = React.useState('');
  const [directory, setDirectory] = React.useState([]);
  const [mediaFiles, setMediaFiles] = React.useState([]);
  const [recording, setRecording] = React.useState(false);
  const [duration, setDuration] = React.useState(0);
  const [voiceBlob, setVoiceBlob] = React.useState(null);
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState('');
  const timerRef = React.useRef(null);
  const mediaRecorderRef = React.useRef(null);
  const chunksRef = React.useRef([]);

  React.useEffect(() => { API.get('/users/directory').then(setDirectory).catch(() => {}); }, []);

  const toggleRecording = async () => {
    if (recording) {
      mediaRecorderRef.current && mediaRecorderRef.current.stop();
      clearInterval(timerRef.current);
      setRecording(false);
      return;
    }
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const mr = new MediaRecorder(stream);
      chunksRef.current = [];
      mr.ondataavailable = e => chunksRef.current.push(e.data);
      mr.onstop = () => {
        const blob = new Blob(chunksRef.current, { type: 'audio/webm' });
        setVoiceBlob(blob);
        stream.getTracks().forEach(t => t.stop());
      };
      mr.start();
      mediaRecorderRef.current = mr;
      setDuration(0);
      setRecording(true);
      timerRef.current = setInterval(() => setDuration(d => d + 1), 1000);
    } catch (e) {
      setError('Mikrofonzugriff wurde verweigert oder ist nicht verfügbar.');
    }
  };

  const canSubmit = projectId && (mediaFiles.length > 0 || voiceBlob) && !submitting;

  const submit = async () => {
    setSubmitting(true);
    setError('');
    try {
      const fd = new FormData();
      fd.append('project_id', projectId);
      fd.append('description', desc);
      if (coOwnerId) fd.append('co_owner_id', coOwnerId);
      mediaFiles.forEach(f => fd.append('media', f.file));
      if (voiceBlob) fd.append('voice', voiceBlob, 'sprachnachricht.webm');
      await API.post('/posts', fd);
      onSubmitted();
    } catch (e) {
      setError('Upload fehlgeschlagen. Bitte erneut versuchen.');
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div style={{ maxWidth: 640, padding: '24px 20px 40px', display: 'flex', flexDirection: 'column', gap: 20 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        <button onClick={onCancel} style={{ background: 'none', border: 'none', color: 'var(--text-secondary)', cursor: 'pointer', display: 'flex' }}><Icon name="arrow-left" size={22} /></button>
        <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-h2)', fontWeight: 800, color: 'var(--text-primary)' }}>Neuer Beitrag</div>
      </div>

      <ProjectPicker projects={projects} value={projectId} onChange={setProjectId} onProjectsChanged={onProjectsChanged} />

      <div style={{ display: 'flex', background: 'var(--navy-700)', borderRadius: 'var(--radius-pill)', padding: 4, gap: 2, alignSelf: 'flex-start' }}>
        {[{ v: 'media', l: 'Foto / Video' }, { v: 'voice', l: 'Sprachnachricht' }].map(o => (
          <button key={o.v} onClick={() => setMode(o.v)} style={{ border: 'none', cursor: 'pointer', padding: '10px 18px', borderRadius: 'var(--radius-pill)', fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 13, background: mode === o.v ? 'var(--accent)' : 'transparent', color: mode === o.v ? 'var(--text-on-accent)' : 'var(--text-secondary)' }}>{o.l}</button>
        ))}
      </div>

      {mode === 'media'
        ? <MultiDropzone files={mediaFiles} onFilesChange={setMediaFiles} />
        : <VoiceRecorder recording={recording} duration={duration} onToggle={toggleRecording} />}

      {voiceBlob && mode !== 'voice' && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--accent)', fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 700 }}>
          <Icon name="mic" size={16} /> Sprachnachricht ({duration}s) angehängt
        </div>
      )}
      {mode === 'voice' && (
        <button onClick={() => setMode('media')} style={{ background: 'none', border: 'none', color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 13, textAlign: 'left', cursor: 'pointer', textDecoration: 'underline' }}>+ Foto/Video zusätzlich anhängen</button>
      )}

      <Input label="Kurzbeschreibung" placeholder="Was ist zu sehen?" value={desc} onChange={setDesc} />

      <Select
        label="Mitbesitzer (hat exakt dieselben Rechte wie du an diesem Beitrag)"
        placeholder="Kein Mitbesitzer"
        options={directory.filter(u => u.id !== user.id).map(u => ({ value: String(u.id), label: u.name }))}
        value={coOwnerId}
        onChange={setCoOwnerId}
      />

      {error && <div style={{ color: 'var(--danger)', fontFamily: 'var(--font-sans)', fontSize: 13 }}>{error}</div>}

      <Button size="lg" disabled={!canSubmit} onClick={submit}>{submitting ? 'Wird hochgeladen…' : 'Absenden'}</Button>
    </div>
  );
}

async function downloadPostZip(post) {
  const brand = window.BRANDING || {};
  const zip = new JSZip();
  const info = [
    `${brand.companyName || ''} ${brand.appName || ''} – Beitrag #${post.id}`.trim(),
    `Autor: ${post.user_name}`,
    `Projekt: ${post.project_name}`,
    `Status: ${statusLabel(post.status)}`,
    `Für Content markiert: ${post.is_content ? 'ja' : 'nein'}`,
    `Erstellt am: ${new Date(post.created_at).toLocaleString('de-DE')}`,
    '',
    'Beschreibung:',
    post.description || '(keine Beschreibung)',
  ].join('\n');
  zip.file('info.txt', info);

  const attachments = [];
  (post.media || []).forEach((m, i) => {
    const ext = m.url.split('.').pop() || (m.type === 'video' ? 'mp4' : 'jpg');
    attachments.push({ url: m.url, name: `${m.type === 'video' ? 'video' : 'foto'}-${i + 1}.${ext}` });
  });
  if (post.voice_path) attachments.push({ url: post.voice_path, name: 'sprachnachricht.webm' });

  await Promise.all(attachments.map(async a => {
    try {
      const blob = await (await fetch(a.url)).blob();
      zip.file(a.name, blob);
    } catch (e) { /* Datei ggf. nicht erreichbar */ }
  }));

  const blob = await zip.generateAsync({ type: 'blob' });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = `beitrag-${post.id}.zip`;
  document.body.appendChild(a);
  a.click();
  a.remove();
}

function FileRow({ file, onEdit, editing, draft, onDraftChange, onSaveComment, onCancelEdit, onToggleSelected, onDelete, onOpen }) {
  const thumb = file.key === 'voice'
    ? <Icon name="mic" size={18} color="var(--text-tertiary)" />
    : file.type === 'video'
      ? <video src={file.url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
      : <img src={file.url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10, background: 'var(--surface-input)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', padding: 12 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        {file.key === 'voice' ? (
          <div style={{ width: 44, height: 44, borderRadius: 'var(--radius-sm)', background: 'var(--navy-700)', flexShrink: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            {thumb}
          </div>
        ) : (
          <button onClick={onOpen} style={{ width: 44, height: 44, borderRadius: 'var(--radius-sm)', background: 'var(--navy-700)', flexShrink: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center', border: 'none', padding: 0, cursor: 'pointer' }}>
            {thumb}
          </button>
        )}
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 13, color: 'var(--text-primary)' }}>{file.label}</div>
          {!editing && (
            <div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: file.comment ? 'var(--text-secondary)' : 'var(--text-tertiary)', marginTop: 2 }}>{file.comment || 'Kein Kommentar'}</div>
          )}
        </div>
        {onDelete && (
          <button onClick={onDelete} title="Löschen" style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--danger)', display: 'flex', flexShrink: 0 }}>
            <Icon name="trash-2" size={16} />
          </button>
        )}
        <button onClick={onEdit} title="Kommentieren" style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-tertiary)', display: 'flex', flexShrink: 0 }}>
          <Icon name="pencil" size={16} />
        </button>
      </div>

      {editing && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          <Input placeholder="Kommentar zu dieser Datei…" value={draft} onChange={onDraftChange} />
          <div style={{ display: 'flex', gap: 8 }}>
            <Button size="sm" variant="secondary" onClick={onCancelEdit} style={{ flex: 1 }}>Abbrechen</Button>
            <Button size="sm" onClick={onSaveComment} style={{ flex: 1 }}>Speichern</Button>
          </div>
        </div>
      )}

      <button onClick={onToggleSelected} style={{ alignSelf: 'flex-start', display: 'flex', alignItems: 'center', gap: 6, border: file.selected ? '1px solid var(--accent)' : '1px solid var(--border-strong)', background: file.selected ? 'rgba(214,242,60,0.12)' : 'transparent', color: file.selected ? 'var(--accent)' : 'var(--text-tertiary)', borderRadius: 'var(--radius-pill)', padding: '5px 12px', fontFamily: 'var(--font-sans)', fontSize: 12, fontWeight: 700, cursor: 'pointer' }}>
        <Icon name={file.selected ? 'check-circle-2' : 'circle'} size={14} />
        Für Weiterverarbeitung freigegeben
      </button>
    </div>
  );
}

function PostDetailPanel({ post, onClose, onSetStatus, onFileMeta, onUpdate, onPostsChanged, onDeletePost }) {
  const [zipping, setZipping] = React.useState(false);
  const [editingFile, setEditingFile] = React.useState(null);
  const [draft, setDraft] = React.useState('');
  const [uploadingMedia, setUploadingMedia] = React.useState(false);
  const [lightbox, setLightbox] = React.useState(null);
  const fileInputRef = React.useRef(null);

  React.useEffect(() => { setEditingFile(null); setDraft(''); }, [post.id]);

  const addFiles = async fileList => {
    const files = Array.from(fileList || []);
    if (!files.length) return;
    setUploadingMedia(true);
    try {
      const fd = new FormData();
      files.forEach(f => fd.append('media', f));
      const { media } = await API.post(`/posts/${post.id}/media`, fd);
      onUpdate(prev => ({ ...prev, media: [...(prev.media || []), ...media] }));
      onPostsChanged();
    } finally {
      setUploadingMedia(false);
    }
  };

  const removeMediaItem = async mediaId => {
    if (!window.confirm('Diese Datei wirklich löschen?')) return;
    await API.delete(`/posts/${post.id}/media/${mediaId}`);
    onUpdate(prev => ({ ...prev, media: (prev.media || []).filter(m => m.id !== mediaId) }));
    onPostsChanged();
  };

  const files = [
    ...((post.media || []).map((m, i) => ({ key: m.id, label: `${m.type === 'video' ? 'Video' : 'Foto'} ${i + 1}`, url: m.url, type: m.type, comment: m.comment, selected: m.selected }))),
    ...(post.voice_path ? [{ key: 'voice', label: 'Sprachnachricht', url: post.voice_path, comment: post.voice_comment, selected: post.voice_selected }] : []),
  ];

  const updateMedia = (mediaId, patch) => onUpdate(prev => ({
    ...prev,
    media: (prev.media || []).map(m => m.id === mediaId ? { ...m, ...patch } : m),
  }));
  const updateVoice = patch => onUpdate(prev => ({ ...prev, ...patch }));

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div style={{ width: '100%', height: 220, borderRadius: 'var(--radius-md)', background: 'var(--navy-700)', display: 'flex', overflowX: 'auto', gap: 8, padding: (post.media && post.media.length) ? 0 : undefined, alignItems: 'center', justifyContent: (post.media && post.media.length) ? 'flex-start' : 'center' }}>
        {post.media && post.media.length > 0
          ? post.media.map(m => (
              <button key={m.id} onClick={() => setLightbox(m)} style={{ height: '100%', width: 220, flexShrink: 0, display: 'block', background: 'none', border: 'none', padding: 0, cursor: 'pointer' }}>
                {m.type === 'video'
                  ? <video src={m.url} style={{ height: '100%', width: '100%', objectFit: 'cover' }} />
                  : <img src={m.url} style={{ height: '100%', width: '100%', objectFit: 'cover' }} />}
              </button>
            ))
          : <Icon name="mic" size={40} color="var(--text-tertiary)" />}
      </div>
      <MediaLightbox item={lightbox} onClose={() => setLightbox(null)} />

      <div>
        <button onClick={() => fileInputRef.current && fileInputRef.current.click()} disabled={uploadingMedia} style={{ background: 'none', border: '1px dashed var(--border-strong)', borderRadius: 'var(--radius-sm)', color: 'var(--text-secondary)', fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 700, padding: '10px 14px', cursor: uploadingMedia ? 'default' : 'pointer', width: '100%' }}>
          {uploadingMedia ? 'Wird hochgeladen…' : '+ Datei hinzufügen'}
        </button>
        <input ref={fileInputRef} type="file" multiple accept="image/jpeg,image/png,video/mp4,video/quicktime" style={{ display: 'none' }} onChange={e => { addFiles(e.target.files); e.target.value = ''; }} />
      </div>

      {post.voice_path && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--surface-input)', borderRadius: 'var(--radius-sm)', padding: 14 }}>
          <audio src={post.voice_path} controls style={{ width: '100%' }} />
        </div>
      )}
      <div style={{ fontFamily: 'var(--font-sans)', fontSize: 14, color: 'var(--text-secondary)', lineHeight: 'var(--lh-relaxed)' }}>{post.description || 'Keine Beschreibung.'}</div>
      <div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-tertiary)' }}>{post.user_name} · {relTime(post.created_at)}</div>

      <Button size="md" variant="secondary" disabled={zipping} onClick={async () => { setZipping(true); try { await downloadPostZip(post); } finally { setZipping(false); } }} style={{ width: '100%' }}>
        <Icon name="download" size={16} /> {zipping ? 'ZIP wird erstellt…' : 'Alle Dateien + Infos als ZIP herunterladen'}
      </Button>

      {files.length > 0 && (
        <div>
          <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-label)', fontWeight: 700, letterSpacing: 'var(--ls-label)', textTransform: 'uppercase', color: 'var(--text-tertiary)', marginBottom: 10 }}>Einzelne Dateien (nur Team)</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {files.map(f => (
              <FileRow key={f.key} file={f}
                editing={editingFile === f.key}
                draft={draft}
                onEdit={() => { setEditingFile(f.key); setDraft(f.comment || ''); }}
                onDraftChange={setDraft}
                onCancelEdit={() => setEditingFile(null)}
                onSaveComment={() => {
                  onFileMeta(post.id, { file: f.key, comment: draft });
                  f.key === 'voice' ? updateVoice({ voice_comment: draft }) : updateMedia(f.key, { comment: draft });
                  setEditingFile(null);
                }}
                onToggleSelected={() => {
                  const next = !f.selected;
                  onFileMeta(post.id, { file: f.key, selected: next });
                  f.key === 'voice' ? updateVoice({ voice_selected: next }) : updateMedia(f.key, { selected: next });
                }}
                onDelete={f.key === 'voice' ? null : () => removeMediaItem(f.key)}
                onOpen={f.key === 'voice' ? null : () => setLightbox({ url: f.url, type: f.type })}
              />
            ))}
          </div>
        </div>
      )}

      <ActionBar actions={[
        { label: 'Freigeben', primary: true, onClick: () => { if (!window.confirm('Diesen Beitrag wirklich freigeben?')) return; onSetStatus(post.id, { status: 'published' }); onClose(); } },
        { label: post.is_content ? 'Content-Markierung entfernen' : 'Für Content markieren', onClick: () => { onSetStatus(post.id, { is_content: !post.is_content }); onUpdate(prev => ({ ...prev, is_content: !prev.is_content })); } },
        { label: 'Archivieren', danger: true, onClick: () => { onSetStatus(post.id, { status: 'archived' }); onClose(); } },
        { label: 'Löschen', danger: true, onClick: () => { if (!window.confirm('Diesen Beitrag wirklich löschen?')) return; onDeletePost(post.id); onClose(); } },
      ]} />
    </div>
  );
}

function LocationsPanel({ projects, onProjectsChanged }) {
  const [editingId, setEditingId] = React.useState(null);
  const [draftName, setDraftName] = React.useState('');
  const [busyId, setBusyId] = React.useState(null);
  const [error, setError] = React.useState('');

  const startEdit = p => { setEditingId(p.id); setDraftName(p.name); setError(''); };
  const cancelEdit = () => { setEditingId(null); setDraftName(''); };

  const saveRename = async id => {
    if (!draftName.trim()) return;
    setBusyId(id);
    setError('');
    try {
      await API.patch(`/projects/${id}`, { name: draftName.trim() });
      await onProjectsChanged();
      setEditingId(null);
    } catch (e) {
      setError(e.message || 'Standort konnte nicht umbenannt werden.');
    } finally {
      setBusyId(null);
    }
  };

  const remove = async p => {
    if (!window.confirm(`Standort "${p.name}" wirklich löschen?`)) return;
    setBusyId(p.id);
    setError('');
    try {
      await API.delete(`/projects/${p.id}`);
      await onProjectsChanged();
    } catch (e) {
      setError(e.message || 'Standort konnte nicht gelöscht werden.');
    } finally {
      setBusyId(null);
    }
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {error && <div style={{ color: 'var(--danger)', fontFamily: 'var(--font-sans)', fontSize: 13 }}>{error}</div>}
      {projects.length === 0 && <div style={{ color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 14 }}>Keine Standorte vorhanden.</div>}
      {projects.map(p => (
        <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 10, background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', padding: 12 }}>
          {editingId === p.id ? (
            <React.Fragment>
              <Input value={draftName} onChange={setDraftName} style={{ flex: 1 }} />
              <Button size="sm" variant="secondary" onClick={cancelEdit} disabled={busyId === p.id}>Abbrechen</Button>
              <Button size="sm" onClick={() => saveRename(p.id)} disabled={busyId === p.id || !draftName.trim()}>{busyId === p.id ? '…' : 'Speichern'}</Button>
            </React.Fragment>
          ) : (
            <React.Fragment>
              <div style={{ flex: 1, minWidth: 0, fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 14, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</div>
              <button onClick={() => startEdit(p)} title="Umbenennen" style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-tertiary)', display: 'flex', flexShrink: 0 }}>
                <Icon name="pencil" size={16} />
              </button>
              <button onClick={() => remove(p)} disabled={busyId === p.id} title="Löschen" style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--danger)', display: 'flex', flexShrink: 0 }}>
                <Icon name="trash-2" size={16} />
              </button>
            </React.Fragment>
          )}
        </div>
      ))}
    </div>
  );
}

function ArchivedPanel({ onRestored }) {
  const [posts, setPosts] = React.useState(null);
  const [restoringId, setRestoringId] = React.useState(null);

  const load = () => API.get('/posts/archived').then(setPosts).catch(() => setPosts([]));
  React.useEffect(() => { load(); }, []);

  const restore = async id => {
    setRestoringId(id);
    try {
      await API.patch(`/posts/${id}/status`, { status: 'review' });
      await load();
      onRestored && onRestored();
    } finally {
      setRestoringId(null);
    }
  };

  if (posts === null) {
    return <div style={{ color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 14 }}>Lädt…</div>;
  }
  if (posts.length === 0) {
    return <div style={{ color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 14 }}>Keine archivierten Beiträge.</div>;
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {posts.map(p => (
        <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', padding: 12 }}>
          <div style={{ width: 48, height: 48, borderRadius: 'var(--radius-sm)', background: 'var(--navy-700)', flexShrink: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            {p.media && p.media.length > 0 ? <img src={p.media[0].url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : <Icon name="mic" size={20} color="var(--text-tertiary)" />}
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, color: 'var(--text-primary)', fontSize: 13 }}>{p.project_name}</div>
            <div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-tertiary)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.user_name} · {relTime(p.created_at)}</div>
          </div>
          <Button size="sm" variant="secondary" disabled={restoringId === p.id} onClick={() => restore(p.id)}>
            {restoringId === p.id ? '…' : 'Wiederherstellen'}
          </Button>
        </div>
      ))}
    </div>
  );
}

function TrashPanel({ onRestored }) {
  const [posts, setPosts] = React.useState(null);
  const [restoringId, setRestoringId] = React.useState(null);

  const load = () => API.get('/posts/deleted').then(setPosts).catch(() => setPosts([]));
  React.useEffect(() => { load(); }, []);

  const restore = async id => {
    setRestoringId(id);
    try {
      await API.postJSON(`/posts/${id}/restore`, {});
      await load();
      onRestored && onRestored();
    } finally {
      setRestoringId(null);
    }
  };

  if (posts === null) {
    return <div style={{ color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 14 }}>Lädt…</div>;
  }
  if (posts.length === 0) {
    return <div style={{ color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 14 }}>Keine gelöschten Beiträge.</div>;
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {posts.map(p => (
        <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', padding: 12 }}>
          <div style={{ width: 48, height: 48, borderRadius: 'var(--radius-sm)', background: 'var(--navy-700)', flexShrink: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            {p.media && p.media.length > 0 ? <img src={p.media[0].url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : <Icon name="mic" size={20} color="var(--text-tertiary)" />}
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, color: 'var(--text-primary)', fontSize: 13 }}>{p.project_name}</div>
            <div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-tertiary)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.user_name} · gelöscht {relTime(p.deleted_at)}</div>
          </div>
          <Button size="sm" variant="secondary" disabled={restoringId === p.id} onClick={() => restore(p.id)}>
            {restoringId === p.id ? '…' : 'Wiederherstellen'}
          </Button>
        </div>
      ))}
    </div>
  );
}

function TeamDashboard({ user, onLogout, onOpenTrash, onManageLocations, onViewArchived, posts, onSetStatus, onFileMeta, onPostsChanged, onDeletePost }) {
  const [active, setActive] = React.useState(null);
  const newCount = posts.filter(p => p.status === 'review').length;

  return (
    <div style={{ display: 'flex' }}>
      <div style={{ flex: 1, minWidth: 0, padding: '24px 20px 40px', display: 'flex', flexDirection: 'column', gap: 20 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
          <div>
            <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-h1)', fontWeight: 800, color: 'var(--text-primary)' }}>Eingehendes Material</div>
            <div style={{ fontFamily: 'var(--font-sans)', fontSize: 13, color: 'var(--accent)', fontWeight: 700, marginTop: 6 }}>{newCount} neue Beiträge zur Prüfung</div>
          </div>
          <ProfileMenu user={user} onOpenTrash={onOpenTrash} onManageLocations={onManageLocations} onViewArchived={onViewArchived} onLogout={onLogout} />
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: 12 }}>
          {posts.length === 0 && <div style={{ gridColumn: '1 / -1', color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 14 }}>Noch keine Beiträge eingegangen.</div>}
          {posts.map(p => (
            <div key={p.id} onClick={() => setActive(p)} style={{ background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', padding: 14, cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 8 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
                <div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-tertiary)', fontWeight: 700 }}>{p.user_name}</div>
                <StatusBadge status={p.status === 'review' ? 'new' : p.status} />
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <Icon name="image" size={14} color="var(--text-secondary)" />
                {p.media && <span style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-secondary)' }}>{p.media.length}</span>}
                {p.voice_path && <Icon name="mic" size={14} color="var(--text-secondary)" />}
                <span style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 13, color: 'var(--text-primary)' }}>{p.project_name}</span>
              </div>
              <div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-tertiary)', lineHeight: 'var(--lh-relaxed)', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{p.description}</div>
              {!!p.is_content && (
                <span style={{ alignSelf: 'flex-start', fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 700, color: 'var(--accent)', border: '1px solid var(--accent)', borderRadius: 'var(--radius-pill)', padding: '3px 10px' }}>Für Content markiert</span>
              )}
            </div>
          ))}
        </div>
      </div>

      <DetailChrome open={!!active} title={active ? active.project_name : ''} onClose={() => setActive(null)}>
        {active && (
          <PostDetailPanel
            post={active}
            onClose={() => setActive(null)}
            onSetStatus={onSetStatus}
            onFileMeta={onFileMeta}
            onUpdate={fn => setActive(a => fn(a))}
            onPostsChanged={onPostsChanged}
            onDeletePost={onDeletePost}
          />
        )}
      </DetailChrome>
    </div>
  );
}

function UserDetailPanel({ user, onClose, onUpdateRole, onToggleActive, onResetPassword, onDeleteUser }) {
  const [role, setRole] = React.useState(user.role);
  const [newPassword, setNewPassword] = React.useState('');
  const [saving, setSaving] = React.useState(false);
  const [resetting, setResetting] = React.useState(false);
  const [deleting, setDeleting] = React.useState(false);
  const [notice, setNotice] = React.useState('');

  React.useEffect(() => {
    setRole(user.role);
    setNewPassword('');
    setNotice('');
  }, [user.id]);

  const roleOptions = [
    { value: 'techniker', label: 'Techniker' },
    { value: 'team', label: 'Team' },
    { value: 'admin', label: 'Admin' },
  ];

  const saveRole = async () => {
    setSaving(true);
    try { await onUpdateRole(user.id, role); setNotice('Rolle gespeichert.'); }
    finally { setSaving(false); }
  };

  const toggleActive = async () => {
    setSaving(true);
    try { await onToggleActive(user.id, !user.is_active); onClose(); }
    finally { setSaving(false); }
  };

  const resetPassword = async () => {
    if (newPassword.length < 6) { setNotice('Passwort muss mindestens 6 Zeichen haben.'); return; }
    setResetting(true);
    try { await onResetPassword(user.id, newPassword); setNewPassword(''); setNotice('Passwort zurückgesetzt.'); }
    finally { setResetting(false); }
  };

  const removeUser = async () => {
    if (!window.confirm(`"${user.name}" wirklich unwiderruflich löschen? Alle Beiträge dieses Nutzers werden dabei ebenfalls unwiderruflich gelöscht.`)) return;
    setDeleting(true);
    try { await onDeleteUser(user.id); onClose(); }
    catch (e) { setNotice(e.message || 'Nutzer konnte nicht gelöscht werden.'); }
    finally { setDeleting(false); }
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 13 }}>
        @{user.username} · <RolePill role={user.role} /> · {user.is_active ? 'aktiv' : 'deaktiviert'}
      </div>

      {notice && <div style={{ color: 'var(--accent)', fontFamily: 'var(--font-sans)', fontSize: 13 }}>{notice}</div>}

      <Select label="Rolle" options={roleOptions} value={role} onChange={setRole} />
      <Button size="md" disabled={saving || role === user.role} onClick={saveRole}>{saving ? 'Speichert…' : 'Rolle speichern'}</Button>

      <div style={{ borderTop: '1px solid var(--border-subtle)', paddingTop: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
        <PasswordField label="Neues Passwort" placeholder="Mind. 6 Zeichen" value={newPassword} onChange={setNewPassword} />
        <Button size="md" variant="secondary" disabled={resetting || newPassword.length < 6} onClick={resetPassword}>{resetting ? 'Setzt zurück…' : 'Passwort zurücksetzen'}</Button>
      </div>

      <Button size="md" variant="ghost" disabled={saving} onClick={toggleActive} style={{ color: user.is_active ? 'var(--danger)' : 'var(--accent)' }}>
        {user.is_active ? 'Nutzer deaktivieren' : 'Nutzer aktivieren'}
      </Button>

      <div style={{ borderTop: '1px solid var(--border-subtle)', paddingTop: 16 }}>
        <Button size="md" variant="ghost" disabled={deleting} onClick={removeUser} style={{ color: 'var(--danger)', width: '100%' }}>
          {deleting ? 'Löscht…' : 'Nutzer löschen'}
        </Button>
      </div>
    </div>
  );
}

function AdminPanel({ user, onLogout, onOpenTrash, onManageLocations, onViewArchived, users, onCreateUser, onUpdateRole, onToggleActive, onResetPassword, onDeleteUser }) {
  const [active, setActive] = React.useState(null);
  const [name, setName] = React.useState('');
  const [username, setUsername] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [role, setRole] = React.useState('techniker');
  const [creating, setCreating] = React.useState(false);
  const [error, setError] = React.useState('');

  const roleOptions = [
    { value: 'techniker', label: 'Techniker' },
    { value: 'team', label: 'Team' },
    { value: 'admin', label: 'Admin' },
  ];

  const create = async () => {
    setError('');
    if (!name.trim() || !username.trim() || password.length < 6) {
      setError('Name, Benutzername und ein Passwort mit mind. 6 Zeichen sind erforderlich.');
      return;
    }
    setCreating(true);
    try {
      await onCreateUser({ name: name.trim(), username: username.trim(), password, role });
      setName(''); setUsername(''); setPassword(''); setRole('techniker');
    } catch (e) {
      setError(e.message || 'Nutzer konnte nicht angelegt werden.');
    } finally {
      setCreating(false);
    }
  };

  return (
    <div style={{ display: 'flex' }}>
      <div style={{ flex: 1, minWidth: 0, maxWidth: 720, padding: '24px 20px 40px', display: 'flex', flexDirection: 'column', gap: 24 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-h1)', fontWeight: 800, color: 'var(--text-primary)' }}>Nutzerverwaltung</div>
          <ProfileMenu user={user} onOpenTrash={onOpenTrash} onManageLocations={onManageLocations} onViewArchived={onViewArchived} onLogout={onLogout} />
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {users.length === 0 && <div style={{ color: 'var(--text-tertiary)', fontFamily: 'var(--font-sans)', fontSize: 14 }}>Lade Nutzer…</div>}
          {users.map(u => (
            <div key={u.id} role="button" tabIndex={0} onClick={() => setActive(u)} onKeyDown={e => { if (e.key === 'Enter') setActive(u); }} style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', padding: 12, cursor: 'pointer', opacity: u.is_active ? 1 : 0.5 }}>
              <Avatar name={u.name} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, color: 'var(--text-primary)', fontSize: 14 }}>{u.name}</div>
                <div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-tertiary)', marginTop: 2 }}>@{u.username}{!u.is_active ? ' · deaktiviert' : ''}</div>
              </div>
              <RolePill role={u.role} />
              <Icon name="chevron-right" size={18} color="var(--text-tertiary)" />
            </div>
          ))}
        </div>

        <div style={{ borderTop: '1px solid var(--border-subtle)', paddingTop: 20, display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div style={{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-label)', fontWeight: 700, letterSpacing: 'var(--ls-label)', textTransform: 'uppercase', color: 'var(--text-tertiary)' }}>Neuen Nutzer anlegen</div>
          {error && <div style={{ color: 'var(--danger)', fontFamily: 'var(--font-sans)', fontSize: 13 }}>{error}</div>}
          <Input label="Name" placeholder="Vor- und Nachname" value={name} onChange={setName} />
          <Input label="Benutzername" placeholder="z.B. max.mustermann" value={username} onChange={setUsername} />
          <PasswordField label="Passwort" placeholder="Mind. 6 Zeichen" value={password} onChange={setPassword} />
          <Select label="Rolle" options={roleOptions} value={role} onChange={setRole} />
          <Button size="lg" disabled={creating} onClick={create}>{creating ? 'Wird angelegt…' : 'Nutzer anlegen'}</Button>
        </div>
      </div>

      <DetailChrome open={!!active} title={active ? active.name : ''} onClose={() => setActive(null)}>
        {active && (
          <UserDetailPanel user={active} onClose={() => setActive(null)} onUpdateRole={onUpdateRole} onToggleActive={onToggleActive} onResetPassword={onResetPassword} onDeleteUser={onDeleteUser} />
        )}
      </DetailChrome>
    </div>
  );
}

function Sidebar({ user, view, screen, onSelectView, onSelectScreen, onOpenTrash, onManageLocations, onViewArchived, onLogout }) {
  const brand = window.BRANDING || {};
  const items = user.role === 'admin'
    ? [
        { key: 'tech', label: 'Techniker-Ansicht', icon: 'user', active: view === 'tech', onClick: () => onSelectView('tech') },
        { key: 'team', label: 'Team-Ansicht', icon: 'users', active: view === 'team', onClick: () => onSelectView('team') },
        { key: 'admin', label: 'Nutzerverwaltung', icon: 'shield', active: view === 'admin', onClick: () => onSelectView('admin') },
      ]
    : user.role === 'techniker'
    ? [
        { key: 'dashboard', label: 'Meine Beiträge', icon: 'layout-grid', active: screen === 'dashboard', onClick: () => onSelectScreen('dashboard') },
        { key: 'upload', label: 'Neuer Beitrag', icon: 'plus', active: screen === 'upload', onClick: () => onSelectScreen('upload') },
      ]
    : [
        { key: 'team', label: 'Eingehendes Material', icon: 'inbox', active: true, onClick: () => {} },
      ];

  return (
    <div style={{
      width: 260, flexShrink: 0, minHeight: '100vh', position: 'sticky', top: 0,
      display: 'flex', flexDirection: 'column', borderRight: '1px solid var(--border-subtle)',
      padding: 'var(--space-6) var(--space-5)', boxSizing: 'border-box', background: 'var(--navy-950)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 'var(--space-10)' }}>
        <img src={brand.logo || 'branding/logo.png'} alt={brand.companyName || 'Logo'} style={{ height: 24 }} />
        <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 13, color: 'var(--text-secondary)' }}>{brand.appName || 'Field Content'}</div>
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 4, flex: 1 }}>
        {items.map(it => (
          <button key={it.key} onClick={it.onClick} style={{
            display: 'flex', alignItems: 'center', gap: 10, border: 'none', cursor: 'pointer',
            background: it.active ? 'rgba(214,242,60,0.12)' : 'transparent',
            color: it.active ? 'var(--accent)' : 'var(--text-secondary)',
            borderRadius: 'var(--radius-sm)', padding: '10px 12px', textAlign: 'left',
            fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 14,
          }}>
            <Icon name={it.icon} size={18} />
            {it.label}
          </button>
        ))}
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderTop: '1px solid var(--border-subtle)', paddingTop: 'var(--space-4)' }}>
        <ProfileMenu user={user} onOpenTrash={onOpenTrash} onManageLocations={user.role !== 'techniker' ? onManageLocations : null} onViewArchived={user.role !== 'techniker' ? onViewArchived : null} onLogout={onLogout} avatarSize={36} dropUp align="left" />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 13, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{user.name}</div>
          <div style={{ fontFamily: 'var(--font-sans)', fontSize: 11, color: 'var(--text-tertiary)' }}>{roleLabel(user.role)}</div>
        </div>
      </div>
    </div>
  );
}

function App() {
  const [session, setSession] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('dtfc_session') || 'null'); } catch (e) { return null; }
  });
  const user = session && session.user;
  const isDesktop = useIsDesktop();

  const [projects, setProjects] = React.useState([]);
  const [posts, setPosts] = React.useState([]);
  const [adminUsers, setAdminUsers] = React.useState([]);
  const [view, setView] = React.useState('tech');
  const [screen, setScreen] = React.useState('dashboard');
  const [trashOpen, setTrashOpen] = React.useState(false);
  const [locationsOpen, setLocationsOpen] = React.useState(false);
  const [archivedOpen, setArchivedOpen] = React.useState(false);

  const logout = React.useCallback(() => {
    localStorage.removeItem('dtfc_session');
    API.setAuthToken(null);
    setSession(null);
    setView('tech');
  }, []);

  React.useEffect(() => { API.onUnauthorized = logout; }, [logout]);
  React.useEffect(() => { if (session && session.token) API.setAuthToken(session.token); }, [session]);

  // Gespeichertes Token beim Start validieren – ein 401 löst über API.onUnauthorized
  // automatisch den Logout aus.
  React.useEffect(() => {
    if (session) API.get('/auth/me').catch(() => {});
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const login = sessionObj => {
    localStorage.setItem('dtfc_session', JSON.stringify(sessionObj));
    API.setAuthToken(sessionObj.token);
    setSession(sessionObj);
  };

  const loadProjects = () => API.get('/projects').then(setProjects).catch(() => {});
  const loadPosts = () => API.get('/posts').then(setPosts).catch(() => {});
  const loadAdminUsers = () => API.get('/users').then(setAdminUsers).catch(() => {});

  React.useEffect(() => {
    if (!user) return;
    loadProjects();
    loadPosts();
    const iv = setInterval(loadPosts, 8000);
    return () => clearInterval(iv);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [user && user.id]);

  const effectiveView = !user ? null : user.role === 'admin' ? view : user.role === 'team' ? 'team' : 'tech';

  React.useEffect(() => {
    if (user && user.role === 'admin' && effectiveView === 'admin') loadAdminUsers();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [user && user.role, effectiveView]);

  if (!user) return <LoginScreen onLogin={login} />;

  const setStatus = (id, body) => API.patch(`/posts/${id}/status`, body).then(loadPosts);
  const selectView = v => { setView(v); setScreen('dashboard'); };

  const mainContent = (
    <div style={{ flex: 1, minWidth: 0 }}>
      {!isDesktop && user.role === 'admin' && (
        <div style={{ position: 'sticky', top: 0, zIndex: 30, background: 'var(--navy-950)', borderBottom: '1px solid var(--border-subtle)', padding: '16px 20px', display: 'flex', justifyContent: 'center' }}>
          <PillToggle
            options={[
              { value: 'tech', label: 'Techniker-Ansicht' },
              { value: 'team', label: 'Team-Ansicht' },
              { value: 'admin', label: 'Nutzerverwaltung' },
            ]}
            value={view}
            onChange={selectView}
          />
        </div>
      )}

      {effectiveView === 'tech' && (user.role === 'admin' || user.role === 'techniker') && screen === 'dashboard' && (
        <TechnikerDashboard user={user} onLogout={logout} onOpenTrash={() => setTrashOpen(true)} posts={posts} projects={projects} onNewUpload={() => setScreen('upload')}
          onSaveEdit={(id, body) => API.patch(`/posts/${id}`, body).then(loadPosts)}
          onDelete={id => API.delete(`/posts/${id}`).then(loadPosts)}
          onPostsChanged={loadPosts}
          onProjectsChanged={loadProjects} />
      )}
      {effectiveView === 'tech' && (user.role === 'admin' || user.role === 'techniker') && screen === 'upload' && (
        <UploadFlow user={user} projects={projects} onCancel={() => setScreen('dashboard')} onSubmitted={() => { setScreen('dashboard'); loadPosts(); }} onProjectsChanged={loadProjects} />
      )}
      {effectiveView === 'team' && (user.role === 'admin' || user.role === 'team') && (
        <TeamDashboard user={user} onLogout={logout} onOpenTrash={() => setTrashOpen(true)} onManageLocations={() => setLocationsOpen(true)} onViewArchived={() => setArchivedOpen(true)} posts={posts} onSetStatus={setStatus} onFileMeta={(id, body) => API.patch(`/posts/${id}/files`, body).then(loadPosts)} onPostsChanged={loadPosts} onDeletePost={id => API.delete(`/posts/${id}`).then(loadPosts)} />
      )}
      {effectiveView === 'admin' && user.role === 'admin' && (
        <AdminPanel
          user={user}
          onLogout={logout}
          onOpenTrash={() => setTrashOpen(true)}
          onManageLocations={() => setLocationsOpen(true)}
          onViewArchived={() => setArchivedOpen(true)}
          users={adminUsers}
          onCreateUser={data => API.postJSON('/users', data).then(loadAdminUsers)}
          onUpdateRole={(id, role) => API.patch(`/users/${id}`, { role }).then(loadAdminUsers)}
          onToggleActive={(id, isActive) => API.patch(`/users/${id}`, { is_active: isActive }).then(loadAdminUsers)}
          onResetPassword={(id, password) => API.postJSON(`/users/${id}/reset-password`, { password })}
          onDeleteUser={id => API.delete(`/users/${id}`).then(loadAdminUsers)}
        />
      )}
    </div>
  );

  return (
    <div style={{ display: isDesktop ? 'flex' : 'block', minHeight: '100vh' }}>
      {isDesktop && (
        <Sidebar user={user} view={view} screen={screen} onSelectView={selectView} onSelectScreen={setScreen} onOpenTrash={() => setTrashOpen(true)} onManageLocations={() => setLocationsOpen(true)} onViewArchived={() => setArchivedOpen(true)} onLogout={logout} />
      )}
      {mainContent}
      <DetailChrome open={trashOpen} title="Zuletzt gelöscht" onClose={() => setTrashOpen(false)}>
        {trashOpen && <TrashPanel onRestored={loadPosts} />}
      </DetailChrome>
      <DetailChrome open={locationsOpen} title="Standorte verwalten" onClose={() => setLocationsOpen(false)}>
        {locationsOpen && <LocationsPanel projects={projects} onProjectsChanged={loadProjects} />}
      </DetailChrome>
      <DetailChrome open={archivedOpen} title="Archiviert" onClose={() => setArchivedOpen(false)}>
        {archivedOpen && <ArchivedPanel onRestored={loadPosts} />}
      </DetailChrome>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('app')).render(<App />);
