Compare commits

...

2 Commits

2 changed files with 216 additions and 3 deletions

View File

@ -0,0 +1,105 @@
import { Search } from 'lucide-react';
export interface ReportFilterValues {
company: string;
branch: string;
department: string;
designation: string;
employeeName: string;
employeeId: string;
fromDate: string;
toDate: string;
}
interface ReportFiltersProps {
filters: ReportFilterValues;
onFilterChange: (newFilters: ReportFilterValues) => void;
}
export default function ReportFilters({ filters, onFilterChange }: ReportFiltersProps) {
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
onFilterChange({ ...filters, [e.target.name]: e.target.value });
};
const inputClass = "w-full px-3 py-2 border border-app-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary text-text-secondary bg-app-card";
const labelClass = "text-xs text-text-muted mb-1";
return (
<div className="bg-app-card p-4 rounded-lg shadow-sm grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Company Dropdown */}
<div className="flex flex-col">
<label className={labelClass}>Company</label>
<select name="company" value={filters.company} onChange={handleChange} className={inputClass}>
<option value="All">All</option>
<option value="CLRI">CLRI</option>
</select>
</div>
{/* Branch Dropdown */}
<div className="flex flex-col">
<label className={labelClass}>Branch</label>
<select name="branch" value={filters.branch} onChange={handleChange} className={inputClass}>
<option value="All">All</option>
<option value="Bengaluru">Bengaluru</option>
<option value="Pune">Pune</option>
<option value="Hyderabad">Hyderabad</option>
</select>
</div>
{/* Department Dropdown */}
<div className="flex flex-col">
<label className={labelClass}>Department</label>
<select name="department" value={filters.department} onChange={handleChange} className={inputClass}>
<option value="All">All</option>
<option value="IT">IT</option>
<option value="Training">Training</option>
<option value="Analytics">Analytics</option>
<option value="Placement">Placement</option>
</select>
</div>
{/* Designation Dropdown */}
<div className="flex flex-col">
<label className={labelClass}>Designation</label>
<select name="designation" value={filters.designation} onChange={handleChange} className={inputClass}>
<option value="All">All</option>
<option value="Software Engineer">Software Engineer</option>
<option value="Trainer">Trainer</option>
<option value="Data Analyst">Data Analyst</option>
<option value="Manager">Manager</option>
</select>
</div>
{/* From Date */}
<div className="flex flex-col">
<label className={labelClass}>From Date</label>
<input type="date" name="fromDate" value={filters.fromDate} onChange={handleChange} className={inputClass} />
</div>
{/* To Date */}
<div className="flex flex-col">
<label className={labelClass}>To Date</label>
<input type="date" name="toDate" value={filters.toDate} onChange={handleChange} className={inputClass} />
</div>
{/* Search by Name */}
<div className="flex flex-col">
<label className={labelClass}>Employee Name</label>
<div className="relative">
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-text-light" />
<input type="text" name="employeeName" value={filters.employeeName} onChange={handleChange} placeholder="Search name..." className={`${inputClass} pl-9`} />
</div>
</div>
{/* Search by ID */}
<div className="flex flex-col">
<label className={labelClass}>Employee ID</label>
<div className="relative">
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-text-light" />
<input type="text" name="employeeId" value={filters.employeeId} onChange={handleChange} placeholder="Search ID..." className={`${inputClass} pl-9`} />
</div>
</div>
</div>
);
}

View File

