Files
bakery-ia/frontend/src/services/api/notification.service.ts
2025-08-28 10:41:04 +02:00

406 lines
13 KiB
TypeScript

import { apiClient, ApiResponse } from './client';
// Notification types
export interface Notification {
id: string;
tenant_id: string;
type: 'info' | 'warning' | 'error' | 'success';
category: 'system' | 'inventory' | 'production' | 'sales' | 'forecasting' | 'orders' | 'pos';
title: string;
message: string;
data?: Record<string, any>;
priority: 'low' | 'normal' | 'high' | 'urgent';
is_read: boolean;
is_dismissed: boolean;
read_at?: string;
dismissed_at?: string;
expires_at?: string;
created_at: string;
user_id?: string;
}
export interface NotificationTemplate {
id: string;
tenant_id: string;
name: string;
description?: string;
category: string;
type: string;
subject_template: string;
message_template: string;
channels: ('email' | 'sms' | 'push' | 'in_app' | 'whatsapp')[];
variables: Array<{
name: string;
type: 'string' | 'number' | 'date' | 'boolean';
required: boolean;
description?: string;
}>;
conditions?: Array<{
field: string;
operator: 'eq' | 'ne' | 'gt' | 'lt' | 'gte' | 'lte' | 'contains';
value: any;
}>;
is_active: boolean;
created_at: string;
updated_at: string;
}
export interface NotificationPreferences {
id: string;
user_id: string;
tenant_id: string;
email_notifications: boolean;
sms_notifications: boolean;
push_notifications: boolean;
whatsapp_notifications: boolean;
quiet_hours: {
enabled: boolean;
start_time: string;
end_time: string;
timezone: string;
};
categories: Record<string, {
enabled: boolean;
channels: string[];
min_priority: 'low' | 'normal' | 'high' | 'urgent';
}>;
updated_at: string;
}
export interface AlertRule {
id: string;
tenant_id: string;
name: string;
description?: string;
category: string;
rule_type: 'threshold' | 'trend' | 'anomaly' | 'schedule';
conditions: Array<{
metric: string;
operator: string;
value: any;
time_window?: string;
}>;
actions: Array<{
type: 'notification' | 'webhook' | 'email';
config: Record<string, any>;
}>;
is_active: boolean;
last_triggered?: string;
trigger_count: number;
created_at: string;
updated_at: string;
}
class NotificationService {
private readonly baseUrl = '/notifications';
// Notification management
async getNotifications(params?: {
page?: number;
size?: number;
category?: string;
type?: string;
priority?: string;
is_read?: boolean;
is_dismissed?: boolean;
}): Promise<ApiResponse<{ items: Notification[]; total: number; page: number; size: number; pages: number; unread_count: number }>> {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, value.toString());
}
});
}
const url = queryParams.toString()
? `${this.baseUrl}?${queryParams.toString()}`
: this.baseUrl;
return apiClient.get(url);
}
async getNotification(notificationId: string): Promise<ApiResponse<Notification>> {
return apiClient.get(`${this.baseUrl}/${notificationId}`);
}
async markAsRead(notificationId: string): Promise<ApiResponse<Notification>> {
return apiClient.post(`${this.baseUrl}/${notificationId}/read`);
}
async markAsUnread(notificationId: string): Promise<ApiResponse<Notification>> {
return apiClient.post(`${this.baseUrl}/${notificationId}/unread`);
}
async dismiss(notificationId: string): Promise<ApiResponse<Notification>> {
return apiClient.post(`${this.baseUrl}/${notificationId}/dismiss`);
}
async markAllAsRead(category?: string): Promise<ApiResponse<{ updated_count: number }>> {
const url = category
? `${this.baseUrl}/mark-all-read?category=${encodeURIComponent(category)}`
: `${this.baseUrl}/mark-all-read`;
return apiClient.post(url);
}
async dismissAll(category?: string): Promise<ApiResponse<{ updated_count: number }>> {
const url = category
? `${this.baseUrl}/dismiss-all?category=${encodeURIComponent(category)}`
: `${this.baseUrl}/dismiss-all`;
return apiClient.post(url);
}
async deleteNotification(notificationId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/${notificationId}`);
}
// Notification creation and sending
async createNotification(notificationData: {
type: Notification['type'];
category: string;
title: string;
message: string;
priority?: Notification['priority'];
data?: Record<string, any>;
user_id?: string;
expires_at?: string;
channels?: string[];
}): Promise<ApiResponse<Notification>> {
return apiClient.post(this.baseUrl, notificationData);
}
async sendBulkNotification(notificationData: {
type: Notification['type'];
category: string;
title: string;
message: string;
priority?: Notification['priority'];
data?: Record<string, any>;
user_ids?: string[];
user_roles?: string[];
channels?: string[];
}): Promise<ApiResponse<{ sent_count: number; failed_count: number }>> {
return apiClient.post(`${this.baseUrl}/bulk-send`, notificationData);
}
// Template management
async getTemplates(category?: string): Promise<ApiResponse<NotificationTemplate[]>> {
const url = category
? `${this.baseUrl}/templates?category=${encodeURIComponent(category)}`
: `${this.baseUrl}/templates`;
return apiClient.get(url);
}
async getTemplate(templateId: string): Promise<ApiResponse<NotificationTemplate>> {
return apiClient.get(`${this.baseUrl}/templates/${templateId}`);
}
async createTemplate(templateData: {
name: string;
description?: string;
category: string;
type: string;
subject_template: string;
message_template: string;
channels: string[];
variables: NotificationTemplate['variables'];
conditions?: NotificationTemplate['conditions'];
}): Promise<ApiResponse<NotificationTemplate>> {
return apiClient.post(`${this.baseUrl}/templates`, templateData);
}
async updateTemplate(templateId: string, templateData: Partial<NotificationTemplate>): Promise<ApiResponse<NotificationTemplate>> {
return apiClient.put(`${this.baseUrl}/templates/${templateId}`, templateData);
}
async deleteTemplate(templateId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/templates/${templateId}`);
}
async previewTemplate(templateId: string, variables: Record<string, any>): Promise<ApiResponse<{
subject: string;
message: string;
rendered_html?: string;
}>> {
return apiClient.post(`${this.baseUrl}/templates/${templateId}/preview`, { variables });
}
// User preferences
async getPreferences(userId?: string): Promise<ApiResponse<NotificationPreferences>> {
const url = userId
? `${this.baseUrl}/preferences/${userId}`
: `${this.baseUrl}/preferences`;
return apiClient.get(url);
}
async updatePreferences(preferencesData: Partial<NotificationPreferences>, userId?: string): Promise<ApiResponse<NotificationPreferences>> {
const url = userId
? `${this.baseUrl}/preferences/${userId}`
: `${this.baseUrl}/preferences`;
return apiClient.put(url, preferencesData);
}
// Alert rules
async getAlertRules(): Promise<ApiResponse<AlertRule[]>> {
return apiClient.get(`${this.baseUrl}/alert-rules`);
}
async getAlertRule(ruleId: string): Promise<ApiResponse<AlertRule>> {
return apiClient.get(`${this.baseUrl}/alert-rules/${ruleId}`);
}
async createAlertRule(ruleData: {
name: string;
description?: string;
category: string;
rule_type: AlertRule['rule_type'];
conditions: AlertRule['conditions'];
actions: AlertRule['actions'];
}): Promise<ApiResponse<AlertRule>> {
return apiClient.post(`${this.baseUrl}/alert-rules`, ruleData);
}
async updateAlertRule(ruleId: string, ruleData: Partial<AlertRule>): Promise<ApiResponse<AlertRule>> {
return apiClient.put(`${this.baseUrl}/alert-rules/${ruleId}`, ruleData);
}
async deleteAlertRule(ruleId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/alert-rules/${ruleId}`);
}
async testAlertRule(ruleId: string): Promise<ApiResponse<{
would_trigger: boolean;
current_values: Record<string, any>;
evaluation_result: string;
}>> {
return apiClient.post(`${this.baseUrl}/alert-rules/${ruleId}/test`);
}
// Real-time notifications (SSE)
async getSSEEndpoint(): Promise<ApiResponse<{ endpoint_url: string; auth_token: string }>> {
return apiClient.get(`${this.baseUrl}/sse/endpoint`);
}
connectSSE(onNotification: (notification: Notification) => void, onError?: (error: Error) => void): EventSource {
const token = localStorage.getItem('access_token');
const tenantId = localStorage.getItem('tenant_id');
const url = new URL(`${apiClient.getBaseURL()}/notifications/sse/stream`);
if (token) url.searchParams.append('token', token);
if (tenantId) url.searchParams.append('tenant_id', tenantId);
const eventSource = new EventSource(url.toString());
eventSource.onmessage = (event) => {
try {
const notification = JSON.parse(event.data);
onNotification(notification);
} catch (error) {
console.error('Failed to parse SSE notification:', error);
onError?.(error as Error);
}
};
eventSource.onerror = (error) => {
console.error('SSE connection error:', error);
onError?.(new Error('SSE connection failed'));
};
return eventSource;
}
// Analytics and reporting
async getNotificationStats(params?: {
start_date?: string;
end_date?: string;
category?: string;
}): Promise<ApiResponse<{
total_sent: number;
total_read: number;
total_dismissed: number;
read_rate: number;
dismiss_rate: number;
by_category: Array<{
category: string;
sent: number;
read: number;
dismissed: number;
}>;
by_channel: Array<{
channel: string;
sent: number;
delivered: number;
failed: number;
}>;
trends: Array<{
date: string;
sent: number;
read: number;
}>;
}>> {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, value.toString());
}
});
}
const url = queryParams.toString()
? `${this.baseUrl}/stats?${queryParams.toString()}`
: `${this.baseUrl}/stats`;
return apiClient.get(url);
}
// Utility methods
getNotificationTypes(): { value: string; label: string; icon: string; color: string }[] {
return [
{ value: 'info', label: 'Information', icon: 'info', color: 'blue' },
{ value: 'success', label: 'Success', icon: 'check', color: 'green' },
{ value: 'warning', label: 'Warning', icon: 'warning', color: 'yellow' },
{ value: 'error', label: 'Error', icon: 'error', color: 'red' },
];
}
getNotificationCategories(): { value: string; label: string; description: string }[] {
return [
{ value: 'system', label: 'System', description: 'System-wide notifications and updates' },
{ value: 'inventory', label: 'Inventory', description: 'Stock levels, expiration alerts, reorder notifications' },
{ value: 'production', label: 'Production', description: 'Production schedules, quality checks, batch completion' },
{ value: 'sales', label: 'Sales', description: 'Sales targets, performance alerts, revenue notifications' },
{ value: 'forecasting', label: 'Forecasting', description: 'Demand predictions, model updates, accuracy alerts' },
{ value: 'orders', label: 'Orders', description: 'New orders, order updates, delivery notifications' },
{ value: 'pos', label: 'POS', description: 'Point of sale integration, sync status, transaction alerts' },
];
}
getPriorityLevels(): { value: string; label: string; color: string; urgency: number }[] {
return [
{ value: 'low', label: 'Low', color: 'gray', urgency: 1 },
{ value: 'normal', label: 'Normal', color: 'blue', urgency: 2 },
{ value: 'high', label: 'High', color: 'orange', urgency: 3 },
{ value: 'urgent', label: 'Urgent', color: 'red', urgency: 4 },
];
}
getNotificationChannels(): { value: string; label: string; description: string; requires_setup: boolean }[] {
return [
{ value: 'in_app', label: 'In-App', description: 'Notifications within the application', requires_setup: false },
{ value: 'email', label: 'Email', description: 'Email notifications', requires_setup: true },
{ value: 'sms', label: 'SMS', description: 'Text message notifications', requires_setup: true },
{ value: 'push', label: 'Push', description: 'Browser/mobile push notifications', requires_setup: true },
{ value: 'whatsapp', label: 'WhatsApp', description: 'WhatsApp notifications', requires_setup: true },
];
}
}
export const notificationService = new NotificationService();