(Feat-Fix): Fixed the check-in, check-out system, added a contractor payment system.
This commit is contained in:
@@ -138,34 +138,34 @@ router.post(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify employee exists
|
// Verify user exists and is Employee or Contractor
|
||||||
let employeeQuery = "SELECT * FROM users WHERE id = ? AND role = ?";
|
let userQuery = "SELECT * FROM users WHERE id = ? AND role IN ('Employee', 'Contractor')";
|
||||||
const employeeParams: unknown[] = [employeeId, "Employee"];
|
const userParams: unknown[] = [employeeId];
|
||||||
|
|
||||||
if (currentUser.role === "Supervisor") {
|
if (currentUser.role === "Supervisor") {
|
||||||
employeeQuery += " AND department_id = ?";
|
userQuery += " AND department_id = ?";
|
||||||
employeeParams.push(currentUser.departmentId);
|
userParams.push(currentUser.departmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const employees = await db.query<User[]>(employeeQuery, employeeParams);
|
const users = await db.query<User[]>(userQuery, userParams);
|
||||||
|
|
||||||
if (employees.length === 0) {
|
if (users.length === 0) {
|
||||||
ctx.response.status = 403;
|
ctx.response.status = 403;
|
||||||
ctx.response.body = {
|
ctx.response.body = {
|
||||||
error: "Employee not found or not in your department",
|
error: "User not found, not an Employee/Contractor, or not in your department",
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if already checked in today
|
// Check if there's an active check-in (not yet checked out) for today
|
||||||
const existing = await db.query<Attendance[]>(
|
const activeCheckIn = await db.query<Attendance[]>(
|
||||||
"SELECT * FROM attendance WHERE employee_id = ? AND work_date = ? AND status = ?",
|
"SELECT * FROM attendance WHERE employee_id = ? AND work_date = ? AND status = 'CheckedIn'",
|
||||||
[employeeId, workDate, "CheckedIn"],
|
[employeeId, workDate],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existing.length > 0) {
|
if (activeCheckIn.length > 0) {
|
||||||
ctx.response.status = 400;
|
ctx.response.status = 400;
|
||||||
ctx.response.body = { error: "Employee already checked in today" };
|
ctx.response.body = { error: "User has an active check-in. Please check out first before checking in again." };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -221,9 +221,11 @@ router.post("/", authenticateToken, authorize("SuperAdmin"), async (ctx) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the employee's department and contractor
|
// Update the employee's department and contractor
|
||||||
|
// If no target contractor specified, keep the original contractor
|
||||||
|
const newContractorId = targetContractorId || employee.contractor_id || null;
|
||||||
const [updateResult] = await connection.execute(
|
const [updateResult] = await connection.execute(
|
||||||
"UPDATE users SET department_id = ?, contractor_id = ? WHERE id = ?",
|
"UPDATE users SET department_id = ?, contractor_id = ? WHERE id = ?",
|
||||||
[targetDepartmentId, targetContractorId || null, employeeId],
|
[targetDepartmentId, newContractorId, employeeId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const affectedRows =
|
const affectedRows =
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ReportingPage } from "./pages/ReportingPage.tsx";
|
|||||||
import { StandardRatesPage } from "./pages/StandardRatesPage.tsx";
|
import { StandardRatesPage } from "./pages/StandardRatesPage.tsx";
|
||||||
import { AllRatesPage } from "./pages/AllRatesPage.tsx";
|
import { AllRatesPage } from "./pages/AllRatesPage.tsx";
|
||||||
import { ActivitiesPage } from "./pages/ActivitiesPage.tsx";
|
import { ActivitiesPage } from "./pages/ActivitiesPage.tsx";
|
||||||
|
import { ContractorPaymentPage } from "./pages/ContractorPaymentPage.tsx";
|
||||||
|
|
||||||
type PageType =
|
type PageType =
|
||||||
| "dashboard"
|
| "dashboard"
|
||||||
@@ -25,7 +26,8 @@ type PageType =
|
|||||||
| "reports"
|
| "reports"
|
||||||
| "standard-rates"
|
| "standard-rates"
|
||||||
| "all-rates"
|
| "all-rates"
|
||||||
| "activities";
|
| "activities"
|
||||||
|
| "contractor-payment";
|
||||||
|
|
||||||
const AppContent: React.FC = () => {
|
const AppContent: React.FC = () => {
|
||||||
const [activePage, setActivePage] = useState<PageType>("dashboard");
|
const [activePage, setActivePage] = useState<PageType>("dashboard");
|
||||||
@@ -53,6 +55,8 @@ const AppContent: React.FC = () => {
|
|||||||
return <AllRatesPage />;
|
return <AllRatesPage />;
|
||||||
case "activities":
|
case "activities":
|
||||||
return <ActivitiesPage />;
|
return <ActivitiesPage />;
|
||||||
|
case "contractor-payment":
|
||||||
|
return <ContractorPaymentPage />;
|
||||||
default:
|
default:
|
||||||
return <DashboardPage />;
|
return <DashboardPage />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
Briefcase,
|
Briefcase,
|
||||||
CalendarCheck,
|
CalendarCheck,
|
||||||
ClipboardList,
|
ClipboardList,
|
||||||
|
CreditCard,
|
||||||
DollarSign,
|
DollarSign,
|
||||||
Eye,
|
Eye,
|
||||||
FileSpreadsheet,
|
FileSpreadsheet,
|
||||||
@@ -168,6 +169,16 @@ export const Sidebar: React.FC<SidebarProps> = ({ activePage, onNavigate }) => {
|
|||||||
onClick={() => onNavigate("activities")}
|
onClick={() => onNavigate("activities")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Contractor Payment Report - SuperAdmin only */}
|
||||||
|
{isSuperAdmin && (
|
||||||
|
<SidebarItem
|
||||||
|
icon={CreditCard}
|
||||||
|
label="Contractor Payment"
|
||||||
|
active={activePage === "contractor-payment"}
|
||||||
|
onClick={() => onNavigate("contractor-payment")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Role indicator at bottom */}
|
{/* Role indicator at bottom */}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export const AttendancePage: React.FC = () => {
|
|||||||
new Date().toISOString().split("T")[0],
|
new Date().toISOString().split("T")[0],
|
||||||
);
|
);
|
||||||
const [checkInLoading, setCheckInLoading] = useState(false);
|
const [checkInLoading, setCheckInLoading] = useState(false);
|
||||||
const [employeeStatus, setEmployeeStatus] = useState<any>(null);
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [sortField, setSortField] = useState<"date" | "employee" | "status">(
|
const [sortField, setSortField] = useState<"date" | "employee" | "status">(
|
||||||
"date",
|
"date",
|
||||||
@@ -72,30 +71,15 @@ export const AttendancePage: React.FC = () => {
|
|||||||
fetchAttendance();
|
fetchAttendance();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Check employee status when selected
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedEmployee && workDate) {
|
|
||||||
const record = attendance.find(
|
|
||||||
(a) =>
|
|
||||||
a.employee_id === parseInt(selectedEmployee) &&
|
|
||||||
a.work_date?.split("T")[0] === workDate,
|
|
||||||
);
|
|
||||||
setEmployeeStatus(record || null);
|
|
||||||
} else {
|
|
||||||
setEmployeeStatus(null);
|
|
||||||
}
|
|
||||||
}, [selectedEmployee, workDate, attendance]);
|
|
||||||
|
|
||||||
const handleCheckIn = async () => {
|
const handleCheckIn = async () => {
|
||||||
if (!selectedEmployee) {
|
if (!selectedEmployee) {
|
||||||
alert("Please select an employee");
|
alert("Please select an employee/contractor");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCheckInLoading(true);
|
setCheckInLoading(true);
|
||||||
try {
|
try {
|
||||||
await api.checkIn(parseInt(selectedEmployee), workDate);
|
await api.checkIn(parseInt(selectedEmployee), workDate);
|
||||||
await fetchAttendance();
|
await fetchAttendance();
|
||||||
setEmployeeStatus({ status: "CheckedIn" });
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
alert(err.message || "Failed to check in");
|
alert(err.message || "Failed to check in");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -105,14 +89,13 @@ export const AttendancePage: React.FC = () => {
|
|||||||
|
|
||||||
const handleCheckOut = async () => {
|
const handleCheckOut = async () => {
|
||||||
if (!selectedEmployee) {
|
if (!selectedEmployee) {
|
||||||
alert("Please select an employee");
|
alert("Please select an employee/contractor");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCheckInLoading(true);
|
setCheckInLoading(true);
|
||||||
try {
|
try {
|
||||||
await api.checkOut(parseInt(selectedEmployee), workDate);
|
await api.checkOut(parseInt(selectedEmployee), workDate);
|
||||||
await fetchAttendance();
|
await fetchAttendance();
|
||||||
setEmployeeStatus({ status: "CheckedOut" });
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
alert(err.message || "Failed to check out");
|
alert(err.message || "Failed to check out");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -122,7 +105,7 @@ export const AttendancePage: React.FC = () => {
|
|||||||
|
|
||||||
const handleMarkAbsent = async () => {
|
const handleMarkAbsent = async () => {
|
||||||
if (!selectedEmployee) {
|
if (!selectedEmployee) {
|
||||||
alert("Please select an employee");
|
alert("Please select an employee/contractor");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCheckInLoading(true);
|
setCheckInLoading(true);
|
||||||
@@ -133,7 +116,6 @@ export const AttendancePage: React.FC = () => {
|
|||||||
"Marked absent by supervisor",
|
"Marked absent by supervisor",
|
||||||
);
|
);
|
||||||
await fetchAttendance();
|
await fetchAttendance();
|
||||||
setEmployeeStatus({ status: "Absent" });
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
alert(err.message || "Failed to mark absent");
|
alert(err.message || "Failed to mark absent");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -166,14 +148,30 @@ export const AttendancePage: React.FC = () => {
|
|||||||
const canEditAttendance = user?.role === "SuperAdmin" ||
|
const canEditAttendance = user?.role === "SuperAdmin" ||
|
||||||
user?.role === "Supervisor";
|
user?.role === "Supervisor";
|
||||||
|
|
||||||
const employeeOptions = [
|
// Include both Employees and Contractors for attendance
|
||||||
{ value: "", label: "Select Employee" },
|
const attendanceUserOptions = [
|
||||||
...employees.filter((e) => e.role === "Employee").map((e) => ({
|
{ value: "", label: "Select Employee/Contractor" },
|
||||||
value: String(e.id),
|
...employees
|
||||||
label: `${e.name} (${e.username})`,
|
.filter((e) => e.role === "Employee" || e.role === "Contractor")
|
||||||
})),
|
.map((e) => ({
|
||||||
|
value: String(e.id),
|
||||||
|
label: `${e.name} (${e.role})`,
|
||||||
|
})),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Get all attendance records for selected user on selected date
|
||||||
|
const userDayRecords = useMemo(() => {
|
||||||
|
if (!selectedEmployee || !workDate) return [];
|
||||||
|
return attendance.filter(
|
||||||
|
(a) =>
|
||||||
|
a.employee_id === parseInt(selectedEmployee) &&
|
||||||
|
a.work_date?.split("T")[0] === workDate
|
||||||
|
);
|
||||||
|
}, [selectedEmployee, workDate, attendance]);
|
||||||
|
|
||||||
|
// Check if user has an active (not checked out) session
|
||||||
|
const hasActiveCheckIn = userDayRecords.some((r) => r.status === "CheckedIn");
|
||||||
|
|
||||||
// Filter and sort attendance records
|
// Filter and sort attendance records
|
||||||
const filteredAndSortedAttendance = useMemo(() => {
|
const filteredAndSortedAttendance = useMemo(() => {
|
||||||
let filtered = attendance;
|
let filtered = attendance;
|
||||||
@@ -465,16 +463,16 @@ export const AttendancePage: React.FC = () => {
|
|||||||
Check In / Check Out Management
|
Check In / Check Out Management
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-gray-600 mb-6">
|
<p className="text-sm text-gray-600 mb-6">
|
||||||
Manage employee attendance
|
Manage employee and contractor attendance. Multiple check-ins per day are supported.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="grid grid-cols-2 gap-6">
|
<div className="grid grid-cols-2 gap-6">
|
||||||
<Select
|
<Select
|
||||||
label="Select Employee"
|
label="Select Employee/Contractor"
|
||||||
value={selectedEmployee}
|
value={selectedEmployee}
|
||||||
onChange={(e) => setSelectedEmployee(e.target.value)}
|
onChange={(e) => setSelectedEmployee(e.target.value)}
|
||||||
options={employeeOptions}
|
options={attendanceUserOptions}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
label="Work Date"
|
label="Work Date"
|
||||||
@@ -485,55 +483,91 @@ export const AttendancePage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedEmployee && (
|
{selectedEmployee && (
|
||||||
<div
|
<div className="space-y-3">
|
||||||
className={`border rounded-md p-4 flex items-start ${
|
{/* Current Status */}
|
||||||
employeeStatus?.status === "CheckedIn"
|
<div
|
||||||
? "bg-blue-50 border-blue-200"
|
className={`border rounded-md p-4 flex items-start ${
|
||||||
: employeeStatus?.status === "CheckedOut"
|
hasActiveCheckIn
|
||||||
? "bg-green-50 border-green-200"
|
? "bg-blue-50 border-blue-200"
|
||||||
: "bg-yellow-50 border-yellow-200"
|
: userDayRecords.length > 0
|
||||||
}`}
|
? "bg-green-50 border-green-200"
|
||||||
>
|
: "bg-yellow-50 border-yellow-200"
|
||||||
{employeeStatus?.status === "CheckedIn"
|
}`}
|
||||||
? (
|
>
|
||||||
|
{hasActiveCheckIn ? (
|
||||||
<>
|
<>
|
||||||
<Clock
|
<Clock
|
||||||
size={20}
|
size={20}
|
||||||
className="text-blue-600 mr-2 flex-shrink-0 mt-0.5"
|
className="text-blue-600 mr-2 flex-shrink-0 mt-0.5"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-blue-800">
|
<p className="text-sm text-blue-800">
|
||||||
Employee is currently checked in. Check-in time:
|
Currently checked in. Please check out before checking in again.
|
||||||
{" "}
|
|
||||||
{employeeStatus.check_in_time
|
|
||||||
? new Date(employeeStatus.check_in_time)
|
|
||||||
.toLocaleTimeString()
|
|
||||||
: "N/A"}
|
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)
|
) : userDayRecords.length > 0 ? (
|
||||||
: employeeStatus?.status === "CheckedOut"
|
|
||||||
? (
|
|
||||||
<>
|
<>
|
||||||
<CheckCircle
|
<CheckCircle
|
||||||
size={20}
|
size={20}
|
||||||
className="text-green-600 mr-2 flex-shrink-0 mt-0.5"
|
className="text-green-600 mr-2 flex-shrink-0 mt-0.5"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-green-800">
|
<p className="text-sm text-green-800">
|
||||||
Employee has completed attendance for this date.
|
{userDayRecords.length} attendance record(s) for this date. Can check in again.
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)
|
) : (
|
||||||
: (
|
|
||||||
<>
|
<>
|
||||||
<AlertTriangle
|
<AlertTriangle
|
||||||
size={20}
|
size={20}
|
||||||
className="text-yellow-600 mr-2 flex-shrink-0 mt-0.5"
|
className="text-yellow-600 mr-2 flex-shrink-0 mt-0.5"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-yellow-800">
|
<p className="text-sm text-yellow-800">
|
||||||
Employee has not checked in for this date
|
No attendance records for this date
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Today's Records */}
|
||||||
|
{userDayRecords.length > 0 && (
|
||||||
|
<div className="bg-gray-50 rounded-md p-3">
|
||||||
|
<h4 className="text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Today's Sessions ({userDayRecords.length})
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{userDayRecords.map((record, idx) => (
|
||||||
|
<div
|
||||||
|
key={record.id}
|
||||||
|
className="flex items-center justify-between text-sm bg-white rounded px-3 py-2 border"
|
||||||
|
>
|
||||||
|
<span className="text-gray-600">Session {idx + 1}</span>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span className="text-gray-500">
|
||||||
|
In: {record.check_in_time
|
||||||
|
? new Date(record.check_in_time).toLocaleTimeString()
|
||||||
|
: "-"}
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-500">
|
||||||
|
Out: {record.check_out_time
|
||||||
|
? new Date(record.check_out_time).toLocaleTimeString()
|
||||||
|
: "-"}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`px-2 py-0.5 rounded text-xs font-medium ${
|
||||||
|
record.status === "CheckedOut"
|
||||||
|
? "bg-green-100 text-green-700"
|
||||||
|
: record.status === "CheckedIn"
|
||||||
|
? "bg-blue-100 text-blue-700"
|
||||||
|
: "bg-gray-100 text-gray-700"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{record.status === "CheckedOut" ? "Completed" : record.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -541,20 +575,16 @@ export const AttendancePage: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
onClick={handleCheckIn}
|
onClick={handleCheckIn}
|
||||||
disabled={checkInLoading || !selectedEmployee ||
|
disabled={checkInLoading || !selectedEmployee || hasActiveCheckIn}
|
||||||
employeeStatus?.status === "CheckedIn" ||
|
|
||||||
employeeStatus?.status === "CheckedOut" ||
|
|
||||||
employeeStatus?.status === "Absent"}
|
|
||||||
>
|
>
|
||||||
<LogIn size={16} className="mr-2" />
|
<LogIn size={16} className="mr-2" />
|
||||||
{checkInLoading ? "Processing..." : "Check In"}
|
{checkInLoading ? "Processing..." : "Check In"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
onClick={handleCheckOut}
|
onClick={handleCheckOut}
|
||||||
disabled={checkInLoading || !selectedEmployee ||
|
disabled={checkInLoading || !selectedEmployee || !hasActiveCheckIn}
|
||||||
employeeStatus?.status !== "CheckedIn"}
|
|
||||||
>
|
>
|
||||||
<LogOut size={16} className="mr-2" />
|
<LogOut size={16} className="mr-2" />
|
||||||
{checkInLoading ? "Processing..." : "Check Out"}
|
{checkInLoading ? "Processing..." : "Check Out"}
|
||||||
@@ -563,10 +593,7 @@ export const AttendancePage: React.FC = () => {
|
|||||||
size="lg"
|
size="lg"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
onClick={handleMarkAbsent}
|
onClick={handleMarkAbsent}
|
||||||
disabled={checkInLoading || !selectedEmployee ||
|
disabled={checkInLoading || !selectedEmployee || hasActiveCheckIn || userDayRecords.length > 0}
|
||||||
employeeStatus?.status === "CheckedIn" ||
|
|
||||||
employeeStatus?.status === "CheckedOut" ||
|
|
||||||
employeeStatus?.status === "Absent"}
|
|
||||||
>
|
>
|
||||||
<UserX size={16} className="mr-2" />
|
<UserX size={16} className="mr-2" />
|
||||||
{checkInLoading ? "Processing..." : "Mark Absent"}
|
{checkInLoading ? "Processing..." : "Mark Absent"}
|
||||||
|
|||||||
576
src/pages/ContractorPaymentPage.tsx
Normal file
576
src/pages/ContractorPaymentPage.tsx
Normal file
@@ -0,0 +1,576 @@
|
|||||||
|
import React, { useMemo, useState } from "react";
|
||||||
|
import { Download, FileSpreadsheet, Printer, RefreshCw } from "lucide-react";
|
||||||
|
import { Card, CardContent } from "../components/ui/Card.tsx";
|
||||||
|
import { Button } from "../components/ui/Button.tsx";
|
||||||
|
import { api } from "../services/api.ts";
|
||||||
|
import { useDepartments } from "../hooks/useDepartments.ts";
|
||||||
|
import * as XLSX from "xlsx";
|
||||||
|
|
||||||
|
interface ContractorPaymentData {
|
||||||
|
contractor_id: number;
|
||||||
|
contractor_name: string;
|
||||||
|
as_per_contractor: number;
|
||||||
|
dana: number;
|
||||||
|
tukdi: number;
|
||||||
|
groundnut: number;
|
||||||
|
commission_salary: number;
|
||||||
|
total: number;
|
||||||
|
tds_base_amount: number;
|
||||||
|
payable_before_deduction: number;
|
||||||
|
security_deduction: number;
|
||||||
|
advance: number;
|
||||||
|
final_payable: number;
|
||||||
|
excess_short: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubDepartmentTotals {
|
||||||
|
sub_department_id: number;
|
||||||
|
sub_department_name: string;
|
||||||
|
contractors: ContractorPaymentData[];
|
||||||
|
subTotal: ContractorPaymentData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContractorPaymentPage: React.FC = () => {
|
||||||
|
const { departments } = useDepartments();
|
||||||
|
const [selectedDepartment, setSelectedDepartment] = useState<string>("");
|
||||||
|
const [startDate, setStartDate] = useState(() => {
|
||||||
|
const date = new Date();
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-01`;
|
||||||
|
});
|
||||||
|
const [endDate, setEndDate] = useState(() => {
|
||||||
|
const date = new Date();
|
||||||
|
return date.toISOString().split("T")[0];
|
||||||
|
});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [reportData, setReportData] = useState<SubDepartmentTotals[]>([]);
|
||||||
|
const [companyName, setCompanyName] = useState("DUSAD AGROFOOD PVT LTD");
|
||||||
|
|
||||||
|
// Fetch report data
|
||||||
|
const fetchReport = async () => {
|
||||||
|
if (!selectedDepartment) {
|
||||||
|
alert("Please select a department");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// Fetch completed allocations for the date range
|
||||||
|
const params: Record<string, any> = {
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
departmentId: parseInt(selectedDepartment),
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await api.getCompletedAllocationsReport(params);
|
||||||
|
|
||||||
|
// Process data to group by sub-department and contractor
|
||||||
|
const processedData = processReportData(data.allocations);
|
||||||
|
setReportData(processedData);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Failed to fetch report:", err);
|
||||||
|
alert(err.message || "Failed to fetch report");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process raw allocation data into the payment report format
|
||||||
|
const processReportData = (allocations: any[]): SubDepartmentTotals[] => {
|
||||||
|
const subDeptMap = new Map<number, Map<number, ContractorPaymentData>>();
|
||||||
|
const subDeptNames = new Map<number, string>();
|
||||||
|
|
||||||
|
allocations.forEach((alloc) => {
|
||||||
|
const subDeptId = alloc.sub_department_id || 0;
|
||||||
|
const contractorId = alloc.contractor_id || 0;
|
||||||
|
const subDeptName = alloc.sub_department_name || "Other";
|
||||||
|
|
||||||
|
subDeptNames.set(subDeptId, subDeptName);
|
||||||
|
|
||||||
|
if (!subDeptMap.has(subDeptId)) {
|
||||||
|
subDeptMap.set(subDeptId, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
const contractorMap = subDeptMap.get(subDeptId)!;
|
||||||
|
|
||||||
|
if (!contractorMap.has(contractorId)) {
|
||||||
|
contractorMap.set(contractorId, {
|
||||||
|
contractor_id: contractorId,
|
||||||
|
contractor_name: alloc.contractor_name || "Unknown",
|
||||||
|
as_per_contractor: 0,
|
||||||
|
dana: 0,
|
||||||
|
tukdi: 0,
|
||||||
|
groundnut: 0,
|
||||||
|
commission_salary: 0,
|
||||||
|
total: 0,
|
||||||
|
tds_base_amount: 0,
|
||||||
|
payable_before_deduction: 0,
|
||||||
|
security_deduction: 0,
|
||||||
|
advance: 0,
|
||||||
|
final_payable: 0,
|
||||||
|
excess_short: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const contractor = contractorMap.get(contractorId)!;
|
||||||
|
const amount = parseFloat(alloc.total_amount) || parseFloat(alloc.rate) || 0;
|
||||||
|
const activity = (alloc.activity || "").toLowerCase();
|
||||||
|
|
||||||
|
// Categorize by activity type
|
||||||
|
if (activity.includes("dana")) {
|
||||||
|
contractor.dana += amount;
|
||||||
|
} else if (activity.includes("tukdi")) {
|
||||||
|
contractor.tukdi += amount;
|
||||||
|
} else if (activity.includes("groundnut")) {
|
||||||
|
contractor.groundnut += amount;
|
||||||
|
} else if (activity.includes("commission") || activity.includes("salary")) {
|
||||||
|
contractor.commission_salary += amount;
|
||||||
|
} else {
|
||||||
|
contractor.as_per_contractor += amount;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate totals for each contractor
|
||||||
|
const result: SubDepartmentTotals[] = [];
|
||||||
|
|
||||||
|
subDeptMap.forEach((contractorMap, subDeptId) => {
|
||||||
|
const contractors: ContractorPaymentData[] = [];
|
||||||
|
const subTotal: ContractorPaymentData = {
|
||||||
|
contractor_id: 0,
|
||||||
|
contractor_name: "Sub Total",
|
||||||
|
as_per_contractor: 0,
|
||||||
|
dana: 0,
|
||||||
|
tukdi: 0,
|
||||||
|
groundnut: 0,
|
||||||
|
commission_salary: 0,
|
||||||
|
total: 0,
|
||||||
|
tds_base_amount: 0,
|
||||||
|
payable_before_deduction: 0,
|
||||||
|
security_deduction: 0,
|
||||||
|
advance: 0,
|
||||||
|
final_payable: 0,
|
||||||
|
excess_short: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
contractorMap.forEach((contractor) => {
|
||||||
|
// Calculate derived values
|
||||||
|
contractor.total = contractor.as_per_contractor + contractor.dana +
|
||||||
|
contractor.tukdi + contractor.groundnut + contractor.commission_salary;
|
||||||
|
contractor.tds_base_amount = Math.round(contractor.total * 0.01); // 1% TDS
|
||||||
|
contractor.payable_before_deduction = contractor.total - contractor.tds_base_amount;
|
||||||
|
contractor.security_deduction = Math.round(contractor.total * 0.0035); // 0.35% security
|
||||||
|
contractor.final_payable = contractor.payable_before_deduction -
|
||||||
|
contractor.security_deduction - contractor.advance;
|
||||||
|
contractor.excess_short = contractor.as_per_contractor - contractor.final_payable;
|
||||||
|
|
||||||
|
// Add to subtotal
|
||||||
|
subTotal.as_per_contractor += contractor.as_per_contractor;
|
||||||
|
subTotal.dana += contractor.dana;
|
||||||
|
subTotal.tukdi += contractor.tukdi;
|
||||||
|
subTotal.groundnut += contractor.groundnut;
|
||||||
|
subTotal.commission_salary += contractor.commission_salary;
|
||||||
|
subTotal.total += contractor.total;
|
||||||
|
subTotal.tds_base_amount += contractor.tds_base_amount;
|
||||||
|
subTotal.payable_before_deduction += contractor.payable_before_deduction;
|
||||||
|
subTotal.security_deduction += contractor.security_deduction;
|
||||||
|
subTotal.advance += contractor.advance;
|
||||||
|
subTotal.final_payable += contractor.final_payable;
|
||||||
|
subTotal.excess_short += contractor.excess_short;
|
||||||
|
|
||||||
|
contractors.push(contractor);
|
||||||
|
});
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
sub_department_id: subDeptId,
|
||||||
|
sub_department_name: subDeptNames.get(subDeptId) || "Other",
|
||||||
|
contractors,
|
||||||
|
subTotal,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format date for display
|
||||||
|
const formatDateRange = () => {
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const end = new Date(endDate);
|
||||||
|
return `${start.toLocaleDateString("en-GB", { day: "2-digit", month: "2-digit", year: "numeric" }).replace(/\//g, ".")} TO ${end.toLocaleDateString("en-GB", { day: "2-digit", month: "2-digit", year: "numeric" }).replace(/\//g, ".")}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export to Excel
|
||||||
|
const exportToExcel = () => {
|
||||||
|
if (reportData.length === 0) {
|
||||||
|
alert("No data to export");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
const wsData: any[][] = [];
|
||||||
|
|
||||||
|
// Header rows
|
||||||
|
wsData.push([companyName]);
|
||||||
|
wsData.push([formatDateRange()]);
|
||||||
|
wsData.push(["ALL CONTRACTOR PAYMENT SHEET"]);
|
||||||
|
wsData.push([]);
|
||||||
|
|
||||||
|
// Column headers
|
||||||
|
wsData.push([
|
||||||
|
"S.NO", "NAME CONTRACTOR", "As per Contractor", "", "", "", "", "",
|
||||||
|
"TDS @1%", "Payable", "SECURITY", "ADVANCE", "Final Payable", "Excess/Short"
|
||||||
|
]);
|
||||||
|
wsData.push([
|
||||||
|
"", "", "ALL", "DANA", "TUKDI", "GROUNDNUT", "Commission & Salary", "Total",
|
||||||
|
"(BASE AMOUNT)", "(Before deduction)", "DEDUCTION 0.35%", "", "", ""
|
||||||
|
]);
|
||||||
|
|
||||||
|
let serialNo = 1;
|
||||||
|
|
||||||
|
reportData.forEach((subDept) => {
|
||||||
|
// Sub-department header
|
||||||
|
wsData.push([subDept.sub_department_name.toUpperCase()]);
|
||||||
|
|
||||||
|
subDept.contractors.forEach((contractor) => {
|
||||||
|
wsData.push([
|
||||||
|
serialNo++,
|
||||||
|
contractor.contractor_name,
|
||||||
|
contractor.as_per_contractor || "",
|
||||||
|
contractor.dana || "",
|
||||||
|
contractor.tukdi || "",
|
||||||
|
contractor.groundnut || "",
|
||||||
|
contractor.commission_salary || "",
|
||||||
|
contractor.total || "",
|
||||||
|
contractor.tds_base_amount || "",
|
||||||
|
contractor.payable_before_deduction || "",
|
||||||
|
contractor.security_deduction || "",
|
||||||
|
contractor.advance || "",
|
||||||
|
contractor.final_payable || "",
|
||||||
|
contractor.excess_short || "",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sub Total row
|
||||||
|
wsData.push([
|
||||||
|
"",
|
||||||
|
"Sub Total",
|
||||||
|
subDept.subTotal.as_per_contractor,
|
||||||
|
subDept.subTotal.dana,
|
||||||
|
subDept.subTotal.tukdi,
|
||||||
|
subDept.subTotal.groundnut,
|
||||||
|
subDept.subTotal.commission_salary,
|
||||||
|
subDept.subTotal.total,
|
||||||
|
subDept.subTotal.tds_base_amount,
|
||||||
|
subDept.subTotal.payable_before_deduction,
|
||||||
|
subDept.subTotal.security_deduction,
|
||||||
|
subDept.subTotal.advance,
|
||||||
|
subDept.subTotal.final_payable,
|
||||||
|
subDept.subTotal.excess_short,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
wsData.push([]);
|
||||||
|
wsData.push(["CONTRACTOR", "", "", "", "", "", "CHECKER", "", "", "", "", "", "AUTHORISED AUTHORITY"]);
|
||||||
|
|
||||||
|
const ws = XLSX.utils.aoa_to_sheet(wsData);
|
||||||
|
|
||||||
|
// Set column widths
|
||||||
|
ws["!cols"] = [
|
||||||
|
{ wch: 5 }, // S.NO
|
||||||
|
{ wch: 30 }, // NAME CONTRACTOR
|
||||||
|
{ wch: 12 }, // ALL
|
||||||
|
{ wch: 10 }, // DANA
|
||||||
|
{ wch: 10 }, // TUKDI
|
||||||
|
{ wch: 12 }, // GROUNDNUT
|
||||||
|
{ wch: 14 }, // Commission
|
||||||
|
{ wch: 12 }, // Total
|
||||||
|
{ wch: 12 }, // TDS
|
||||||
|
{ wch: 12 }, // Payable
|
||||||
|
{ wch: 12 }, // Security
|
||||||
|
{ wch: 10 }, // Advance
|
||||||
|
{ wch: 14 }, // Final Payable
|
||||||
|
{ wch: 12 }, // Excess/Short
|
||||||
|
];
|
||||||
|
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, "Payment Report");
|
||||||
|
|
||||||
|
const filename = `contractor_payment_${startDate}_to_${endDate}.xlsx`;
|
||||||
|
XLSX.writeFile(wb, filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Print report
|
||||||
|
const printReport = () => {
|
||||||
|
window.print();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate grand totals
|
||||||
|
const grandTotal = useMemo(() => {
|
||||||
|
const total: ContractorPaymentData = {
|
||||||
|
contractor_id: 0,
|
||||||
|
contractor_name: "Grand Total",
|
||||||
|
as_per_contractor: 0,
|
||||||
|
dana: 0,
|
||||||
|
tukdi: 0,
|
||||||
|
groundnut: 0,
|
||||||
|
commission_salary: 0,
|
||||||
|
total: 0,
|
||||||
|
tds_base_amount: 0,
|
||||||
|
payable_before_deduction: 0,
|
||||||
|
security_deduction: 0,
|
||||||
|
advance: 0,
|
||||||
|
final_payable: 0,
|
||||||
|
excess_short: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
reportData.forEach((subDept) => {
|
||||||
|
total.as_per_contractor += subDept.subTotal.as_per_contractor;
|
||||||
|
total.dana += subDept.subTotal.dana;
|
||||||
|
total.tukdi += subDept.subTotal.tukdi;
|
||||||
|
total.groundnut += subDept.subTotal.groundnut;
|
||||||
|
total.commission_salary += subDept.subTotal.commission_salary;
|
||||||
|
total.total += subDept.subTotal.total;
|
||||||
|
total.tds_base_amount += subDept.subTotal.tds_base_amount;
|
||||||
|
total.payable_before_deduction += subDept.subTotal.payable_before_deduction;
|
||||||
|
total.security_deduction += subDept.subTotal.security_deduction;
|
||||||
|
total.advance += subDept.subTotal.advance;
|
||||||
|
total.final_payable += subDept.subTotal.final_payable;
|
||||||
|
total.excess_short += subDept.subTotal.excess_short;
|
||||||
|
});
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}, [reportData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6">
|
||||||
|
<Card>
|
||||||
|
<div className="border-b border-gray-200 px-6 py-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<FileSpreadsheet className="text-green-600" size={24} />
|
||||||
|
<h2 className="text-xl font-semibold text-gray-800">
|
||||||
|
Contractor Payment Report
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="secondary" onClick={printReport} disabled={reportData.length === 0}>
|
||||||
|
<Printer size={16} className="mr-2" />
|
||||||
|
Print
|
||||||
|
</Button>
|
||||||
|
<Button onClick={exportToExcel} disabled={reportData.length === 0}>
|
||||||
|
<Download size={16} className="mr-2" />
|
||||||
|
Export to Excel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
{/* Filters */}
|
||||||
|
<div className="mb-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
|
||||||
|
<div className="grid grid-cols-4 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Company Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={companyName}
|
||||||
|
onChange={(e) => setCompanyName(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Department</label>
|
||||||
|
<select
|
||||||
|
value={selectedDepartment}
|
||||||
|
onChange={(e) => setSelectedDepartment(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
<option value="">Select Department</option>
|
||||||
|
{departments.map((dept) => (
|
||||||
|
<option key={dept.id} value={dept.id}>{dept.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Start Date</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={startDate}
|
||||||
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">End Date</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={endDate}
|
||||||
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<Button onClick={fetchReport} disabled={loading}>
|
||||||
|
<RefreshCw size={16} className={`mr-2 ${loading ? "animate-spin" : ""}`} />
|
||||||
|
{loading ? "Generating..." : "Generate Report"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Report Table */}
|
||||||
|
{reportData.length > 0 && (
|
||||||
|
<div className="overflow-x-auto print:overflow-visible" id="payment-report">
|
||||||
|
<div className="min-w-[1200px]">
|
||||||
|
{/* Report Header */}
|
||||||
|
<div className="text-center mb-4 bg-yellow-300 py-2">
|
||||||
|
<h1 className="text-lg font-bold text-black">{companyName}</h1>
|
||||||
|
<p className="text-sm font-semibold">{formatDateRange()}</p>
|
||||||
|
<p className="text-base font-bold">ALL CONTRACTOR PAYMENT SHEET</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Table */}
|
||||||
|
<table className="w-full border-collapse text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-yellow-300">
|
||||||
|
<th rowSpan={2} className="border border-black px-2 py-1 text-left">S.NO</th>
|
||||||
|
<th rowSpan={2} className="border border-black px-2 py-1 text-left">NAME CONTRACTOR</th>
|
||||||
|
<th colSpan={6} className="border border-black px-2 py-1 text-center">As per DAFPL</th>
|
||||||
|
<th rowSpan={2} className="border border-black px-2 py-1 text-center">TDS @1%<br/>(BASE AMOUNT)</th>
|
||||||
|
<th rowSpan={2} className="border border-black px-2 py-1 text-center">Payable<br/>(Before deduction)</th>
|
||||||
|
<th rowSpan={2} className="border border-black px-2 py-1 text-center">SECURITY<br/>DEDUCTION 0.35%</th>
|
||||||
|
<th rowSpan={2} className="border border-black px-2 py-1 text-center">ADVANCE</th>
|
||||||
|
<th rowSpan={2} className="border border-black px-2 py-1 text-center">Final Payable</th>
|
||||||
|
<th rowSpan={2} className="border border-black px-2 py-1 text-center">Excess/Short</th>
|
||||||
|
</tr>
|
||||||
|
<tr className="bg-yellow-300">
|
||||||
|
<th className="border border-black px-2 py-1 text-center">ALL</th>
|
||||||
|
<th className="border border-black px-2 py-1 text-center">DANA</th>
|
||||||
|
<th className="border border-black px-2 py-1 text-center">TUKDI</th>
|
||||||
|
<th className="border border-black px-2 py-1 text-center">GROUNDNUT</th>
|
||||||
|
<th className="border border-black px-2 py-1 text-center">Commission<br/>& Salary</th>
|
||||||
|
<th className="border border-black px-2 py-1 text-center">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{reportData.map((subDept, subDeptIdx) => (
|
||||||
|
<React.Fragment key={subDept.sub_department_id}>
|
||||||
|
{/* Sub-department header */}
|
||||||
|
<tr className="bg-yellow-300">
|
||||||
|
<td colSpan={14} className="border border-black px-2 py-1 font-bold">
|
||||||
|
{subDept.sub_department_name.toUpperCase()}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{/* Contractors */}
|
||||||
|
{subDept.contractors.map((contractor, idx) => {
|
||||||
|
const rowNum = reportData
|
||||||
|
.slice(0, subDeptIdx)
|
||||||
|
.reduce((acc, sd) => acc + sd.contractors.length, 0) + idx + 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={contractor.contractor_id} className="hover:bg-gray-50">
|
||||||
|
<td className="border border-black px-2 py-1">{rowNum}</td>
|
||||||
|
<td className="border border-black px-2 py-1 font-medium">{contractor.contractor_name}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.as_per_contractor || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.dana || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.tukdi || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.groundnut || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.commission_salary || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.total || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.tds_base_amount || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.payable_before_deduction || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.security_deduction || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.advance || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.final_payable || ""}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{contractor.excess_short || ""}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Sub Total */}
|
||||||
|
<tr className="bg-yellow-300 font-bold">
|
||||||
|
<td className="border border-black px-2 py-1"></td>
|
||||||
|
<td className="border border-black px-2 py-1">Sub Total</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.as_per_contractor}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.dana}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.tukdi}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.groundnut}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.commission_salary}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.total}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.tds_base_amount}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.payable_before_deduction}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.security_deduction}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.advance}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.final_payable}</td>
|
||||||
|
<td className="border border-black px-2 py-1 text-right">{subDept.subTotal.excess_short}</td>
|
||||||
|
</tr>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Grand Total */}
|
||||||
|
<tr className="bg-yellow-400 font-bold text-base">
|
||||||
|
<td className="border border-black px-2 py-2"></td>
|
||||||
|
<td className="border border-black px-2 py-2">GRAND TOTAL</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.as_per_contractor}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.dana}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.tukdi}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.groundnut}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.commission_salary}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.total}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.tds_base_amount}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.payable_before_deduction}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.security_deduction}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.advance}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.final_payable}</td>
|
||||||
|
<td className="border border-black px-2 py-2 text-right">{grandTotal.excess_short}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="mt-8 flex justify-between text-sm font-medium border-t border-black pt-4">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="border-t border-black w-40 pt-2">CONTRACTOR</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="border-t border-black w-40 pt-2">CHECKER</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="border-t border-black w-48 pt-2">AUTHORISED AUTHORITY</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{reportData.length === 0 && !loading && (
|
||||||
|
<div className="text-center py-12 text-gray-500">
|
||||||
|
<FileSpreadsheet size={48} className="mx-auto mb-4 text-gray-300" />
|
||||||
|
<p>Select a department and date range, then click "Generate Report"</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Print Styles */}
|
||||||
|
<style>{`
|
||||||
|
@media print {
|
||||||
|
body * {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
#payment-report, #payment-report * {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
#payment-report {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.no-print {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -571,7 +571,9 @@ export const EmployeeSwapPage: React.FC = () => {
|
|||||||
targetContractorId: e.target.value,
|
targetContractorId: e.target.value,
|
||||||
})}
|
})}
|
||||||
options={[
|
options={[
|
||||||
{ value: "", label: "No contractor" },
|
{ value: "", label: selectedEmployee?.contractor_name
|
||||||
|
? `Keep Original (${selectedEmployee.contractor_name})`
|
||||||
|
: "Keep Original Contractor" },
|
||||||
...targetContractors.map((c) => ({
|
...targetContractors.map((c) => ({
|
||||||
value: String(c.id),
|
value: String(c.id),
|
||||||
label: c.name,
|
label: c.name,
|
||||||
@@ -669,7 +671,7 @@ export const EmployeeSwapPage: React.FC = () => {
|
|||||||
<div className="flex justify-end gap-3 pt-4">
|
<div className="flex justify-end gap-3 pt-4">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
onClick={() => setActiveTab("list")}
|
onClick={() => setActiveTab("list")}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
Reference in New Issue
Block a user