@ -1,8 +1,116 @@
import { useState, useMemo } from 'react';
import { Download, FileText } from 'lucide-react';
import { toast } from 'sonner';
import ReportFilters, { type ReportFilterValues } from '../../components/summary/ReportFilters';
// 1. Dummy Data for testing
const dummyReportData = [
{ id: 'EMP001', name: 'Alice', company: 'CLRI', branch: 'Bengaluru', department: 'IT', designation: 'Software Engineer', date: '2024-07-01', status: 'Present' },
{ id: 'EMP002', name: 'Bob', company: 'CLRI', branch: 'Pune', department: 'Training', designation: 'Trainer', date: '2024-07-01', status: 'Absent' },
{ id: 'EMP003', name: 'Charlie', company: 'CLRI', branch: 'Hyderabad', department: 'Analytics', designation: 'Data Analyst', date: '2024-07-01', status: 'Present' },
{ id: 'EMP004', name: 'David', company: 'CLRI', branch: 'Bengaluru', department: 'Placement', designation: 'Manager', date: '2024-07-02', status: 'Late' },
{ id: 'EMP005', name: 'Eve', company: 'CLRI', branch: 'Pune', department: 'IT', designation: 'Software Engineer', date: '2024-07-02', status: 'Present' },
];
export default function MonthlyReport() { export default function MonthlyReport() {
// 2. Default Filters State
const [filters, setFilters] = useState<ReportFilterValues>({
company: 'All',
branch: 'All',
department: 'All',
designation: 'All',
employeeName: '',
employeeId: '',
fromDate: '',
toDate: '',
});
// 3. Client-side filtering logic (for current dummy data testing)
const filteredData = useMemo(() => {
return dummyReportData.filter(record => {
const matchesName = filters.employeeName ? record.name.toLowerCase().includes(filters.employeeName.toLowerCase()) : true;
const matchesId = filters.employeeId ? record.id.toLowerCase().includes(filters.employeeId.toLowerCase()) : true;
const matchesCompany = filters.company === 'All' || record.company === filters.company;
const matchesBranch = filters.branch === 'All' || record.branch === filters.branch;
const matchesDept = filters.department === 'All' || record.department === filters.department;
const matchesDesg = filters.designation === 'All' || record.designation === filters.designation;
const recordDate = new Date(record.date);
const matchesFrom = filters.fromDate ? recordDate >= new Date(filters.fromDate) : true;
const matchesTo = filters.toDate ? recordDate <= new Date(filters.toDate) : true;
return matchesName && matchesId && matchesCompany && matchesBranch && matchesDept && matchesDesg && matchesFrom && matchesTo;
});
}, [filters]);
const handleDownload = () => {
// In the future, this will trigger an API call to get an Excel/CSV file
toast.success("Preparing report for download...");
};
return ( return (
<div className="bg-app-card p-6 rounded-lg shadow-sm"> <div className="space-y-6">
<h2 className="text-xl font-bold text-text-primary">Monthly Report</h2>
<p className="text-text-muted mt-2">Monthly attendance report UI will be implemented here.</p> {/* Header & Download Button */}
<div className="bg-app-card p-6 rounded-lg shadow-sm flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="flex items-center space-x-3">
<div className="p-3 bg-primary/10 rounded-full">
<FileText className="text-primary" size={28} />
</div>
<div>
<h2 className="text-xl font-bold text-text-primary">Monthly Attendance Report</h2>
<p className="text-sm text-text-muted">Filter and download detailed attendance reports.</p>
</div>
</div>
<button
onClick={handleDownload}
className="flex items-center justify-center px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-hover transition-colors font-medium"
>
<Download size={18} className="mr-2" />
Download Report
</button>
</div>
{/* Reusable Filters Component */}
<ReportFilters filters={filters} onFilterChange={setFilters} />
{/* Dummy Data Table (Proves filters are working) */}
<div className="bg-app-card p-6 rounded-lg shadow-sm overflow-x-auto">
<h3 className="text-lg font-semibold text-text-primary mb-4">Filtered Results ({filteredData.length})</h3>
<table className="min-w-full divide-y divide-app-border">
<thead>
<tr>
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Emp ID</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Name</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Branch</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Department</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Designation</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Date</th>
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-app-border">
{filteredData.length > 0 ? (
filteredData.map((record, index) => (
<tr key={index}>
<td className="px-4 py-3 text-sm text-text-primary">{record.id}</td>
<td className="px-4 py-3 text-sm text-text-primary">{record.name}</td>
<td className="px-4 py-3 text-sm text-text-secondary">{record.branch}</td>
<td className="px-4 py-3 text-sm text-text-secondary">{record.department}</td>
<td className="px-4 py-3 text-sm text-text-secondary">{record.designation}</td>
<td className="px-4 py-3 text-sm text-text-secondary">{record.date}</td>
<td className="px-4 py-3 text-sm text-text-secondary">{record.status}</td>
</tr>
))
) : (
<tr>
<td colSpan={7} className="px-4 py-8 text-center text-sm text-text-muted">No records match your filters.</td>
</tr>
)}
</tbody>
</table>
</div>
</div> </div>
); );
} }