Ajoute l'historique des versions des questions soumises par les Citoyens
Build and deploy / deploy (push) Successful in 35s

Nouvelle table chariot_submission_history (append-only, comme
points_log) : chariot_submissions ne garde que la valeur actuelle
(upsert), donc sans ce journal les Archontes ne voyaient jamais les
versions précédentes d'une question modifiée plusieurs fois.
submit_chariot_question() y écrit désormais à chaque changement réel.

Sur /char/questions, chaque suggestion affiche un lien "Historique (N)"
(visible seulement si la personne a déjà modifié sa proposition) qui
déplie les versions précédentes avec leur date.
This commit is contained in:
Valentin ROBIN
2026-08-22 00:16:42 +02:00
parent 6badff38af
commit ec6b1920af
4 changed files with 110 additions and 11 deletions
+14
View File
@@ -17,6 +17,12 @@ export type SubmissionRow = {
profiles: { pseudo: string; avatar_url: string | null } | null;
};
export type SubmissionHistoryRow = {
user_id: string;
text: string;
created_at: string;
};
export default async function CharQuestionsPage() {
const supabase = await createClient();
const {
@@ -58,6 +64,13 @@ export default async function CharQuestionsPage() {
.order("updated_at", { ascending: true })
.returns<SubmissionRow[]>();
// Versions précédentes des propositions (chariot_submissions ne garde que
// la version actuelle) — pour la section "Historique" de la modération.
const { data: submissionHistory } = await supabase
.from("chariot_submission_history")
.select("user_id, text, created_at")
.order("created_at", { ascending: false });
return (
<div className="mx-auto w-full max-w-2xl px-4 py-8">
<div className="mb-6 text-center">
@@ -76,6 +89,7 @@ export default async function CharQuestionsPage() {
initialQuestions={questions ?? []}
initialRevealedId={settings?.chariot_revealed_question_id ?? null}
initialSubmissions={submissions ?? []}
initialSubmissionHistory={submissionHistory ?? []}
/>
</div>
);
+60 -10
View File
@@ -4,7 +4,15 @@ import { useCallback, useEffect, useState } from "react";
import { Avatar } from "@/components/avatar";
import { IconChevronDown, IconPencil, IconPlus, IconTrash } from "@/components/icons";
import { createClient, waitForRealtimeAuth } from "@/lib/supabase/client";
import type { QuestionRow, SubmissionRow } from "./page";
import type { QuestionRow, SubmissionHistoryRow, SubmissionRow } from "./page";
function formatHistoryDate(iso: string): string {
return new Date(iso).toLocaleString("fr-FR", {
dateStyle: "short",
timeStyle: "short",
timeZone: "Europe/Paris",
});
}
function QuestionForm({
initial,
@@ -187,14 +195,17 @@ function QuestionRowItem({
function SubmissionRowItem({
submission,
pastVersions,
onAddToBank,
onReject,
}: {
submission: SubmissionRow;
pastVersions: SubmissionHistoryRow[];
onAddToBank: () => void;
onReject: () => void;
}) {
const [confirmingReject, setConfirmingReject] = useState(false);
const [showHistory, setShowHistory] = useState(false);
const profile = submission.profiles;
const pseudo = profile?.pseudo ?? "un Citoyen";
@@ -204,6 +215,28 @@ function SubmissionRowItem({
<div className="min-w-0 flex-1">
<p className="text-xs font-medium text-text-mut">{pseudo}</p>
<p className="text-text-marble">{submission.text}</p>
{pastVersions.length > 0 && (
<>
<button
type="button"
onClick={() => setShowHistory((v) => !v)}
className="mt-1 text-[0.7rem] font-medium text-text-mut underline hover:text-text-marble"
>
{showHistory ? "Masquer l'historique" : `Historique (${pastVersions.length})`}
</button>
{showHistory && (
<ul className="mt-1 flex flex-col gap-1 border-l-2 border-gold/20 pl-2">
{pastVersions.map((version) => (
<li key={version.created_at} className="text-xs text-text-mut">
<span className="text-text-marble">{version.text}</span>
{" — "}
{formatHistoryDate(version.created_at)}
</li>
))}
</ul>
)}
</>
)}
</div>
<div className="flex shrink-0 items-center gap-1">
<button
@@ -242,19 +275,22 @@ export function QuestionsView({
initialQuestions,
initialRevealedId,
initialSubmissions,
initialSubmissionHistory,
}: {
initialQuestions: QuestionRow[];
initialRevealedId: string | null;
initialSubmissions: SubmissionRow[];
initialSubmissionHistory: SubmissionHistoryRow[];
}) {
const [questions, setQuestions] = useState(initialQuestions);
const [revealedId, setRevealedId] = useState(initialRevealedId);
const [submissions, setSubmissions] = useState(initialSubmissions);
const [submissionHistory, setSubmissionHistory] = useState(initialSubmissionHistory);
const [adding, setAdding] = useState(false);
const refetch = useCallback(async () => {
const supabase = createClient();
const [{ data: q }, { data: s }, { data: sub }] = await Promise.all([
const [{ data: q }, { data: s }, { data: sub }, { data: hist }] = await Promise.all([
supabase
.from("chariot_questions")
.select("id, text, position, created_at")
@@ -266,10 +302,15 @@ export function QuestionsView({
.select("user_id, text, updated_at, profiles(pseudo, avatar_url)")
.order("updated_at", { ascending: true })
.returns<SubmissionRow[]>(),
supabase
.from("chariot_submission_history")
.select("user_id, text, created_at")
.order("created_at", { ascending: false }),
]);
if (q) setQuestions(q);
if (s) setRevealedId(s.chariot_revealed_question_id);
if (sub) setSubmissions(sub);
if (hist) setSubmissionHistory(hist);
}, []);
useEffect(() => {
@@ -284,6 +325,7 @@ export function QuestionsView({
.on("postgres_changes", { event: "*", schema: "public", table: "chariot_questions" }, refetch)
.on("postgres_changes", { event: "*", schema: "public", table: "settings" }, refetch)
.on("postgres_changes", { event: "*", schema: "public", table: "chariot_submissions" }, refetch)
.on("postgres_changes", { event: "*", schema: "public", table: "chariot_submission_history" }, refetch)
.subscribe();
});
@@ -336,14 +378,22 @@ export function QuestionsView({
Suggestions des Citoyens
</p>
<ul className="flex flex-col divide-y divide-gold/10">
{submissions.map((submission) => (
<SubmissionRowItem
key={submission.user_id}
submission={submission}
onAddToBank={() => addSubmissionToBank(submission)}
onReject={() => rejectSubmission(submission.user_id)}
/>
))}
{submissions.map((submission) => {
// Trié desc : la première entrée est la version actuelle
// (déjà affichée au-dessus), le reste est l'historique.
const pastVersions = submissionHistory
.filter((h) => h.user_id === submission.user_id)
.slice(1);
return (
<SubmissionRowItem
key={submission.user_id}
submission={submission}
pastVersions={pastVersions}
onAddToBank={() => addSubmissionToBank(submission)}
onReject={() => rejectSubmission(submission.user_id)}
/>
);
})}
</ul>
</div>
)}