Files
app/app/admin/ReportTab.tsx
T

120 lines
6.5 KiB
TypeScript

// app/admin/ReportTab.tsx
"use client";
import { Download, Users } from 'lucide-react';
import React, { useState } from 'react';
import { type AttendanceDataMap, formatTimeHHMM, isWeekend, type Period, type Role } from './adminTypes';
import { YouthReportCard } from './YouthReportCard';
interface Props {
periods: Period[]; attendance: AttendanceDataMap; activePeriodId: string; setActivePeriodId: (id: string) => void; currentUserRole: Role;
scheduleData: any[];
}
const POT_HOUR_LIMIT = 90;
export const ReportTab: React.FC<Props> = ({ periods, attendance, activePeriodId, setActivePeriodId, currentUserRole, scheduleData }) => {
const activePeriod = periods.find(p => p.id === activePeriodId);
const [expandedYouthId, setExpandedYouthId] = useState<string | null>(null);
if (!activePeriod) return <p className="text-center font-bold text-slate-teal mt-10">Ingen period tillgänglig.</p>;
const getPeriodHoursTotal = (youthId: string): number => {
let total = 0;
for (const key in attendance) {
const entry = attendance[key];
if (entry.youthId === youthId && entry.date >= activePeriod.startDate && entry.date <= activePeriod.endDate) total += entry.weightedHours;
}
return total;
};
const getTimelineForYouth = (youthId: string) => {
return Object.values(attendance).filter(a => a.youthId === youthId && a.date >= activePeriod.startDate && a.date <= activePeriod.endDate)
.sort((a, b) => { if (a.date !== b.date) return a.date.localeCompare(b.date); return a.shiftId === 'MORNING' ? -1 : 1; });
};
const exportToCSV = () => {
if (!activePeriod) return;
let csvContent = "Datum;Pass;Namn;Lag;Status;Arbetad Tid (HH:MM);Viktad Pott;Anteckning\n";
const entries = Object.values(attendance).filter(a => a.date >= activePeriod.startDate && a.date <= activePeriod.endDate);
entries.sort((a, b) => {
if (a.date !== b.date) return a.date.localeCompare(b.date);
if (a.shiftId !== b.shiftId) return a.shiftId.localeCompare(b.shiftId);
const nameA = activePeriod.youthList.find(y => y.id === a.youthId)?.name || '';
const nameB = activePeriod.youthList.find(y => y.id === b.youthId)?.name || '';
return nameA.localeCompare(nameB);
});
entries.forEach(entry => {
const youth = activePeriod.youthList.find(y => y.id === entry.youthId);
if (!youth) return;
const shift = isWeekend(entry.date) ? 'Hela dagen' : (entry.shiftId === 'MORNING' ? 'Morgon' : 'Eftermiddag');
const teamName = youth.team === 'PF' ? 'Pilgrimsfalk' : 'Tumlare';
const worked = formatTimeHHMM(entry.hoursWorked);
const weighted = entry.weightedHours.toFixed(2).replace('.', ',');
const note = entry.note || '';
csvContent += `${entry.date};${shift};${youth.name};${teamName};${entry.status};${worked};${weighted};${note}\n`;
});
csvContent += "\nSummering (Timpott)\nNamn;Lag;Total Viktad Pott\n";
const sortedYouth = [...activePeriod.youthList].sort((a, b) => a.name.localeCompare(b.name));
sortedYouth.forEach(youth => {
const total = getPeriodHoursTotal(youth.id).toFixed(2).replace('.', ',');
const teamName = youth.team === 'PF' ? 'Pilgrimsfalk' : 'Tumlare';
csvContent += `${youth.name};${teamName};${total}\n`;
});
const blob = new Blob(["\uFEFF" + csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.setAttribute("href", url); link.setAttribute("download", `Narvaro_${activePeriod.name.replace(/ /g, '_')}.csv`);
document.body.appendChild(link); link.click(); document.body.removeChild(link);
};
const sortedActiveYouth = [...activePeriod.youthList].sort((a, b) => a.name.localeCompare(b.name));
return (
<div className="space-y-6 animate-fade-in">
<div className="flex flex-col md:flex-row justify-between items-center gap-4 rounded-3xl shadow-sm transition-all duration-300 bg-white/40 backdrop-blur-md border border-white/40 p-3 sm:p-5 hover:shadow-md">
<select value={activePeriodId} onChange={(e) => { setActivePeriodId(e.target.value); setExpandedYouthId(null); }} className="bg-white border border-slate-teal/10 p-3 rounded-xl font-bold text-slate-teal text-sm w-full md:w-auto focus:outline-none shadow-sm">
{periods.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
</select>
<div className="text-center md:text-right">
<h2 className="text-base font-black text-ebony uppercase tracking-widest">{activePeriod.name}</h2>
<p className="text-xs font-bold text-moss">{activePeriod.startDate} {activePeriod.endDate}</p>
</div>
</div>
<div>
<div className="flex flex-col md:flex-row justify-between md:items-center mb-6 gap-3">
<h2 className="font-black text-ebony uppercase tracking-widest flex items-center text-2xl justify-center sm:justify-normal">
<Users className="mr-2 text-slate-teal" size={24} /> Timrapport
</h2>
{currentUserRole !== 'Viewer' && (
<button onClick={exportToCSV} className="flex items-center justify-center gap-2 bg-seafoam text-eggshell font-black uppercase tracking-widest text-[10px] px-5 py-3 rounded-xl hover:bg-slate-teal transition-colors shadow-sm">
<Download size={16} /> Exportera till Excel
</button>
)}
</div>
<div className="space-y-3">
{sortedActiveYouth.map(youth => (
<YouthReportCard
key={youth.id}
youth={youth}
totalHours={getPeriodHoursTotal(youth.id)}
potHourLimit={POT_HOUR_LIMIT}
isExpanded={expandedYouthId === youth.id}
onToggle={() => setExpandedYouthId(expandedYouthId === youth.id ? null : youth.id)}
timeline={getTimelineForYouth(youth.id)}
scheduleData={scheduleData}
/>
))}
</div>
</div>
</div>
);
};