mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Introduce an information system for important announcements
Implement a new announcement system with priority levels, role-based access, and store-specific filtering, replacing the customer order statistics card on the dashboard. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e437ffed-c31b-44eb-9fd5-b6f9faa8e7ce Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/e437ffed-c31b-44eb-9fd5-b6f9faa8e7ce/biGr9ER
This commit is contained in:
1 parent
5688450117
commit
30cfc4b3de
9 files changed
+729
-47
No files matched your search
@@ -34,6 +34,7 @@ import {
|
||||
insertDlcProductSchema,
|
||||
insertDlcProductFrontendSchema,
|
||||
insertTaskSchema,
|
||||
insertAnnouncementSchema,
|
||||
insertNocodbConfigSchema,
|
||||
insertSavTicketSchema,
|
||||
insertSavTicketHistorySchema,
|
||||
@@ -2996,6 +2997,93 @@ RÉSUMÉ DU SCAN
|
||||
});
|
||||
|
||||
// Route de géolocalisation météo
|
||||
// Announcement routes
|
||||
app.get('/api/announcements', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
|
||||
const user = await storage.getUserWithGroups(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
|
||||
// Get user's group IDs for filtering
|
||||
let groupIds: number[] = [];
|
||||
if (user.role === 'admin') {
|
||||
// Admin can see all announcements, but can filter by selected store
|
||||
const storeId = req.query.storeId;
|
||||
if (storeId) {
|
||||
groupIds = [parseInt(storeId)];
|
||||
}
|
||||
} else {
|
||||
// Other roles see only their assigned groups + global announcements
|
||||
const userGroups = (user as any).userGroups;
|
||||
groupIds = userGroups ? userGroups.map((ug: any) => ug.groupId) : [];
|
||||
}
|
||||
|
||||
const announcements = await storage.getAnnouncements(groupIds.length > 0 ? groupIds : undefined);
|
||||
res.json(announcements);
|
||||
} catch (error) {
|
||||
console.error("Error fetching announcements:", error);
|
||||
res.status(500).json({ message: "Failed to fetch announcements" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/announcements', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
|
||||
const user = await storage.getUserWithGroups(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
|
||||
// Only admin can create announcements
|
||||
if (user.role !== 'admin') {
|
||||
return res.status(403).json({ message: "Only administrators can create announcements" });
|
||||
}
|
||||
|
||||
const announcementData = insertAnnouncementSchema.parse({
|
||||
...req.body,
|
||||
authorId: user.id,
|
||||
});
|
||||
|
||||
const announcement = await storage.createAnnouncement(announcementData);
|
||||
res.status(201).json(announcement);
|
||||
} catch (error) {
|
||||
console.error("Error creating announcement:", error);
|
||||
if (error instanceof z.ZodError) {
|
||||
return res.status(400).json({ message: "Validation error", errors: error.errors });
|
||||
}
|
||||
res.status(500).json({ message: "Failed to create announcement" });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/announcements/:id', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
|
||||
const user = await storage.getUserWithGroups(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
|
||||
// Only admin can delete announcements
|
||||
if (user.role !== 'admin') {
|
||||
return res.status(403).json({ message: "Only administrators can delete announcements" });
|
||||
}
|
||||
|
||||
const id = parseInt(req.params.id);
|
||||
const success = await storage.deleteAnnouncement(id);
|
||||
|
||||
if (!success) {
|
||||
return res.status(404).json({ message: "Announcement not found" });
|
||||
}
|
||||
|
||||
res.json({ message: "Announcement deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error("Error deleting announcement:", error);
|
||||
res.status(500).json({ message: "Failed to delete announcement" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/weather/geolocation', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
customerOrders,
|
||||
dlcProducts,
|
||||
tasks,
|
||||
announcements,
|
||||
nocodbConfig,
|
||||
invoiceVerificationCache,
|
||||
savTickets,
|
||||
@@ -47,6 +48,9 @@ import {
|
||||
type Task,
|
||||
type InsertTask,
|
||||
type TaskWithRelations,
|
||||
type Announcement,
|
||||
type InsertAnnouncement,
|
||||
type AnnouncementWithRelations,
|
||||
type NocodbConfig,
|
||||
type InsertNocodbConfig,
|
||||
type InvoiceVerificationCache,
|
||||
@@ -170,6 +174,11 @@ export interface IStorage {
|
||||
updateTask(id: number, task: Partial<InsertTask>): Promise<Task>;
|
||||
deleteTask(id: number): Promise<void>;
|
||||
completeTask(id: number, completedBy?: string): Promise<void>;
|
||||
|
||||
// Announcement operations
|
||||
createAnnouncement(announcement: InsertAnnouncement): Promise<Announcement>;
|
||||
getAnnouncements(groupIds?: number[]): Promise<AnnouncementWithRelations[]>;
|
||||
deleteAnnouncement(id: number): Promise<boolean>;
|
||||
|
||||
// SAV operations
|
||||
getSavTickets(filters?: {
|
||||
@@ -1580,6 +1589,55 @@ export class DatabaseStorage implements IStorage {
|
||||
.where(eq(tasks.id, id));
|
||||
}
|
||||
|
||||
// Announcement operations
|
||||
async createAnnouncement(announcementData: InsertAnnouncement): Promise<Announcement> {
|
||||
const [announcement] = await db.insert(announcements).values(announcementData).returning();
|
||||
return announcement;
|
||||
}
|
||||
|
||||
async getAnnouncements(groupIds?: number[]): Promise<AnnouncementWithRelations[]> {
|
||||
let query = db
|
||||
.select({
|
||||
announcement: announcements,
|
||||
author: users,
|
||||
group: groups,
|
||||
})
|
||||
.from(announcements)
|
||||
.leftJoin(users, eq(announcements.authorId, users.id))
|
||||
.leftJoin(groups, eq(announcements.groupId, groups.id));
|
||||
|
||||
// Filter by groupIds if provided (for admin filtering by store)
|
||||
if (groupIds && groupIds.length > 0) {
|
||||
query = query.where(
|
||||
or(
|
||||
isNull(announcements.groupId), // Global announcements (no group)
|
||||
inArray(announcements.groupId, groupIds)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const results = await query.orderBy(
|
||||
// Sort by priority (urgent > important > normal) then by date (newest first)
|
||||
sql`CASE
|
||||
WHEN ${announcements.priority} = 'urgent' THEN 3
|
||||
WHEN ${announcements.priority} = 'important' THEN 2
|
||||
ELSE 1
|
||||
END DESC`,
|
||||
desc(announcements.createdAt)
|
||||
);
|
||||
|
||||
return results.map(result => ({
|
||||
...result.announcement,
|
||||
author: result.author!,
|
||||
group: result.group,
|
||||
}));
|
||||
}
|
||||
|
||||
async deleteAnnouncement(id: number): Promise<boolean> {
|
||||
const result = await db.delete(announcements).where(eq(announcements.id, id));
|
||||
return result.rowCount ? result.rowCount > 0 : false;
|
||||
}
|
||||
|
||||
// SAV operations
|
||||
async getSavTickets(filters?: {
|
||||
groupIds?: number[];
|
||||
@@ -1861,6 +1919,7 @@ export class MemStorage implements IStorage {
|
||||
private customerOrders = new Map<number, CustomerOrder>();
|
||||
private dlcProducts = new Map<number, DlcProduct>();
|
||||
private tasks = new Map<number, Task>();
|
||||
private announcements = new Map<number, Announcement>();
|
||||
private savTickets = new Map<number, SavTicket>();
|
||||
private savTicketHistories = new Map<number, SavTicketHistory[]>();
|
||||
|
||||
@@ -1873,6 +1932,7 @@ export class MemStorage implements IStorage {
|
||||
customerOrder: 1,
|
||||
dlcProduct: 1,
|
||||
task: 1,
|
||||
announcement: 1,
|
||||
savTicket: 1,
|
||||
savTicketHistory: 1,
|
||||
};
|
||||
@@ -3272,6 +3332,55 @@ export class MemStorage implements IStorage {
|
||||
// In development, this is a no-op
|
||||
console.log('🧹 DEV: Weather cache cleared due to location change');
|
||||
}
|
||||
|
||||
// Announcement operations
|
||||
async createAnnouncement(announcementData: InsertAnnouncement): Promise<Announcement> {
|
||||
const id = this.idCounters.announcement++;
|
||||
const announcement: Announcement = {
|
||||
id,
|
||||
...announcementData,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
this.announcements.set(id, announcement);
|
||||
return announcement;
|
||||
}
|
||||
|
||||
async getAnnouncements(groupIds?: number[]): Promise<AnnouncementWithRelations[]> {
|
||||
const announcements = Array.from(this.announcements.values());
|
||||
|
||||
let filteredAnnouncements = announcements;
|
||||
|
||||
// Filter by groupIds if provided (for admin filtering by store)
|
||||
if (groupIds && groupIds.length > 0) {
|
||||
filteredAnnouncements = announcements.filter(announcement =>
|
||||
!announcement.groupId || groupIds.includes(announcement.groupId)
|
||||
);
|
||||
}
|
||||
|
||||
// Add relations
|
||||
return filteredAnnouncements.map(announcement => {
|
||||
const author = this.users.get(announcement.authorId);
|
||||
const group = announcement.groupId ? this.groups.get(announcement.groupId) : undefined;
|
||||
|
||||
return {
|
||||
...announcement,
|
||||
author: author!,
|
||||
group,
|
||||
};
|
||||
}).sort((a, b) => {
|
||||
// Sort by priority (urgent > important > normal) then by date (newest first)
|
||||
const priorityOrder = { urgent: 3, important: 2, normal: 1 };
|
||||
const priorityDiff = (priorityOrder[b.priority as keyof typeof priorityOrder] || 1) -
|
||||
(priorityOrder[a.priority as keyof typeof priorityOrder] || 1);
|
||||
if (priorityDiff !== 0) return priorityDiff;
|
||||
return new Date(b.createdAt!).getTime() - new Date(a.createdAt!).getTime();
|
||||
});
|
||||
}
|
||||
|
||||
async deleteAnnouncement(id: number): Promise<boolean> {
|
||||
return this.announcements.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Use MemStorage in development, DatabaseStorage in production
|
||||
|
||||
Reference in new issue
Block a user