import React, { useEffect, useState } from 'react'; import { Calendar, CheckCircle, Clock, Loader2, Plus, Trash2, Bell } from 'lucide-react'; import toast from 'react-hot-toast'; import { createPostAwardMilestone, deletePostAwardMilestone, getNotificationInbox, getPostAwardMilestones, ProjectMilestone, updatePostAwardMilestone, } from '../../api/client'; interface Props { projectId: string; userId?: string; } const PostAwardPanel: React.FC = ({ projectId, userId }) => { const [milestones, setMilestones] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [title, setTitle] = useState(''); const [dueDate, setDueDate] = useState(''); const [description, setDescription] = useState(''); const [alerts, setAlerts] = useState([]); const load = async () => { setLoading(true); try { const [ms, inbox] = await Promise.all([ getPostAwardMilestones(projectId), getNotificationInbox(userId, true).catch(() => ({ notifications: [] })), ]); setMilestones(ms.milestones || []); const related = (inbox.notifications || []).filter( (n: any) => n.project_id === projectId && n.type === 'post_award_deadline', ); setAlerts(related); } catch { toast.error('Nie udało się załadować kamieni milowych.'); } finally { setLoading(false); } }; useEffect(() => { if (projectId) load(); }, [projectId]); const handleAdd = async () => { if (!title.trim() || !dueDate) { toast.error('Podaj tytuł i termin.'); return; } setSaving(true); try { await createPostAwardMilestone(projectId, { title: title.trim(), due_date: new Date(dueDate).toISOString(), description: description.trim() || undefined, }); setTitle(''); setDueDate(''); setDescription(''); toast.success('Kamień milowy dodany.'); await load(); } catch (e: any) { toast.error(e.response?.data?.detail || 'Błąd zapisu.'); } finally { setSaving(false); } }; const toggleComplete = async (m: ProjectMilestone) => { try { await updatePostAwardMilestone(projectId, m.id, { status: m.status === 'completed' ? 'pending' : 'completed', }); await load(); } catch { toast.error('Nie udało się zaktualizować statusu.'); } }; const handleDelete = async (id: string) => { try { await deletePostAwardMilestone(projectId, id); toast.success('Usunięto.'); await load(); } catch { toast.error('Nie udało się usunąć.'); } }; if (loading) { return (
); } return (

Post-award — śledzenie terminów

Kamienie milowe po przyznaniu dotacji (raporty, rozliczenia). Scheduler tworzy alerty w skrzynce powiadomień.

{alerts.length > 0 && (
Zbliżające się terminy ({alerts.length})
{alerts.map((a) => (
{a.title} — {a.deadline ? new Date(a.deadline).toLocaleDateString('pl-PL') : ''}
))}
)}

Nowy kamień milowy

setTitle(e.target.value)} style={{ padding: '0.6rem 0.75rem', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.1)', background: 'rgba(0,0,0,0.2)', color: '#fff' }} /> setDueDate(e.target.value)} style={{ padding: '0.6rem 0.75rem', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.1)', background: 'rgba(0,0,0,0.2)', color: '#fff' }} />