Checkpoint: Boîte à idées : table BDD, API tRPC (créer, lister, répondre, changer statut), page avec liste filtrée par statut et recherche, bouton Nouvelle demande, réponse admin avec statut colorisé, menu dans la sidebar

This commit is contained in:
Manus
2026-04-17 10:57:07 -04:00
parent b36a049ca5
commit 535dd19188
11 changed files with 1311 additions and 0 deletions

View File

@@ -9,6 +9,8 @@ import {
appSettings,
importLogs,
InsertLocalUser,
ideas,
InsertIdea,
} from "../drizzle/schema";
import { ENV } from "./_core/env";
@@ -315,3 +317,45 @@ export async function getImportStats() {
totalNewRows,
};
}
// ─── Boîte à idées ────────────────────────────────────────────────────────────
export async function createIdea(data: InsertIdea) {
const db = await getDb();
if (!db) throw new Error("Database not available");
await db.insert(ideas).values(data);
}
export async function getAllIdeas() {
const db = await getDb();
if (!db) return [];
return db.select().from(ideas).orderBy(desc(ideas.createdAt));
}
export async function getIdeasByUser(userId: number) {
const db = await getDb();
if (!db) return [];
return db.select().from(ideas).where(eq(ideas.userId, userId)).orderBy(desc(ideas.createdAt));
}
export async function repondreIdea(
id: number,
reponseAdmin: string,
reponduPar: string,
statut: "ouvert" | "en_cours" | "resolu" | "ferme"
) {
const db = await getDb();
if (!db) throw new Error("Database not available");
await db.update(ideas).set({
reponseAdmin,
reponduPar,
reponduAt: new Date(),
statut,
}).where(eq(ideas.id, id));
}
export async function updateIdeaStatut(id: number, statut: "ouvert" | "en_cours" | "resolu" | "ferme") {
const db = await getDb();
if (!db) throw new Error("Database not available");
await db.update(ideas).set({ statut }).where(eq(ideas.id, id));
}

View File

@@ -17,6 +17,11 @@ import {
createLocalUser,
updateLocalUser,
deleteLocalUser,
createIdea,
getAllIdeas,
getIdeasByUser,
repondreIdea,
updateIdeaStatut,
} from "./db";
import { importVeille, importAAP, runFullImport, getImportConfig } from "./importer";
import { loginLocalUser, hashPassword, ensureAdminExists } from "./localAuth";
@@ -238,6 +243,67 @@ export const appRouter = router({
return { success: true };
}),
}),
// ─── Boîte à idées ───────────────────────────────────────────────────────────
ideas: router({
// Créer une nouvelle idée / question
create: protectedProcedure
.input(
z.object({
titre: z.string().min(3).max(512),
message: z.string().min(10),
})
)
.mutation(async ({ input, ctx }) => {
await createIdea({
userId: ctx.user.id,
userName: ctx.user.name ?? ctx.user.email ?? "Utilisateur",
titre: input.titre,
message: input.message,
});
return { success: true };
}),
// Lister toutes les idées (admin) ou les siennes (user)
list: protectedProcedure.query(async ({ ctx }) => {
if (ctx.user.role === "admin") {
return getAllIdeas();
}
return getIdeasByUser(ctx.user.id);
}),
// Répondre à une idée (admin uniquement)
repondre: adminProcedure
.input(
z.object({
id: z.number().int().positive(),
reponseAdmin: z.string().min(1),
statut: z.enum(["ouvert", "en_cours", "resolu", "ferme"]),
})
)
.mutation(async ({ input, ctx }) => {
await repondreIdea(
input.id,
input.reponseAdmin,
ctx.user.name ?? ctx.user.email ?? "Admin",
input.statut
);
return { success: true };
}),
// Changer le statut (admin uniquement)
updateStatut: adminProcedure
.input(
z.object({
id: z.number().int().positive(),
statut: z.enum(["ouvert", "en_cours", "resolu", "ferme"]),
})
)
.mutation(async ({ input }) => {
await updateIdeaStatut(input.id, input.statut);
return { success: true };
}),
}),
});
export type AppRouter = typeof appRouter;