0a9588abb7
- Add pagination support to findAll (page, limit query params) - Add findByTemplateId method to service - Add GET /by-template/:templateId endpoint to controller - Service already includes CRUD for QuestionBank and QuestionBankItem
83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
|
|
import Toast, { ToastType } from '../components/Toast';
|
|
import { registerToastHandler } from '../src/utils/toast';
|
|
|
|
interface ToastItem {
|
|
id: string;
|
|
type: ToastType;
|
|
title?: string;
|
|
message: string;
|
|
duration?: number;
|
|
}
|
|
|
|
interface ToastContextType {
|
|
showToast: (type: ToastType, message: string, title?: string, duration?: number) => void;
|
|
showSuccess: (message: string, title?: string) => void;
|
|
showError: (message: string, title?: string) => void;
|
|
showWarning: (message: string, title?: string) => void;
|
|
showInfo: (message: string, title?: string) => void;
|
|
}
|
|
|
|
const ToastContext = createContext<ToastContextType | undefined>(undefined);
|
|
|
|
export const useToast = () => {
|
|
const context = useContext(ToastContext);
|
|
if (!context) {
|
|
throw new Error('useToast must be used within a ToastProvider');
|
|
}
|
|
return context;
|
|
};
|
|
|
|
interface ToastProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export const ToastProvider: React.FC<ToastProviderProps> = ({ children }) => {
|
|
const [toasts, setToasts] = useState<ToastItem[]>([]);
|
|
|
|
const showToast = (type: ToastType, message: string, title?: string, duration?: number) => {
|
|
const id = Date.now().toString();
|
|
const newToast: ToastItem = { id, type, message, title, duration };
|
|
|
|
setToasts(prev => {
|
|
// Deduplicate identical messages: discard old one if current type and content are the same
|
|
const filtered = prev.filter(t => t.message !== message || t.type !== type);
|
|
return [...filtered, newToast];
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
registerToastHandler({ showToast });
|
|
}, []);
|
|
|
|
const removeToast = (id: string) => {
|
|
setToasts(prev => prev.filter(toast => toast.id !== id));
|
|
};
|
|
|
|
const showSuccess = (message: string, title?: string) => showToast('success', message, title);
|
|
const showError = (message: string, title?: string) => showToast('error', message, title);
|
|
const showWarning = (message: string, title?: string) => showToast('warning', message, title);
|
|
const showInfo = (message: string, title?: string) => showToast('info', message, title);
|
|
|
|
return (
|
|
<ToastContext.Provider value={{ showToast, showSuccess, showError, showWarning, showInfo }}>
|
|
{children}
|
|
<div className="fixed bottom-0 right-0 z-[9999] p-4 space-y-3 pointer-events-none w-full max-w-sm flex flex-col items-end">
|
|
{toasts.map((toast) => (
|
|
<div
|
|
key={toast.id}
|
|
className="pointer-events-auto w-full"
|
|
>
|
|
<Toast
|
|
type={toast.type}
|
|
title={toast.title}
|
|
message={toast.message}
|
|
duration={toast.duration}
|
|
onClose={() => removeToast(toast.id)}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</ToastContext.Provider>
|
|
);
|
|
}; |