feat: Implement backend API and basic frontend structure

Adds initial backend API endpoints for fetching users and attendances, including basic filtering. Sets up the frontend routing with a layout component and includes placeholder pages for dashboard, users, and login. Refactors the README for local development setup.
This commit is contained in:
farelos
2026-02-23 10:36:00 -03:00
parent 0cf4fb92d7
commit 3250ad7537
26 changed files with 2947 additions and 8 deletions

41
components/KPICard.tsx Normal file
View File

@@ -0,0 +1,41 @@
import React from 'react';
import { LucideIcon } from 'lucide-react';
interface KPICardProps {
title: string;
value: string | number;
subValue?: string;
trend?: 'up' | 'down' | 'neutral';
trendValue?: string;
icon: LucideIcon;
colorClass?: string;
}
export const KPICard: React.FC<KPICardProps> = ({ title, value, subValue, trend, trendValue, icon: Icon, colorClass = "bg-blue-500" }) => {
return (
<div className="bg-white p-6 rounded-2xl shadow-sm border border-slate-100 flex flex-col justify-between hover:shadow-md transition-shadow duration-300">
<div className="flex justify-between items-start mb-4">
<div>
<h3 className="text-slate-500 text-sm font-medium mb-1">{title}</h3>
<div className="text-3xl font-bold text-slate-800 tracking-tight">{value}</div>
</div>
<div className={`p-3 rounded-xl ${colorClass} bg-opacity-10 text-opacity-100`}>
{/* Note: In Tailwind bg-opacity works if colorClass is like 'bg-blue-500'.
Here we assume the consumer passes specific utility classes or we construct them.
Simpler approach: Use a wrapper */}
<div className={`w-8 h-8 flex items-center justify-center rounded-lg ${colorClass.replace('text', 'bg').replace('500', '100')} ${colorClass}`}>
<Icon size={20} />
</div>
</div>
</div>
{(trend || subValue) && (
<div className="flex items-center gap-2 text-sm mt-auto">
{trend === 'up' && <span className="text-green-500 flex items-center font-medium"> {trendValue}</span>}
{trend === 'down' && <span className="text-red-500 flex items-center font-medium"> {trendValue}</span>}
{subValue && <span className="text-slate-400">{subValue}</span>}
</div>
)}
</div>
);
};