feat: add new critical API documentation for notifications, dashboard widgets, signage, and exam sessions

Co-authored-by: cetinmustafa83 <30570791+cetinmustafa83@users.noreply.github.com>
This commit is contained in:
v0andcetinmustafa83 committed 2026-07-31 13:57:47 +00:00
1 parent 0c9a46c163
commit f6a3953469
9 files changed
+1788

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { WidgetContainer } from './widget-container';
import { WIDGET_TYPES, WIDGET_SIZES, getDefaultConfig } from '@/lib/widget-utils';
import { Plus, Edit2, Save } from 'lucide-react';
interface DashboardCustomizerProps {
schoolId: string;
userId: string;
widgets: any[];
onWidgetAdd: (widgetType: string) => void;
onWidgetRemove: (widgetId: string) => void;
onLayoutSave: () => void;
}
export function DashboardCustomizer({
schoolId,
userId,
widgets,
onWidgetAdd,
onWidgetRemove,
onLayoutSave,
}: DashboardCustomizerProps) {
const [isEditing, setIsEditing] = useState(false);
const [showWidgetGallery, setShowWidgetGallery] = useState(false);
const availableWidgets = Object.values(WIDGET_TYPES).filter(
(type) => !widgets.some((w) => w.widgetType === type)
);
return (
<>
<div className="flex gap-2 mb-4">
<Button
onClick={() => setIsEditing(!isEditing)}
variant={isEditing ? 'default' : 'outline'}
size="sm"
className="flex items-center gap-2"
>
<Edit2 className="h-4 w-4" />
{isEditing ? 'Done Editing' : 'Customize'}
</Button>
{isEditing && (
<>
<Button
onClick={() => setShowWidgetGallery(true)}
variant="outline"
size="sm"
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add Widget
</Button>
<Button onClick={onLayoutSave} size="sm" className="flex items-center gap-2">
<Save className="h-4 w-4" />
Save Layout
</Button>
</>
)}
</div>
{/* Widget Gallery Dialog */}
<Dialog open={showWidgetGallery} onOpenChange={setShowWidgetGallery}>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Widget</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-4">
{availableWidgets.map((type) => (
<button
key={type}
onClick={() => {
onWidgetAdd(type);
setShowWidgetGallery(false);
}}
className="p-4 border rounded-lg hover:bg-gray-50 text-left"
>
<div className="font-semibold text-sm capitalize">
{type.replace(/_/g, ' ')}
</div>
<div className="text-xs text-gray-500 mt-1">
Add this widget to your dashboard
</div>
</button>
))}
</div>
</DialogContent>
</Dialog>
{/* Dashboard Grid */}
<div className="grid grid-cols-4 gap-4 auto-rows-max">
{widgets.map((widget) => (
<WidgetContainer
key={widget.id}
id={widget.id}
title={widget.title || widget.widgetType.replace(/_/g, ' ')}
widgetType={widget.widgetType}
size={widget.size as any}
isEditing={isEditing}
onRemove={() => onWidgetRemove(widget.id)}
>
<div className="text-sm text-gray-500 text-center py-8">
{widget.widgetType} widget content
</div>
</WidgetContainer>
))}
</div>
</>
);
}
+84
View File
@@ -0,0 +1,84 @@
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { MoreVertical, X, RotateCw } from 'lucide-react';
import { needsRefresh, updateWidgetCache } from '@/lib/widget-utils';
interface WidgetContainerProps {
id: string;
title: string;
widgetType: string;
size: 'small' | 'medium' | 'large' | 'full_width';
isEditing: boolean;
onRemove: () => void;
onRefresh?: () => Promise<void>;
refreshInterval?: number;
lastRefresh?: Date | null;
children: React.ReactNode;
}
export function WidgetContainer({
id,
title,
size,
isEditing,
onRemove,
onRefresh,
refreshInterval = 300,
lastRefresh,
children,
}: WidgetContainerProps) {
const [isRefreshing, setIsRefreshing] = useState(false);
const [showMenu, setShowMenu] = useState(false);
const sizeClasses = {
small: 'col-span-1',
medium: 'col-span-2',
large: 'col-span-3',
full_width: 'col-span-full',
};
const handleRefresh = async () => {
if (!onRefresh) return;
setIsRefreshing(true);
try {
await onRefresh();
} catch (error) {
console.error('[v0] Widget refresh failed:', error);
} finally {
setIsRefreshing(false);
}
};
return (
<Card className={`${sizeClasses[size]} h-full shadow-sm hover:shadow-md transition-shadow`}>
<CardHeader className="flex flex-row items-center justify-between pb-3">
<CardTitle className="text-base font-semibold">{title}</CardTitle>
{isEditing && (
<div className="flex gap-1">
{onRefresh && (
<Button
size="sm"
variant="ghost"
onClick={handleRefresh}
disabled={isRefreshing}
className="h-6 w-6 p-0"
>
<RotateCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} />
</Button>
)}
<Button
size="sm"
variant="ghost"
onClick={onRemove}
className="h-6 w-6 p-0 hover:bg-red-50 hover:text-red-600"
>
<X className="h-4 w-4" />
</Button>
</div>
)}
</CardHeader>
<CardContent className="pb-4">{children}</CardContent>
</Card>
);
}