Merge pull request #191 from R0m1k3/1.1

Update announcement system to use production database and improve car…
This commit is contained in:
LogiFlow authored and GitHub committed 2025-08-17 11:08:09 +02:00
commit 2a52ccd2be
3 files changed
+138 -44

No files matched your search

+25 -27
View File
@@ -387,33 +387,31 @@ export default function AnnouncementCard() {
return (
<div key={announcement.id} className="flex items-start justify-between p-3 rounded-lg border bg-card hover:bg-accent/50 transition-colors">
<div className="flex items-start gap-3 flex-1 min-w-0">
<TypeIcon className="h-4 w-4 text-blue-600 mt-0.5 flex-shrink-0" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-medium text-sm truncate">
{announcement.title}
</h4>
<Badge variant={priorityConfig.color} className="flex items-center gap-1 text-xs">
{priorityConfig.label}
</Badge>
</div>
{announcement.content && (
<p className="text-xs text-muted-foreground mb-1 line-clamp-1">
{announcement.content}
</p>
)}
<div className="flex items-center gap-3 text-xs text-muted-foreground">
<span>
{safeFormat(safeDate(announcement.createdAt), 'dd/MM à HH:mm')}
</span>
<span>
{announcement.author.firstName} {announcement.author.lastName}
{announcement.group && ` • ${announcement.group.name}`}
</span>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-medium text-sm truncate">
{announcement.title}
</h4>
<Badge variant={priorityConfig.color} className="flex items-center gap-1 text-xs">
<TypeIcon className="w-2.5 h-2.5" />
{priorityConfig.label}
</Badge>
</div>
{announcement.content && (
<p className="text-xs text-muted-foreground mb-1 line-clamp-1">
{announcement.content}
</p>
)}
<div className="flex items-center gap-3 text-xs text-muted-foreground">
<span>
{safeFormat(safeDate(announcement.createdAt), 'dd/MM à HH:mm')}
</span>
<span>
{announcement.author.firstName} {announcement.author.lastName}
{announcement.group && ` • ${announcement.group.name}`}
</span>
</div>
</div>
{user?.role === 'admin' && (
+109 -12
View File
@@ -1,7 +1,90 @@
import { Announcement, InsertAnnouncement, AnnouncementWithRelations, User, Group } from "@shared/schema";
import { DashboardMessage, InsertDashboardMessage, DashboardMessageWithRelations, User, Group } from "@shared/schema";
import { db } from "./db";
import { dashboardMessages, users, groups } from "@shared/schema";
import { eq, desc } from "drizzle-orm";
// Stockage en mémoire partagé pour les annonces (maximum 5)
class AnnouncementMemoryStorage {
// Type aliases pour compatibilité
type Announcement = DashboardMessage;
type InsertAnnouncement = InsertDashboardMessage;
type AnnouncementWithRelations = DashboardMessageWithRelations;
// Interface commune pour le stockage
interface IAnnouncementStorage {
getAnnouncements(groupIds?: number[]): Promise<AnnouncementWithRelations[]>;
createAnnouncement(announcement: InsertAnnouncement): Promise<DashboardMessage>;
updateAnnouncement(id: number, announcement: Partial<InsertAnnouncement>): Promise<DashboardMessage>;
deleteAnnouncement(id: number): Promise<void>;
}
// Stockage PostgreSQL pour la production
class AnnouncementDatabaseStorage implements IAnnouncementStorage {
async getAnnouncements(groupIds?: number[]): Promise<AnnouncementWithRelations[]> {
let query = db
.select({
id: dashboardMessages.id,
title: dashboardMessages.title,
content: dashboardMessages.content,
type: dashboardMessages.type,
storeId: dashboardMessages.storeId,
createdBy: dashboardMessages.createdBy,
createdAt: dashboardMessages.createdAt,
author: {
id: users.id,
firstName: users.firstName,
lastName: users.lastName,
username: users.username,
},
group: {
id: groups.id,
name: groups.name,
},
})
.from(dashboardMessages)
.leftJoin(users, eq(dashboardMessages.createdBy, users.id))
.leftJoin(groups, eq(dashboardMessages.storeId, groups.id))
.orderBy(desc(dashboardMessages.createdAt));
const results = await query;
return results.map(result => ({
id: result.id,
title: result.title,
content: result.content,
type: result.type,
storeId: result.storeId,
createdBy: result.createdBy,
createdAt: result.createdAt,
author: result.author as User,
group: result.group as Group | undefined,
}));
}
async createAnnouncement(announcement: InsertAnnouncement): Promise<DashboardMessage> {
const [created] = await db
.insert(dashboardMessages)
.values(announcement)
.returning();
return created;
}
async updateAnnouncement(id: number, announcement: Partial<InsertAnnouncement>): Promise<DashboardMessage> {
const [updated] = await db
.update(dashboardMessages)
.set(announcement)
.where(eq(dashboardMessages.id, id))
.returning();
return updated;
}
async deleteAnnouncement(id: number): Promise<void> {
await db
.delete(dashboardMessages)
.where(eq(dashboardMessages.id, id));
}
}
// Stockage en mémoire pour le développement
class AnnouncementMemoryStorage implements IAnnouncementStorage {
private announcements = new Map<number, Announcement>();
private idCounter = 1;
private readonly MAX_ANNOUNCEMENTS = 5;
@@ -178,16 +261,30 @@ class AnnouncementMemoryStorage {
}
}
// Instance globale partagée
let globalAnnouncementStorage: AnnouncementMemoryStorage | null = null;
// Instances globales pour les différents environnements
let memoryInstance: AnnouncementMemoryStorage | null = null;
let databaseInstance: AnnouncementDatabaseStorage | null = null;
export function getAnnouncementStorage(
usersGetter: () => Promise<User[]>,
groupsGetter: () => Promise<Group[]>
): AnnouncementMemoryStorage {
if (!globalAnnouncementStorage) {
globalAnnouncementStorage = new AnnouncementMemoryStorage(usersGetter, groupsGetter);
console.log('📢 Stockage mémoire des annonces initialisé');
usersGetter?: () => Promise<User[]>,
groupsGetter?: () => Promise<Group[]>
): IAnnouncementStorage {
const isProduction = process.env.NODE_ENV === 'production';
if (isProduction) {
if (!databaseInstance) {
databaseInstance = new AnnouncementDatabaseStorage();
console.log('📢 Stockage PostgreSQL des annonces initialisé pour la production');
}
return databaseInstance;
} else {
if (!memoryInstance) {
if (!usersGetter || !groupsGetter) {
throw new Error("usersGetter and groupsGetter are required for memory storage initialization");
}
memoryInstance = new AnnouncementMemoryStorage(usersGetter, groupsGetter);
console.log('📢 Stockage mémoire des annonces initialisé pour le développement');
}
return memoryInstance;
}
return globalAnnouncementStorage;
}
+4 -5
View File
@@ -70,6 +70,8 @@ export const userGroups = pgTable("user_groups", {
createdAt: timestamp("created_at").defaultNow(),
});
// Suppliers - Schema exact correspondant à la production
export const suppliers = pgTable("suppliers", {
id: serial("id").primaryKey(),
@@ -530,15 +532,12 @@ export const insertTaskSchema = createInsertSchema(tasks).omit({
dueDate: z.coerce.date().optional().nullable(), // Convertit automatiquement les chaînes en Date
});
export const insertAnnouncementSchema = createInsertSchema(dashboardMessages).omit({
export const insertDashboardMessageSchema = createInsertSchema(dashboardMessages).omit({
id: true,
createdAt: true,
}).extend({
type: z.enum(["info", "warning", "error", "success"]).default("info"),
});
// Mapping pour la compatibilité avec l'interface actuelle
export const insertDashboardMessageSchema = insertAnnouncementSchema;
export const insertAnnouncementSchema = insertDashboardMessageSchema;
export const insertNocodbConfigSchema = createInsertSchema(nocodbConfig).omit({
id: true,