Compare commits
2 Commits
5b88648885
...
2e79c232b0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e79c232b0 | |||
| 4aad26c852 |
@ -0,0 +1,85 @@
|
|||||||
|
import { Search } from 'lucide-react';
|
||||||
|
|
||||||
|
export interface DailyReportFilterValues {
|
||||||
|
date: string;
|
||||||
|
company: string;
|
||||||
|
branch: string;
|
||||||
|
department: string;
|
||||||
|
designation: string;
|
||||||
|
employeeName: string;
|
||||||
|
employeeId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DailyReportFiltersProps {
|
||||||
|
filters: DailyReportFilterValues;
|
||||||
|
onFilterChange: (newFilters: DailyReportFilterValues) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DailyReportFilters({ filters, onFilterChange }: DailyReportFiltersProps) {
|
||||||
|
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">
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label className={labelClass}>Date</label>
|
||||||
|
<input type="date" name="date" value={filters.date} onChange={handleChange} className={inputClass} />
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,8 +1,147 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import { useState, useMemo } from 'react';
|
||||||
|
import { Download, FileText } from 'lucide-react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import DailyReportFilters, { type DailyReportFilterValues } from '../../components/summary/DailyReportFilters';
|
||||||
|
|
||||||
|
// Helper to get current system date in YYYY-MM-DD format
|
||||||
|
const getTodayDate = () => {
|
||||||
|
const today = new Date();
|
||||||
|
return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const todayStr = getTodayDate();
|
||||||
|
const yesterday = new Date();
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
const yesterdayStr = `${yesterday.getFullYear()}-${String(yesterday.getMonth() + 1).padStart(2, '0')}-${String(yesterday.getDate()).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
// Dummy Data - Dynamically using today's and yesterday's date
|
||||||
|
const dummyDailyData = [
|
||||||
|
{ id: 'EMP001', name: 'Alice', company: 'CLRI', branch: 'Bengaluru', department: 'IT', designation: 'Software Engineer', date: todayStr, checkIn: '09:05 AM', checkOut: '06:15 PM', status: 'Present' },
|
||||||
|
{ id: 'EMP002', name: 'Bob', company: 'CLRI', branch: 'Pune', department: 'Training', designation: 'Trainer', date: todayStr, checkIn: '--', checkOut: '--', status: 'Absent' },
|
||||||
|
{ id: 'EMP003', name: 'Charlie', company: 'CLRI', branch: 'Hyderabad', department: 'Analytics', designation: 'Data Analyst', date: todayStr, checkIn: '09:15 AM', checkOut: '06:05 PM', status: 'Late' },
|
||||||
|
{ id: 'EMP004', name: 'David', company: 'CLRI', branch: 'Bengaluru', department: 'Placement', designation: 'Manager', date: todayStr, checkIn: '09:00 AM', checkOut: '--', status: 'Mispunch' },
|
||||||
|
{ id: 'EMP005', name: 'Eve', company: 'CLRI', branch: 'Pune', department: 'IT', designation: 'Software Engineer', date: todayStr, checkIn: '08:50 AM', checkOut: '05:30 PM', status: 'Present' },
|
||||||
|
{ id: 'EMP006', name: 'Frank', company: 'CLRI', branch: 'Bengaluru', department: 'IT', designation: 'Software Engineer', date: yesterdayStr, checkIn: '09:00 AM', checkOut: '06:00 PM', status: 'Present' },
|
||||||
|
];
|
||||||
|
|
||||||
export default function DailyReport() {
|
export default function DailyReport() {
|
||||||
|
// Manage all filter state in one object
|
||||||
|
const [filters, setFilters] = useState<DailyReportFilterValues>({
|
||||||
|
date: getTodayDate(),
|
||||||
|
company: 'All',
|
||||||
|
branch: 'All',
|
||||||
|
department: 'All',
|
||||||
|
designation: 'All',
|
||||||
|
employeeName: '',
|
||||||
|
employeeId: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client-side filtering logic
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
return dummyDailyData.filter(record => {
|
||||||
|
const matchesDate = record.date === filters.date;
|
||||||
|
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;
|
||||||
|
|
||||||
|
return matchesDate && matchesName && matchesId && matchesCompany && matchesBranch && matchesDept && matchesDesg;
|
||||||
|
});
|
||||||
|
}, [filters]);
|
||||||
|
|
||||||
|
const handleDownload = () => {
|
||||||
|
toast.success(`Preparing daily report for ${filters.date}...`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusClass = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'Present': return 'bg-present-100 text-present-700';
|
||||||
|
case 'Late': return 'bg-late-100 text-late-700';
|
||||||
|
case 'Absent': return 'bg-absent-100 text-absent-700';
|
||||||
|
case 'Mispunch': return 'bg-mispunch-100 text-mispunch-700';
|
||||||
|
default: return 'bg-app-muted text-text-secondary';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
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">Daily Report</h2>
|
|
||||||
<p className="text-text-muted mt-2">Daily 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">Daily Attendance Report</h2>
|
||||||
|
<p className="text-sm text-text-muted">View and download daily attendance logs.</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 */}
|
||||||
|
<DailyReportFilters filters={filters} onFilterChange={setFilters} />
|
||||||
|
|
||||||
|
{/* Data Table */}
|
||||||
|
<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">
|
||||||
|
Results for {new Date(filters.date + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: 'long', year: 'numeric' })} ({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">Company</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">Check-In</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Check-Out</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} className="hover:bg-app-muted transition-colors">
|
||||||
|
<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.company}</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.checkIn}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.checkOut}</td>
|
||||||
|
<td className="px-4 py-3 text-sm">
|
||||||
|
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusClass(record.status)}`}>
|
||||||
|
{record.status}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={9} className="px-4 py-8 text-center text-sm text-text-muted">
|
||||||
|
No attendance records found for this date.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { Download, FileText } from 'lucide-react';
|
import { Download, FileText } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import ReportFilters, { type ReportFilterValues } from '../../components/summary/ReportFilters';
|
import ReportFilters, { type ReportFilterValues } from '../../components/summary/MonthlyReportFilters';
|
||||||
|
|
||||||
// 1. Dummy Data for testing
|
// 1. Dummy Data for testing
|
||||||
const dummyReportData = [
|
const dummyReportData = [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user