// PHYSIO SAMOS — admin console: sign-in, the dashboard (with the blog switch) and the posts list.
(() => {
  const { useState, useEffect, useRef } = React;
  const PSA = window.PSA;
  const { Icon, api, toast, confirm, errorText, Link, Field, StateBadge, fmtDate, fmtDay, fmtRelative } = PSA;

  // ---------- sign-in ----------
  PSA.SignIn = ({ expired, onSignedIn }) => {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [show, setShow] = useState(false);
    const [busy, setBusy] = useState(false);
    const [error, setError] = useState(null);
    const submit = async (e) => {
      e.preventDefault();
      setBusy(true);
      setError(null);
      try {
        // Better Auth's own sign-in endpoint (the Worker adds the per-address lockout around it).
        await api("/api/auth/sign-in/email", { method: "POST", body: { email, password, rememberMe: true } });
        const next = new URLSearchParams(location.search).get("next");
        // /preview/:id is a page the Worker renders, not a console route: leave the app for it.
        if (next && /^\/preview\/\d+$/.test(next)) { location.href = next; return; }
        setPassword("");
        onSignedIn();
      } catch (err) {
        setError(err);
        setBusy(false);
      }
    };
    return (
      <div className="signin signin--split">
        {/* The brand panel. It is drawn from tokens; a photograph can be layered in from CSS
            (.signin__art) once one exists, and nothing here depends on it. */}
        <aside className="signin__art" aria-hidden="true">
          <div className="signin__art-copy">
            <p className="signin__art-eyebrow">Blog · Διαχείριση</p>
            <p className="signin__art-title">Γράψτε, μεταφράστε και δημοσιεύστε <em>από ένα σημείο.</em></p>
          </div>
        </aside>
        <div className="signin__form">
          <form className="signin__box" onSubmit={submit} noValidate>
            <div className="signin__logo"><PSA.Logo fill /></div>
            <div className="signin__head">
              <h1>Σύνδεση</h1>
              <p>Με το email και τον κωδικό του λογαριασμού διαχείρισης.</p>
            </div>
            {expired && !error && <div className="notice notice--info"><Icon name="clock" size="sm" /><div>Η σύνδεση έληξε. Συνδεθείτε ξανά· ό,τι δεν είχε αποθηκευτεί κρατήθηκε σε αυτή τη συσκευή.</div></div>}
            {error && <div className="notice notice--danger" role="alert"><Icon name="warning" size="sm" /><div>{errorText(error)}</div></div>}
            <Field label="Email" htmlFor="si-email">
              <input id="si-email" className="input" type="email" autoComplete="username" required value={email} onChange={(e) => setEmail(e.target.value)} autoFocus />
            </Field>
            <Field label="Κωδικός" htmlFor="si-password">
              <div className="input-group">
                <input id="si-password" className="input" type={show ? "text" : "password"} autoComplete="current-password" required value={password} onChange={(e) => setPassword(e.target.value)} />
                <button type="button" className="btn btn--quiet btn--icon" aria-label={show ? "Απόκρυψη κωδικού" : "Εμφάνιση κωδικού"} aria-pressed={show} onClick={() => setShow(!show)}>
                  <Icon name={show ? "lock" : "eye"} size="sm" />
                </button>
              </div>
            </Field>
            <button type="submit" className="btn btn--primary btn--block" disabled={busy || !email || !password}>
              {busy ? <><Icon name="loader" size="sm" className="spin" />Σύνδεση…</> : "Σύνδεση"}
            </button>
            <p className="hint">Ξεχάσατε τον κωδικό; Μιλήστε με τον διαχειριστή του ιστότοπου: ο κωδικός αλλάζει μόνο από εκεί.</p>
            <p className="signin__foot"><Icon name="lock" size="sm" />Αποκλειστικά για εσωτερική χρήση.</p>
          </form>
        </div>
      </div>
    );
  };

  // ---------- first sign-in: choose your own password ----------
  PSA.FirstPassword = ({ user, onDone, onSignOut }) => {
    const [current, setCurrent] = useState("");
    const [next, setNext] = useState("");
    const [again, setAgain] = useState("");
    const [busy, setBusy] = useState(false);
    const [error, setError] = useState(null);
    const submit = async (e) => {
      e.preventDefault();
      setBusy(true);
      setError(null);
      try {
        await api("/api/account/password", { method: "POST", body: { current, next } });
        toast("Ο κωδικός σας αποθηκεύτηκε.");
        onDone();
      } catch (err) {
        setError(err);
        setBusy(false);
      }
    };
    const mismatch = again && next !== again;
    return (
      <div className="signin signin--single">
        <div className="signin__form">
          <form className="signin__box" onSubmit={submit} noValidate>
            <div>
              <h1>Ορίστε τον δικό σας κωδικό</h1>
              <p className="muted" style={{ marginTop: 6 }}>
                Ο κωδικός με τον οποίο συνδεθήκατε δόθηκε από κάποιον άλλον. Πριν συνεχίσετε, διαλέξτε έναν που θα ξέρετε μόνο εσείς.
              </p>
            </div>
            {error && <div className="notice notice--danger" role="alert"><Icon name="warning" size="sm" /><div>{errorText(error)}</div></div>}
            {/* Lets a password manager file the new password under the right account. */}
            <input type="email" autoComplete="username" value={user.email} readOnly hidden />
            <Field label="Ο κωδικός που σας δόθηκε" htmlFor="fp-current">
              <input id="fp-current" className="input" type="password" autoComplete="current-password" value={current} onChange={(e) => setCurrent(e.target.value)} autoFocus />
            </Field>
            <Field label="Νέος κωδικός" htmlFor="fp-next" hint="Τουλάχιστον 10 χαρακτήρες. Μια φράση με λίγες λέξεις είναι και ασφαλής και εύκολη να τη θυμάστε.">
              <input id="fp-next" className="input" type="password" autoComplete="new-password" value={next} onChange={(e) => setNext(e.target.value)} />
            </Field>
            <Field label="Ξανά ο νέος κωδικός" htmlFor="fp-again" error={mismatch ? "Οι δύο κωδικοί δεν ταιριάζουν." : null}>
              <input id="fp-again" className="input" type="password" autoComplete="new-password" value={again} onChange={(e) => setAgain(e.target.value)} />
            </Field>
            <button type="submit" className="btn btn--primary btn--block" disabled={busy || !current || next.length < 10 || next !== again}>
              {busy ? <><Icon name="loader" size="sm" className="spin" />Αποθήκευση…</> : "Αποθήκευση και συνέχεια"}
            </button>
            <button type="button" className="btn btn--quiet btn--block" onClick={onSignOut}>Αποσύνδεση</button>
            <p className="hint">Συνδεδεμένος λογαριασμός: {user.email}</p>
          </form>
        </div>
      </div>
    );
  };

  // ---------- activity wording ----------
  const ACTION = {
    "auth.sign_in": "συνδέθηκε", "auth.dev_sign_in": "συνδέθηκε (τοπική ανάπτυξη)",
    "post.create": "ξεκίνησε νέο άρθρο", "post.save": "αποθήκευσε", "post.update": "ενημέρωσε το δημοσιευμένο",
    "post.publish": "δημοσίευσε", "post.schedule": "προγραμμάτισε", "post.reschedule": "άλλαξε την ημερομηνία του",
    "post.unpublish": "απέσυρε", "post.trash": "έστειλε στον κάδο", "post.restore": "επανέφερε",
    "post.duplicate": "δημιούργησε αντίγραφο", "post.delete": "διέγραψε οριστικά",
    "translation.save": "αποθήκευσε μετάφραση", "translation.ready": "ενέκρινε μετάφραση",
    "translation.delete": "διέγραψε μετάφραση", "translation.machine": "ζήτησε αυτόματη μετάφραση",
    "media.upload": "ανέβασε την εικόνα", "media.delete": "διέγραψε την εικόνα",
    "blog.enable": "έκανε το blog δημόσιο", "blog.disable": "έκρυψε το blog",
    "reviews.updated": "ανανέωσε τη βαθμολογία Google", "reviews.failed": "δεν μπόρεσε να ανανεώσει τη βαθμολογία Google",
    "writer.create": "δημιούργησε συντάκτη", "writer.update": "ενημέρωσε συντάκτη", "writer.delete": "διέγραψε συντάκτη",
    "account.seed": "ρύθμισε τον λογαριασμό", "account.seed_reset": "ανανέωσε τον κωδικό",
    "account.password": "άλλαξε κωδικό πρόσβασης", "account.name": "άλλαξε το όνομα του λογαριασμού",
    "account.seed_email": "άλλαξε το email του λογαριασμού", "writer.photo": "άλλαξε τη φωτογραφία του συντάκτη",
  };
  const detailText = (a) => {
    if (!a.detail) return "";
    if (a.action.startsWith("translation.")) {
      const langs = a.detail.split("→");
      return ` · ${langs.map((l) => PSA.LANG_NAME[l] || l).join(" → ")}`;
    }
    // A writer deletion records {name, to, count}: "«Α» → «Β» (2 άρθρα)" when posts moved to another writer.
    if (a.action === "writer.delete") {
      try {
        const d = JSON.parse(a.detail);
        return d.to && d.count > 0 ? ` «${d.name}» → «${d.to}» (${PSA.plural(d.count, "άρθρο", "άρθρα")})` : ` «${d.name}»`;
      } catch (e) { /* an older row holds the plain name */ }
    }
    return ` «${a.detail}»`;
  };
  // The dashboard's short list is about the writing: sign-ins and account changes stay in Settings, and a
  // run of the same action on the same post by the same person collapses to its latest entry.
  PSA.condenseActivity = (items) => items
    .filter((a) => !/^(auth|user|account)\./.test(a.action))
    .filter((a, i, list) => i === 0 || !(list[i - 1].action === a.action && list[i - 1].target === a.target));

  PSA.ActivityList = ({ items }) => (
    <ul className="activity">
      {items.map((a) => {
        const post = a.target && a.target.startsWith("post:") ? a.target.slice(5) : null;
        const text = <>{ACTION[a.action] || a.action}{detailText(a)}</>;
        return (
          <li key={a.id}>
            <span>{post && !a.action.endsWith(".delete") ? <Link to={`/posts/${post}`}>{text}</Link> : text}</span>
            <span className="when" title={fmtDate(a.at)}>{fmtRelative(a.at)}</span>
          </li>
        );
      })}
    </ul>
  );

  // ---------- post rows ----------
  const dateLine = (p) => {
    if (p.state === "published") return `Δημοσιεύτηκε ${fmtDay(p.publishedAt)}`;
    if (p.state === "scheduled") return `Δημοσιεύεται ${fmtDate(p.publishedAt)}`;
    if (p.state === "trash") return `Στον κάδο ${fmtRelative(p.deletedAt)}`;
    return `Αλλαγή ${fmtRelative(p.updatedAt)}`;
  };

  const LangTags = ({ post }) => (
    <span className="post-row__langs" aria-label="Γλώσσες">
      <span className="lang-tag is-source" title={`Πρωτότυπο: ${PSA.LANG_NAME[post.lang]}`}>{post.lang}</span>
      {(post.translations || []).map((t) => (
        <span key={t.lang} className={`lang-tag${t.status === "ready" ? " is-ready" : ""}`}
              title={`${PSA.LANG_NAME[t.lang]}: ${t.status === "ready" ? "εγκεκριμένη μετάφραση" : "μετάφραση σε εξέλιξη"}`}>{t.lang}</span>
      ))}
    </span>
  );

  const PostRow = ({ post, actions }) => (
    <li className="post-row">
      <span className="post-row__thumb" aria-hidden="true">
        {post.coverUrl ? <img src={post.coverUrl.replace(/_(\d+)x(\d+)\./, (m, w, h) => (+w > 800 ? `_800x${Math.round((h * 800) / w)}.` : m))} alt="" loading="lazy" draggable="false" /> : <Icon name="posts" />}
      </span>
      <span style={{ minWidth: 0 }}>
        <Link to={`/posts/${post.id}`} className={`post-row__title${post.title.trim() ? "" : " is-empty"}`}>{post.title.trim() || "Χωρίς τίτλο"}</Link>
        <span className="post-row__meta">
          <StateBadge state={post.state} />
          <span className="post-row__date">{dateLine(post)}</span>
        </span>
        <span className="post-row__meta">
          {post.writer
            ? <span className="post-row__writer"><PSA.WriterAvatar writer={post.writer} size={20} /><span>{post.writer.name}</span></span>
            : <span className="post-row__writer is-missing">Χωρίς συντάκτη</span>}
          <LangTags post={post} />
        </span>
      </span>
      {actions && <span className="post-row__actions">{actions}</span>}
    </li>
  );

  PSA.newPost = async () => {
    try {
      const { post } = await api("/api/posts", { method: "POST", body: {} });
      PSA.navigate(`/posts/${post.id}`);
    } catch (err) {
      toast(errorText(err), { kind: "error" });
    }
  };

  // ---------- dashboard ----------
  const BlogSwitch = ({ counts, onChanged }) => {
    const session = PSA.useSession();
    const on = session.settings.blogEnabled;
    const [busy, setBusy] = useState(false);
    const flip = async (next) => {
      const ok = await confirm(next ? {
        title: "Να γίνει δημόσιο το blog;",
        confirmLabel: "Ναι, δημόσιο",
        body: (
          <div style={{ display: "grid", gap: 10 }}>
            <p>Μέσα σε λίγα δευτερόλεπτα:</p>
            <ul style={{ margin: 0, paddingLeft: 20 }}>
              <li>το «Άρθρα» μπαίνει στο μενού του physiosamos.gr,</li>
              <li>η σελίδα /blog ανοίγει για όλους και μπαίνει στον χάρτη του ιστότοπου για τις μηχανές αναζήτησης,</li>
              <li>τα δημοσιευμένα άρθρα γίνονται ορατά (τα μεμονωμένα άρθρα δεν εμφανίζονται στην αναζήτηση Google).</li>
            </ul>
            {counts && counts.published === 0 && (
              <div className="notice notice--warn"><Icon name="warning" size="sm" /><div>Δεν υπάρχει ακόμη δημοσιευμένο άρθρο. Η σελίδα θα γράφει ότι δεν υπάρχουν άρθρα.</div></div>
            )}
          </div>
        ),
      } : {
        title: "Να κρυφτεί το blog;",
        confirmLabel: "Ναι, κρυφό",
        danger: true,
        body: "Το «Άρθρα» φεύγει από το μενού και το /blog με όλα τα άρθρα σταματά να ανοίγει. Τίποτα δεν διαγράφεται και μπορείτε να συνεχίσετε να γράφετε.",
      });
      if (!ok) return;
      setBusy(true);
      try {
        const settings = await api("/api/settings/blog", { method: "PUT", body: { enabled: next } });
        session.setSettings(settings);
        toast(next ? "Το blog είναι πλέον δημόσιο." : "Το blog κρύφτηκε από τον ιστότοπο.");
        if (onChanged) onChanged();
      } catch (err) {
        toast(errorText(err), { kind: "error" });
      } finally {
        setBusy(false);
      }
    };
    return (
      <section className={`sheet blog-state${on ? " is-on" : ""}`} aria-labelledby="blog-state-title">
        <span className="blog-state__mark" aria-hidden="true"><Icon name={on ? "globe" : "lock"} size="lg" /></span>
        <div>
          <h2 id="blog-state-title">{on ? "Το blog είναι δημόσιο" : "Το blog είναι κρυφό"}</h2>
          <p>
            {on
              ? <>Εμφανίζεται στο μενού του ιστότοπου και στο <a href={`${session.publicOrigin}/blog`} target="_blank" rel="noopener">/blog</a>.</>
              : "Κανένας επισκέπτης δεν το βλέπει. Γράψτε, προγραμματίστε και ελέγξτε με την ησυχία σας."}
            {session.settings.blogChangedAt ? ` Τελευταία αλλαγή ${fmtRelative(session.settings.blogChangedAt)}.` : ""}
          </p>
        </div>
        <div className="blog-state__side">
          <PSA.Switch checked={on} disabled={busy} onChange={flip} label={on ? "Δημόσιο" : "Κρυφό"} />
        </div>
      </section>
    );
  };

  PSA.Dashboard = () => {
    const session = PSA.useSession();
    const dash = PSA.useAsync(() => api("/api/dashboard"), []);
    const hour = new Date().getHours();
    const greeting = hour < 12 ? "Καλημέρα" : "Καλησπέρα";
    const first = session.user.name.split(/\s+/)[0];
    const d = dash.data;
    const c = d && d.counts;
    return (
      <div className="page">
        <div className="page-head">
          <div>
            <h1>{greeting}, {first}</h1>
            <p>{c ? [
              PSA.plural(c.published, "δημοσιευμένο", "δημοσιευμένα"),
              PSA.plural(c.scheduled, "προγραμματισμένο", "προγραμματισμένα"),
              PSA.plural(c.drafts, "πρόχειρο", "πρόχειρα"),
            ].join(" · ") : " "}</p>
          </div>
          <div className="page-head__actions">
            <button type="button" className="btn btn--primary" onClick={PSA.newPost}><Icon name="plus" size="sm" />Νέο άρθρο</button>
          </div>
        </div>

        <BlogSwitch counts={c} onChanged={dash.reload} />

        {c && c.writers === 0 && (
          <div className="notice notice--info" style={{ marginTop: 16 }}>
            <Icon name="info" size="sm" />
            <div style={{ flex: 1 }}>Για να δημοσιεύσετε, προσθέστε πρώτα έναν συντάκτη με όνομα και φωτογραφία.</div>
            <Link to="/writers" className="btn btn--sm">Συντάκτες</Link>
          </div>
        )}

        {dash.error && !d && <div style={{ marginTop: 20 }}><PSA.ErrorNotice error={dash.error} onRetry={dash.reload} /></div>}

        <div className="dash-grid">
          <section className="section" style={{ marginTop: 0 }} aria-labelledby="recent-title">
            <h2 id="recent-title">Συνεχίστε από εκεί που μείνατε</h2>
            {d && d.nextScheduled && (
              <div className="notice notice--info" style={{ marginBottom: 12 }}>
                <Icon name="clock" size="sm" />
                <div>Επόμενη δημοσίευση: <Link to={`/posts/${d.nextScheduled.id}`}>{d.nextScheduled.title}</Link>, {fmtDate(d.nextScheduled.publishedAt)}.</div>
              </div>
            )}
            <div className="sheet sheet--rows">
              {!d && dash.loading && <div style={{ padding: 14, display: "grid", gap: 14 }}>{[0, 1, 2].map((i) => <div key={i} className="skeleton" style={{ height: 48 }} />)}</div>}
              {d && d.recent.length > 0 && <ul className="post-rows">{d.recent.map((p) => <PostRow key={p.id} post={p} />)}</ul>}
              {d && d.recent.length === 0 && (
                <div className="empty">
                  <Icon name="posts" size="lg" />
                  <h3>Εδώ θα εμφανίζονται τα άρθρα σας</h3>
                  <p>Γράψτε το πρώτο. Μένει πρόχειρο μέχρι να το δημοσιεύσετε.</p>
                  <button type="button" className="btn btn--primary" onClick={PSA.newPost}><Icon name="plus" size="sm" />Νέο άρθρο</button>
                </div>
              )}
            </div>
            {d && d.recent.length > 0 && <p style={{ marginTop: 10 }}><Link to="/posts">Όλα τα άρθρα</Link></p>}
          </section>
          <section className="section" style={{ marginTop: 0 }} aria-labelledby="activity-title">
            <h2 id="activity-title">Πρόσφατη δραστηριότητα</h2>
            <div className="sheet sheet__pad" style={{ paddingTop: 8, paddingBottom: 8 }}>
              {d ? (PSA.condenseActivity(d.activity).length
                ? <PSA.ActivityList items={PSA.condenseActivity(d.activity).slice(0, 6)} />
                : <p className="muted" style={{ padding: "10px 0" }}>Τίποτα ακόμη.</p>) : <PSA.Spinner />}
            </div>
            {d && <Link to="/settings" className="activity-more">Όλη η δραστηριότητα στις Ρυθμίσεις</Link>}
          </section>
        </div>
      </div>
    );
  };

  // ---------- posts list ----------
  const TABS = [
    { value: "all", label: "Όλα" },
    { value: "draft", label: "Πρόχειρα" },
    { value: "scheduled", label: "Προγραμματισμένα" },
    { value: "published", label: "Δημοσιευμένα" },
    { value: "trash", label: "Κάδος" },
  ];
  const EMPTY = {
    all: ["Δεν υπάρχουν άρθρα ακόμη", "Ξεκινήστε ένα νέο άρθρο. Μένει πρόχειρο μέχρι να το δημοσιεύσετε."],
    draft: ["Κανένα πρόχειρο", "Κάθε νέο άρθρο ξεκινά από εδώ."],
    scheduled: ["Τίποτα προγραμματισμένο", "Στο άρθρο, διαλέξτε ημερομηνία και ώρα δημοσίευσης."],
    published: ["Κανένα δημοσιευμένο άρθρο", "Όταν δημοσιεύσετε ένα άρθρο, θα εμφανίζεται εδώ."],
    trash: ["Ο κάδος είναι άδειος", "Τα άρθρα που διαγράφετε μένουν εδώ μέχρι να τα επαναφέρετε ή να σβηστούν οριστικά."],
  };

  PSA.PostsPage = () => {
    const session = PSA.useSession();
    const path = PSA.usePath();
    const params = new URLSearchParams(path.split("?")[1] || "");
    const state = TABS.some((t) => t.value === params.get("state")) ? params.get("state") : "all";
    const [q, setQ] = useState(params.get("q") || "");
    const [from, setFrom] = useState(null), [to, setTo] = useState(null);
    const writerId = params.get("writer") || "";
    const writers = PSA.useWriters();
    const [query, setQuery] = useState(q);
    useEffect(() => { const t = setTimeout(() => setQuery(q.trim()), 250); return () => clearTimeout(t); }, [q]);
    const list = PSA.useAsync((signal) => api(`/api/posts?state=${state}&q=${encodeURIComponent(query)}&writer=${encodeURIComponent(writerId)}`, { signal }), [state, query, writerId]);
    const setFilter = (key, value) => { const next = new URLSearchParams(location.search); if (value) next.set(key, value); else next.delete(key); PSA.navigate(`/posts${next.size ? `?${next}` : ""}`, { replace: true }); };
    const setTab = (value) => setFilter("state", value === "all" ? "" : value);

    const act = async (fn, okText, undo) => {
      try {
        await fn();
        list.reload();
        toast(okText, undo ? { action: { label: "Αναίρεση", onClick: async () => { try { await undo(); list.reload(); } catch (err) { toast(errorText(err), { kind: "error" }); } } } } : {});
      } catch (err) {
        toast(errorText(err), { kind: "error" });
      }
    };
    const trash = async (p) => {
      if (p.state === "published" || p.state === "scheduled") {
        const ok = await confirm({
          title: "Μεταφορά στον κάδο;",
          danger: true,
          confirmLabel: "Στον κάδο",
          body: p.state === "published"
            ? "Το άρθρο θα αφαιρεθεί αμέσως από τον ιστότοπο. Αν το επαναφέρετε, θα γυρίσει ως πρόχειρο."
            : "Η προγραμματισμένη δημοσίευση ακυρώνεται. Αν το επαναφέρετε, θα γυρίσει ως πρόχειρο.",
        });
        if (!ok) return;
        return act(() => api(`/api/posts/${p.id}/trash`, { method: "POST", body: {} }), "Το άρθρο μεταφέρθηκε στον κάδο.");
      }
      return act(() => api(`/api/posts/${p.id}/trash`, { method: "POST", body: {} }), "Το άρθρο μεταφέρθηκε στον κάδο.",
        () => api(`/api/posts/${p.id}/restore`, { method: "POST", body: {} }));
    };
    const destroy = async (p) => {
      const ok = await confirm({ title: "Οριστική διαγραφή;", danger: true, confirmLabel: "Διαγραφή για πάντα",
        body: `Το «${p.title || "Χωρίς τίτλο"}» θα σβηστεί μαζί με τις μεταφράσεις και το ιστορικό του. Δεν αναιρείται.` });
      if (ok) act(() => api(`/api/posts/${p.id}`, { method: "DELETE" }), "Το άρθρο διαγράφηκε οριστικά.");
    };
    const duplicate = (p) => act(async () => {
      const { post } = await api(`/api/posts/${p.id}/duplicate`, { method: "POST", body: { titleSuffix: " (αντίγραφο)" } });
      PSA.navigate(`/posts/${post.id}`);
    }, "Δημιουργήθηκε αντίγραφο ως πρόχειρο.");

    const counts = list.data ? list.data.counts : null;
    const posts = (list.data ? list.data.posts : []).filter((p) => { const day = PSA.athens.dateKey(p.updatedAt); return (!from || day >= from) && (!to || day <= to); });
    return (
      <div className="page">
        <div className="page-head">
          <div>
            <h1>Άρθρα</h1>
            <p>Κάθε κείμενο του blog, από το πρώτο πρόχειρο μέχρι τα δημοσιευμένα.</p>
          </div>
          <div className="page-head__actions">
            <button type="button" className="btn btn--primary" onClick={PSA.newPost}><Icon name="plus" size="sm" />Νέο άρθρο</button>
          </div>
        </div>
        <div className="toolbar-row">
          <PSA.Segmented label="Κατάσταση" value={state} onChange={setTab}
                         options={TABS.map((t) => ({ ...t, count: counts ? counts[t.value] : undefined }))} />
          <div className="search">
            <Icon name="search" size="sm" />
            <input className="input" type="search" placeholder="Αναζήτηση στους τίτλους" aria-label="Αναζήτηση άρθρων" value={q} onChange={(e) => setQ(e.target.value)} />
          </div>
        </div>
        <div className="toolbar-row" style={{ alignItems: "end" }}>
          <Field label="Συντάκτης" htmlFor="posts-writer"><PSA.Select id="posts-writer" ariaLabel="Φίλτρο συντάκτη" value={writerId} onChange={(v) => setFilter("writer", v)}
            options={[{ value: "", label: "Όλοι οι συντάκτες" }, ...((writers.data && writers.data.writers) || []).map((w) => ({ value: String(w.id), label: w.name, avatarUrl: w.photoSmallUrl }))]} /></Field>
          <Field label="Διάστημα αλλαγών" htmlFor="posts-range"><PSA.DateRangePicker id="posts-range" from={from} to={to} onChange={({ from, to }) => { setFrom(from); setTo(to); }} /></Field>
        </div>
        <div className="sheet sheet--rows" aria-busy={list.loading}>
          {list.error && !list.data && <div style={{ padding: 12 }}><PSA.ErrorNotice error={list.error} onRetry={list.reload} /></div>}
          {!list.data && list.loading && <div style={{ padding: 14, display: "grid", gap: 16 }}>{[0, 1, 2, 3].map((i) => <div key={i} className="skeleton" style={{ height: 48 }} />)}</div>}
          {list.data && posts.length === 0 && (
            <div className="empty">
              <Icon name={state === "trash" ? "trash" : "posts"} size="lg" />
              <h3>{query ? "Κανένα άρθρο δεν ταιριάζει" : EMPTY[state][0]}</h3>
              <p>{query ? "Δοκιμάστε άλλη λέξη ή άλλη καρτέλα." : EMPTY[state][1]}</p>
              {!query && state !== "trash" && <button type="button" className="btn btn--primary" onClick={PSA.newPost}><Icon name="plus" size="sm" />Νέο άρθρο</button>}
            </div>
          )}
          {posts.length > 0 && (
            <ul className="post-rows">
              {posts.map((p) => (
                <PostRow key={p.id} post={p} actions={p.state === "trash" ? <>
                  <button type="button" className="btn btn--sm" onClick={() => act(() => api(`/api/posts/${p.id}/restore`, { method: "POST", body: {} }), "Το άρθρο επανήλθε ως πρόχειρο.")}>
                    <Icon name="restore" size="sm" />Επαναφορά
                  </button>
                  {<button type="button" className="btn btn--danger btn--sm" onClick={() => destroy(p)}>Οριστική διαγραφή</button>}
                </> : <>
                  {/* Words, not icon riddles: these are the same actions the editor labels in full. */}
                  {p.state === "published" && session.settings.blogEnabled && (
                    <a className="btn btn--quiet btn--sm" href={`${session.publicOrigin}/blog/${p.slug}`} target="_blank" rel="noopener">
                      <Icon name="external" size="sm" />Στον ιστότοπο
                    </a>
                  )}
                  <a className="btn btn--quiet btn--sm" href={`/preview/${p.id}`} target="_blank" rel="noopener"><Icon name="eye" size="sm" />Προεπισκόπηση</a>
                  <button type="button" className="btn btn--quiet btn--sm" onClick={() => duplicate(p)}><Icon name="copy" size="sm" />Αντίγραφο</button>
                  <button type="button" className="btn btn--quiet btn--sm" onClick={() => trash(p)}><Icon name="trash" size="sm" />Στον κάδο</button>
                </>} />
              ))}
            </ul>
          )}
        </div>
      </div>
    );
  };
})();
