diff --git a/index.html b/index.html index 7b09be9..526dbfb 100644 --- a/index.html +++ b/index.html @@ -1,14 +1,15 @@ + - collect-track-report - + Employee Collection Management System + - - + + diff --git a/src/components/AdminDataEntryModal.tsx b/src/components/AdminDataEntryModal.tsx new file mode 100644 index 0000000..90d08e4 --- /dev/null +++ b/src/components/AdminDataEntryModal.tsx @@ -0,0 +1,184 @@ + +import React, { useState } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Calendar } from "@/components/ui/calendar"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { CalendarIcon } from "lucide-react"; +import { format } from "date-fns"; +import { cn } from "@/lib/utils"; +import { useEmployeeData } from "@/hooks/useEmployeeData"; +import { useToast } from "@/hooks/use-toast"; + +interface AdminDataEntryModalProps { + isOpen: boolean; + onClose: () => void; + onDataUpdate: () => void; +} + +export const AdminDataEntryModal = ({ isOpen, onClose, onDataUpdate }: AdminDataEntryModalProps) => { + const { employees, addTransaction } = useEmployeeData(); + const { toast } = useToast(); + + const [selectedEmployeeId, setSelectedEmployeeId] = useState(''); + const [collectionAmount, setCollectionAmount] = useState(''); + const [depositAmount, setDepositAmount] = useState(''); + const [selectedDate, setSelectedDate] = useState(new Date()); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!selectedEmployeeId) { + toast({ + title: "Error", + description: "Please select an employee", + variant: "destructive" + }); + return; + } + + const collection = parseFloat(collectionAmount) || 0; + const deposit = parseFloat(depositAmount) || 0; + + if (collection === 0 && deposit === 0) { + toast({ + title: "Error", + description: "Please enter at least one amount (collection or deposit)", + variant: "destructive" + }); + return; + } + + addTransaction(selectedEmployeeId, { + date: format(selectedDate, 'yyyy-MM-dd'), + collection, + deposit + }); + + toast({ + title: "Success", + description: "Transaction added successfully", + }); + + // Reset form + setSelectedEmployeeId(''); + setCollectionAmount(''); + setDepositAmount(''); + setSelectedDate(new Date()); + + onDataUpdate(); + }; + + const handleClose = () => { + setSelectedEmployeeId(''); + setCollectionAmount(''); + setDepositAmount(''); + setSelectedDate(new Date()); + onClose(); + }; + + return ( + + + + Insert Employee Data + + +
+
+ + +
+ +
+ + + + + + + date && setSelectedDate(date)} + initialFocus + className="pointer-events-auto" + /> + + +
+ +
+
+ + setCollectionAmount(e.target.value)} + className="text-right" + step="0.01" + min="0" + /> +
+ +
+ + setDepositAmount(e.target.value)} + className="text-right" + step="0.01" + min="0" + /> +
+
+ +
+ + +
+
+
+
+ ); +}; diff --git a/src/components/EmployeePaymentReport.tsx b/src/components/EmployeePaymentReport.tsx new file mode 100644 index 0000000..f9566fb --- /dev/null +++ b/src/components/EmployeePaymentReport.tsx @@ -0,0 +1,130 @@ + +import React, { useState } from 'react'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Badge } from "@/components/ui/badge"; +import { useEmployeeData } from "@/hooks/useEmployeeData"; + +export const EmployeePaymentReport = () => { + const { employees, getEmployeeTransactions } = useEmployeeData(); + const [selectedEmployeeId, setSelectedEmployeeId] = useState('all'); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-IN', { + style: 'currency', + currency: 'INR' + }).format(amount); + }; + + const formatDate = (date: string) => { + return new Date(date).toLocaleDateString('en-IN'); + }; + + const getTransactionsToShow = () => { + if (selectedEmployeeId === 'all') { + return employees.flatMap(employee => + getEmployeeTransactions(employee.id).map(transaction => ({ + ...transaction, + employeeName: employee.name, + employeeId: employee.id + })) + ).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + } else { + const employee = employees.find(emp => emp.id === selectedEmployeeId); + return getEmployeeTransactions(selectedEmployeeId).map(transaction => ({ + ...transaction, + employeeName: employee?.name || '', + employeeId: selectedEmployeeId + })); + } + }; + + const transactions = getTransactionsToShow(); + + return ( +
+
+

Filter by Employee

+ +
+ +
+ + + + Employee ID + Employee Name + Date + MM Collection + Deposit Amount + Difference + Running Balance + Status + + + + {transactions.map((transaction, index) => ( + + {transaction.employeeId} + +
+
+ + {transaction.employeeName.charAt(0)} + +
+ {transaction.employeeName} +
+
+ {formatDate(transaction.date)} + + {formatCurrency(transaction.collection)} + + + {formatCurrency(transaction.deposit)} + + + = 0 ? 'text-green-600' : 'text-red-600'}`}> + {formatCurrency(transaction.difference)} + + + + = 0 ? 'text-green-600' : 'text-red-600'}`}> + {formatCurrency(Math.abs(transaction.runningBalance))} + + + + = 0 ? "default" : "destructive"} + className={transaction.difference >= 0 ? "bg-green-100 text-green-800 hover:bg-green-200" : "bg-red-100 text-red-800 hover:bg-red-200"} + > + {transaction.difference >= 0 ? 'Balanced' : 'Deficit'} + + +
+ ))} +
+
+
+ + {transactions.length === 0 && ( +
+
No transaction data available
+
Use the "Insert Employee Data" button to add records
+
+ )} +
+ ); +}; diff --git a/src/components/OutstandingReportDashboard.tsx b/src/components/OutstandingReportDashboard.tsx new file mode 100644 index 0000000..70edd81 --- /dev/null +++ b/src/components/OutstandingReportDashboard.tsx @@ -0,0 +1,90 @@ + +import React from 'react'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Badge } from "@/components/ui/badge"; +import { useEmployeeData } from "@/hooks/useEmployeeData"; + +export const OutstandingReportDashboard = () => { + const { employees, getEmployeeSummary } = useEmployeeData(); + + const employeeSummaries = employees.map(employee => { + const summary = getEmployeeSummary(employee.id); + return { + ...employee, + ...summary + }; + }); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-IN', { + style: 'currency', + currency: 'INR' + }).format(amount); + }; + + const formatDate = (date: string) => { + return new Date(date).toLocaleDateString('en-IN'); + }; + + return ( +
+
+ + + + Employee ID + Employee Name + Net Collection Till Date + Most Recent Transaction + Outstanding Amount + Status + + + + {employeeSummaries.map((employee) => ( + + {employee.id} + +
+
+ + {employee.name.charAt(0)} + +
+ {employee.name} +
+
+ + {formatCurrency(employee.totalCollection)} + + + {employee.lastTransactionDate ? formatDate(employee.lastTransactionDate) : 'No transactions'} + + + 0 ? 'text-red-600' : 'text-green-600'}`}> + {formatCurrency(Math.abs(employee.outstandingAmount))} + + + + 0 ? "destructive" : "default"} + className={employee.outstandingAmount > 0 ? "bg-red-100 text-red-800 hover:bg-red-200" : "bg-green-100 text-green-800 hover:bg-green-200"} + > + {employee.outstandingAmount > 0 ? 'Pending' : 'Clear'} + + +
+ ))} +
+
+
+ + {employeeSummaries.length === 0 && ( +
+
No employee data available
+
Use the "Insert Employee Data" button to add records
+
+ )} +
+ ); +}; diff --git a/src/hooks/useEmployeeData.ts b/src/hooks/useEmployeeData.ts new file mode 100644 index 0000000..3710665 --- /dev/null +++ b/src/hooks/useEmployeeData.ts @@ -0,0 +1,175 @@ + +import { useState, useEffect } from 'react'; + +export interface Employee { + id: string; + name: string; + email: string; + department: string; +} + +export interface Transaction { + date: string; + collection: number; + deposit: number; +} + +export interface ProcessedTransaction extends Transaction { + difference: number; + runningBalance: number; +} + +export interface EmployeeSummary { + totalCollection: number; + totalDeposit: number; + outstandingAmount: number; + lastTransactionDate: string | null; +} + +const STORAGE_KEY = 'employeeCollectionData'; + +// Sample employee data +const initialEmployees: Employee[] = [ + { + id: 'EMP001', + name: 'Mayank Sharma', + email: 'mayank.sharma@company.com', + department: 'Collections' + }, + { + id: 'EMP002', + name: 'Priya Patel', + email: 'priya.patel@company.com', + department: 'Collections' + }, + { + id: 'EMP003', + name: 'Rajesh Kumar', + email: 'rajesh.kumar@company.com', + department: 'Collections' + }, + { + id: 'EMP004', + name: 'Anjali Singh', + email: 'anjali.singh@company.com', + department: 'Collections' + }, + { + id: 'EMP005', + name: 'Vikram Gupta', + email: 'vikram.gupta@company.com', + department: 'Collections' + } +]; + +// Sample transaction data for demonstration +const initialTransactions: Record = { + 'EMP001': [ + { date: '2025-03-26', collection: 10000, deposit: 0 }, + { date: '2025-03-27', collection: 20000, deposit: 0 }, + { date: '2025-03-28', collection: 0, deposit: 5000 }, + { date: '2025-03-29', collection: 0, deposit: 7000 }, + { date: '2025-03-30', collection: 0, deposit: 8000 }, + { date: '2025-03-31', collection: 0, deposit: 15000 } + ], + 'EMP002': [ + { date: '2025-03-25', collection: 15000, deposit: 0 }, + { date: '2025-03-26', collection: 12000, deposit: 15000 }, + { date: '2025-03-27', collection: 0, deposit: 12000 } + ], + 'EMP003': [ + { date: '2025-03-24', collection: 8000, deposit: 0 }, + { date: '2025-03-25', collection: 0, deposit: 8000 } + ] +}; + +export const useEmployeeData = () => { + const [employees] = useState(initialEmployees); + const [transactions, setTransactions] = useState>({}); + + // Load data from localStorage on mount + useEffect(() => { + const savedData = localStorage.getItem(STORAGE_KEY); + if (savedData) { + try { + const parsed = JSON.parse(savedData); + setTransactions(parsed); + } catch (error) { + console.error('Error loading saved data:', error); + setTransactions(initialTransactions); + } + } else { + setTransactions(initialTransactions); + } + }, []); + + // Save data to localStorage whenever transactions change + useEffect(() => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(transactions)); + }, [transactions]); + + const addTransaction = (employeeId: string, transaction: Omit) => { + setTransactions(prev => ({ + ...prev, + [employeeId]: [...(prev[employeeId] || []), transaction].sort((a, b) => + new Date(a.date).getTime() - new Date(b.date).getTime() + ) + })); + }; + + const processTransactions = (employeeTransactions: Transaction[]): ProcessedTransaction[] => { + let runningBalance = 0; + + return employeeTransactions.map(transaction => { + const difference = transaction.deposit - transaction.collection; + runningBalance += difference; + + return { + ...transaction, + difference, + runningBalance + }; + }); + }; + + const getEmployeeTransactions = (employeeId: string): ProcessedTransaction[] => { + const employeeTransactions = transactions[employeeId] || []; + return processTransactions(employeeTransactions); + }; + + const getEmployeeSummary = (employeeId: string): EmployeeSummary => { + const employeeTransactions = transactions[employeeId] || []; + + if (employeeTransactions.length === 0) { + return { + totalCollection: 0, + totalDeposit: 0, + outstandingAmount: 0, + lastTransactionDate: null + }; + } + + const totalCollection = employeeTransactions.reduce((sum, t) => sum + t.collection, 0); + const totalDeposit = employeeTransactions.reduce((sum, t) => sum + t.deposit, 0); + const outstandingAmount = totalCollection - totalDeposit; + + // Find the most recent transaction date + const lastTransactionDate = employeeTransactions + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())[0]?.date || null; + + return { + totalCollection, + totalDeposit, + outstandingAmount, + lastTransactionDate + }; + }; + + return { + employees, + transactions, + addTransaction, + getEmployeeTransactions, + getEmployeeSummary + }; +}; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 52ea22c..0b16b97 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,11 +1,88 @@ -// Update this page (the content is just a fallback if you fail to update the page) + +import React, { useState } from 'react'; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { OutstandingReportDashboard } from "@/components/OutstandingReportDashboard"; +import { EmployeePaymentReport } from "@/components/EmployeePaymentReport"; +import { AdminDataEntryModal } from "@/components/AdminDataEntryModal"; +import { Plus } from "lucide-react"; const Index = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [refreshTrigger, setRefreshTrigger] = useState(0); + + const handleDataUpdate = () => { + setRefreshTrigger(prev => prev + 1); + setIsModalOpen(false); + }; + return ( -
-
-

Welcome to Your Blank App

-

Start building your amazing project here!

+
+
+
+

+ Employee Collection Management +

+

+ Track and manage employee collections and deposits +

+
+ +
+ +
+ + + + + Outstanding Report Dashboard + + + Employee Payment Report + + + + + + + Outstanding Report Dashboard + + + + + + + + + + + Employee Payment Report Dashboard + + + + + + + + + setIsModalOpen(false)} + onDataUpdate={handleDataUpdate} + />
);