(Feat-Fix): Added unit display and completion in work allocation system, and ability to edit completed units. Fixed contractor reporting system not displaying data under corresponding departments.
This commit is contained in:
@@ -6,7 +6,7 @@ DB_NAME=work_allocation
|
||||
DB_PORT=3307
|
||||
|
||||
# JWT Configuration - CHANGE IN PRODUCTION!
|
||||
JWT_SECRET=work_alloc_jwt_secret_key_change_in_production_2024
|
||||
JWT_SECRET=VwoPOwDth7aqCSlrbf/qYYY0upLu0kRuTcuM71ionJI=
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
# Server Configuration
|
||||
|
||||
@@ -9,11 +9,27 @@
|
||||
"@std/dotenv": "jsr:@std/dotenv@^0.225.3",
|
||||
"mysql2": "npm:mysql2@^3.11.0",
|
||||
"bcrypt": "https://deno.land/x/bcrypt@v0.4.1/mod.ts",
|
||||
"djwt": "https://deno.land/x/djwt@v3.0.2/mod.ts"
|
||||
"djwt": "https://deno.land/x/djwt@v3.0.2/mod.ts",
|
||||
"react": "https://esm.sh/react@18",
|
||||
"react-dom": "https://esm.sh/react-dom@18"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
"strictNullChecks": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react"
|
||||
},
|
||||
"lint": {
|
||||
"rules": {
|
||||
"tags": ["recommended"]
|
||||
}
|
||||
},
|
||||
"fmt": {
|
||||
"useTabs": false,
|
||||
"lineWidth": 100,
|
||||
"indentWidth": 2,
|
||||
"semiColons": false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,9 +69,9 @@ router.get(
|
||||
queryParams.push(endDate);
|
||||
}
|
||||
|
||||
// Department filter (for SuperAdmin)
|
||||
if (departmentId && currentUser.role === "SuperAdmin") {
|
||||
query += " AND e.department_id = ?";
|
||||
// Department filter - filter by sub-department's department
|
||||
if (departmentId) {
|
||||
query += " AND sd.department_id = ?";
|
||||
queryParams.push(departmentId);
|
||||
}
|
||||
|
||||
|
||||
@@ -304,6 +304,91 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Update work allocation units (Supervisor, Contractor, or SuperAdmin)
|
||||
router.put(
|
||||
"/:id/units",
|
||||
authenticateToken,
|
||||
authorize("Supervisor", "Contractor", "SuperAdmin"),
|
||||
async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const allocationId = ctx.params.id;
|
||||
const body = await ctx.request.body.json() as {
|
||||
units?: number;
|
||||
completedUnits?: number;
|
||||
markComplete?: boolean;
|
||||
};
|
||||
const { units, completedUnits, markComplete } = body;
|
||||
|
||||
if (units === undefined && completedUnits === undefined) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = { error: "Units or completedUnits required" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify allocation exists and user has access
|
||||
let query = "SELECT * FROM work_allocations WHERE id = ?";
|
||||
const params: unknown[] = [allocationId];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
query += " AND supervisor_id = ?";
|
||||
params.push(currentUser.id);
|
||||
} else if (currentUser.role === "Contractor") {
|
||||
query += " AND contractor_id = ?";
|
||||
params.push(currentUser.id);
|
||||
}
|
||||
|
||||
const allocations = await db.query<WorkAllocation[]>(query, params);
|
||||
|
||||
if (allocations.length === 0) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = {
|
||||
error: "Work allocation not found or access denied",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const allocation = allocations[0];
|
||||
const newUnits = units !== undefined ? units : allocation.units;
|
||||
const newCompletedUnits = completedUnits !== undefined ? completedUnits : (allocation as any).completed_units || 0;
|
||||
const rate = allocation.rate || 0;
|
||||
const newTotalAmount = newCompletedUnits * rate;
|
||||
|
||||
// Determine status: mark as Completed if markComplete is true
|
||||
const newStatus = markComplete ? "Completed" : allocation.status;
|
||||
const completionDate = markComplete ? new Date().toISOString().split("T")[0] : allocation.completion_date;
|
||||
|
||||
await db.execute(
|
||||
"UPDATE work_allocations SET units = ?, completed_units = ?, total_amount = ?, status = ?, completion_date = ? WHERE id = ?",
|
||||
[newUnits, newCompletedUnits, newTotalAmount, newStatus, completionDate, allocationId],
|
||||
);
|
||||
|
||||
const updatedAllocation = await db.query<WorkAllocation[]>(
|
||||
`SELECT wa.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
c.name as contractor_name,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_name
|
||||
FROM work_allocations wa
|
||||
JOIN users e ON wa.employee_id = e.id
|
||||
JOIN users s ON wa.supervisor_id = s.id
|
||||
JOIN users c ON wa.contractor_id = c.id
|
||||
LEFT JOIN sub_departments sd ON wa.sub_department_id = sd.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
WHERE wa.id = ?`,
|
||||
[allocationId],
|
||||
);
|
||||
|
||||
ctx.response.body = updatedAllocation[0];
|
||||
} catch (error) {
|
||||
console.error("Update work allocation units error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Delete work allocation (Supervisor or SuperAdmin)
|
||||
router.delete(
|
||||
"/:id",
|
||||
|
||||
15
backend-deno/scripts/add_completed_units.sql
Normal file
15
backend-deno/scripts/add_completed_units.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- Add completed_units column to work_allocations table
|
||||
-- Run this migration to support tracking completed units separately from total units
|
||||
-- Note: MySQL 8.0 does not support IF NOT EXISTS for ADD COLUMN
|
||||
|
||||
-- Check if column exists before running:
|
||||
-- SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'work_allocations' AND COLUMN_NAME = 'completed_units';
|
||||
|
||||
-- Add the column (will error if already exists)
|
||||
ALTER TABLE work_allocations
|
||||
ADD COLUMN completed_units DECIMAL(10,2) DEFAULT 0 AFTER units;
|
||||
|
||||
-- Update existing records to set completed_units equal to units for completed allocations
|
||||
UPDATE work_allocations
|
||||
SET completed_units = units
|
||||
WHERE status = 'Completed' AND units IS NOT NULL;
|
||||
@@ -70,6 +70,7 @@ export interface WorkAllocation {
|
||||
status: AllocationStatus;
|
||||
rate: number | null;
|
||||
units: number | null;
|
||||
completed_units: number | null;
|
||||
total_amount: number | null;
|
||||
created_at: Date;
|
||||
employee_name?: string;
|
||||
|
||||
@@ -82,6 +82,27 @@ export const useWorkAllocations = (
|
||||
}
|
||||
};
|
||||
|
||||
const updateUnits = async (
|
||||
id: number,
|
||||
units?: number,
|
||||
completedUnits?: number,
|
||||
markComplete?: boolean,
|
||||
) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const updated = await api.updateWorkAllocationUnits(id, units, completedUnits, markComplete);
|
||||
await fetchAllocations(); // Refresh list
|
||||
return updated;
|
||||
} catch (err: never) {
|
||||
setError(err.message || "Failed to update work allocation units");
|
||||
console.error("Failed to update work allocation units:", err);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
allocations,
|
||||
loading,
|
||||
@@ -90,5 +111,6 @@ export const useWorkAllocations = (
|
||||
createAllocation,
|
||||
updateAllocation,
|
||||
deleteAllocation,
|
||||
updateUnits,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -47,20 +47,19 @@ export const ContractorPaymentPage: React.FC = () => {
|
||||
|
||||
// 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),
|
||||
};
|
||||
|
||||
// Only add departmentId if a specific department is selected
|
||||
if (selectedDepartment) {
|
||||
params.departmentId = parseInt(selectedDepartment);
|
||||
}
|
||||
|
||||
const data = await api.getCompletedAllocationsReport(params);
|
||||
|
||||
// Process data to group by sub-department and contractor
|
||||
@@ -113,14 +112,17 @@ export const ContractorPaymentPage: React.FC = () => {
|
||||
|
||||
const contractor = contractorMap.get(contractorId)!;
|
||||
const amount = parseFloat(alloc.total_amount) || parseFloat(alloc.rate) || 0;
|
||||
const departmentName = (alloc.department_name || "").toLowerCase();
|
||||
const subDeptNameLower = (alloc.sub_department_name || "").toLowerCase();
|
||||
const activity = (alloc.activity || "").toLowerCase();
|
||||
|
||||
// Categorize by activity type
|
||||
if (activity.includes("dana")) {
|
||||
// Categorize by department/sub-department name
|
||||
if (departmentName.includes("dana") || subDeptNameLower.includes("dana")) {
|
||||
contractor.dana += amount;
|
||||
} else if (activity.includes("tukdi")) {
|
||||
} else if (departmentName.includes("tudki") || departmentName.includes("tukdi") ||
|
||||
subDeptNameLower.includes("tudki") || subDeptNameLower.includes("tukdi")) {
|
||||
contractor.tukdi += amount;
|
||||
} else if (activity.includes("groundnut")) {
|
||||
} else if (departmentName.includes("groundnut") || subDeptNameLower.includes("groundnut")) {
|
||||
contractor.groundnut += amount;
|
||||
} else if (activity.includes("commission") || activity.includes("salary")) {
|
||||
contractor.commission_salary += amount;
|
||||
@@ -383,7 +385,7 @@ export const ContractorPaymentPage: React.FC = () => {
|
||||
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>
|
||||
<option value="">All Departments</option>
|
||||
{departments.map((dept) => (
|
||||
<option key={dept.id} value={dept.id}>{dept.name}</option>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { CheckCircle, Plus, RefreshCw, Search, Trash2 } from "lucide-react";
|
||||
import { CheckCircle, Edit2, Plus, RefreshCw, Search, Trash2, X } from "lucide-react";
|
||||
import { Card, CardContent } from "../components/ui/Card.tsx";
|
||||
import {
|
||||
Table,
|
||||
@@ -31,6 +31,7 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
createAllocation,
|
||||
updateAllocation,
|
||||
deleteAllocation,
|
||||
updateUnits,
|
||||
} = useWorkAllocations();
|
||||
const { departments } = useDepartments();
|
||||
const { employees } = useEmployees();
|
||||
@@ -40,6 +41,15 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
// Check if user is supervisor (limited to their department)
|
||||
const isSupervisor = user?.role === "Supervisor";
|
||||
|
||||
// Check if user can edit units (supervisor, contractor, or super admin)
|
||||
const canEditUnits = user?.role === "Supervisor" || user?.role === "Contractor" || user?.role === "SuperAdmin";
|
||||
|
||||
// State for edit units modal
|
||||
const [editingAllocation, setEditingAllocation] = useState<any | null>(null);
|
||||
const [editUnitsValue, setEditUnitsValue] = useState<string>("");
|
||||
const [editCompletedUnitsValue, setEditCompletedUnitsValue] = useState<string>("");
|
||||
const [markCompleteChecked, setMarkCompleteChecked] = useState<boolean>(false);
|
||||
|
||||
// Get supervisor's department name
|
||||
const supervisorDeptName =
|
||||
departments.find((d) => d.id === user?.department_id)?.name || "";
|
||||
@@ -213,6 +223,42 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenEditUnits = (allocation: any, fromCheckmark = false) => {
|
||||
setEditingAllocation(allocation);
|
||||
setEditUnitsValue(String(allocation.units || 0));
|
||||
setEditCompletedUnitsValue(String(allocation.completed_units || 0));
|
||||
// If opened from checkmark, default to marking complete
|
||||
setMarkCompleteChecked(fromCheckmark && allocation.status !== "Completed");
|
||||
};
|
||||
|
||||
const handleCloseEditUnits = () => {
|
||||
setEditingAllocation(null);
|
||||
setEditUnitsValue("");
|
||||
setEditCompletedUnitsValue("");
|
||||
setMarkCompleteChecked(false);
|
||||
};
|
||||
|
||||
const handleSaveUnits = async () => {
|
||||
if (!editingAllocation) return;
|
||||
try {
|
||||
const units = parseFloat(editUnitsValue) || 0;
|
||||
const completedUnits = parseFloat(editCompletedUnitsValue) || 0;
|
||||
await updateUnits(editingAllocation.id, units, completedUnits, markCompleteChecked);
|
||||
handleCloseEditUnits();
|
||||
} catch (err: any) {
|
||||
alert(err.message || "Failed to update units");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMarkCompleteWithUnits = async (allocation: any) => {
|
||||
const isPerUnit = allocation.units && allocation.units > 0;
|
||||
if (isPerUnit && canEditUnits) {
|
||||
handleOpenEditUnits(allocation, true);
|
||||
} else {
|
||||
await handleMarkComplete(allocation.id);
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate summary stats
|
||||
const stats = {
|
||||
total: allocations.length,
|
||||
@@ -517,8 +563,10 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>Employee</TableHead>
|
||||
<TableHead>Contractor</TableHead>
|
||||
<TableHead>Department</TableHead>
|
||||
<TableHead>Sub-Department</TableHead>
|
||||
<TableHead>Activity</TableHead>
|
||||
<TableHead>Units</TableHead>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead>Rate Details</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
@@ -526,12 +574,12 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredAllocations.map((allocation) => {
|
||||
const isPerUnit = allocation.activity === "Loading" ||
|
||||
allocation.activity === "Unloading";
|
||||
const isPerUnit = allocation.units && allocation.units > 0;
|
||||
const units = parseFloat(allocation.units) || 0;
|
||||
const completedUnits = parseFloat(allocation.completed_units) || 0;
|
||||
const rate = parseFloat(allocation.rate) || 0;
|
||||
const total = parseFloat(allocation.total_amount) ||
|
||||
(isPerUnit ? units * rate : rate);
|
||||
(isPerUnit ? completedUnits * rate : rate);
|
||||
|
||||
return (
|
||||
<TableRow key={allocation.id}>
|
||||
@@ -542,6 +590,9 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
<TableCell>
|
||||
{allocation.contractor_name || "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{allocation.department_name || "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{allocation.sub_department_name || "-"}
|
||||
</TableCell>
|
||||
@@ -550,8 +601,7 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
? (
|
||||
<span
|
||||
className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
allocation.activity === "Loading" ||
|
||||
allocation.activity === "Unloading"
|
||||
isPerUnit
|
||||
? "bg-purple-100 text-purple-700"
|
||||
: "bg-gray-100 text-gray-700"
|
||||
}`}
|
||||
@@ -561,6 +611,41 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
)
|
||||
: "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{isPerUnit ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-sm">
|
||||
<span className={`font-semibold ${completedUnits < units ? "text-orange-600" : "text-green-600"}`}>
|
||||
{completedUnits}
|
||||
</span>
|
||||
<span className="text-gray-500"> / {units}</span>
|
||||
{completedUnits < units && (
|
||||
<div className="text-xs text-orange-500">
|
||||
({units - completedUnits} remaining)
|
||||
</div>
|
||||
)}
|
||||
{completedUnits >= units && units > 0 && (
|
||||
<div className="text-xs text-green-500">
|
||||
✓ Complete
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{canEditUnits && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleOpenEditUnits(allocation)}
|
||||
className="text-blue-600 p-1"
|
||||
title="Edit Units"
|
||||
>
|
||||
<Edit2 size={12} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-400">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{new Date(allocation.assigned_date)
|
||||
.toLocaleDateString()}
|
||||
@@ -569,11 +654,11 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
{rate > 0
|
||||
? (
|
||||
<div className="text-sm">
|
||||
{isPerUnit && units > 0
|
||||
{isPerUnit && completedUnits > 0
|
||||
? (
|
||||
<div>
|
||||
<div className="text-gray-500">
|
||||
{units} × ₹{rate.toFixed(2)}
|
||||
{completedUnits} × ₹{rate.toFixed(2)}
|
||||
</div>
|
||||
<div className="font-semibold text-green-600">
|
||||
= ₹{total.toFixed(2)}
|
||||
@@ -611,9 +696,9 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleMarkComplete(allocation.id)}
|
||||
handleMarkCompleteWithUnits(allocation)}
|
||||
className="text-green-600"
|
||||
title="Mark Complete"
|
||||
title={isPerUnit ? "Complete & Set Units" : "Mark Complete"}
|
||||
>
|
||||
<CheckCircle size={14} />
|
||||
</Button>
|
||||
@@ -691,6 +776,123 @@ export const WorkAllocationPage: React.FC = () => {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Edit Units Modal */}
|
||||
{editingAllocation && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-full max-w-md shadow-xl">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-800">
|
||||
Edit Units - {editingAllocation.activity}
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCloseEditUnits}
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Total Units Assigned
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={editUnitsValue}
|
||||
onChange={(e) => setEditUnitsValue(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
min="0"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Completed Units
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={editCompletedUnitsValue}
|
||||
onChange={(e) => setEditCompletedUnitsValue(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
min="0"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Units Difference Display */}
|
||||
{(() => {
|
||||
const totalUnits = parseFloat(editUnitsValue) || 0;
|
||||
const completed = parseFloat(editCompletedUnitsValue) || 0;
|
||||
const remaining = totalUnits - completed;
|
||||
return (
|
||||
<div className={`p-3 rounded-md ${remaining > 0 ? "bg-orange-50 border border-orange-200" : "bg-green-50 border border-green-200"}`}>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Total Units:</span>
|
||||
<span className="font-medium">{totalUnits}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Completed:</span>
|
||||
<span className="font-medium text-green-600">{completed}</span>
|
||||
</div>
|
||||
{remaining > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Remaining:</span>
|
||||
<span className="font-medium text-orange-600">{remaining}</span>
|
||||
</div>
|
||||
)}
|
||||
{remaining === 0 && totalUnits > 0 && (
|
||||
<div className="text-sm text-green-600 font-medium mt-1">
|
||||
✓ All units completed
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Calculation Preview */}
|
||||
{parseFloat(editingAllocation.rate) > 0 && (
|
||||
<div className="p-3 bg-blue-50 border border-blue-200 rounded-md">
|
||||
<div className="text-sm text-gray-600">
|
||||
Rate: ₹{parseFloat(editingAllocation.rate).toFixed(2)} per unit
|
||||
</div>
|
||||
<div className="text-lg font-semibold text-green-600 mt-1">
|
||||
Total Payment: ₹{((parseFloat(editCompletedUnitsValue) || 0) * parseFloat(editingAllocation.rate)).toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mark as Complete Checkbox */}
|
||||
{editingAllocation.status !== "Completed" && (
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="markComplete"
|
||||
checked={markCompleteChecked}
|
||||
onChange={(e) => setMarkCompleteChecked(e.target.checked)}
|
||||
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label htmlFor="markComplete" className="text-sm font-medium text-gray-700">
|
||||
Mark as Completed
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 mt-6">
|
||||
<Button variant="ghost" onClick={handleCloseEditUnits}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSaveUnits}>
|
||||
{markCompleteChecked ? "Save & Complete" : "Save Units"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -174,6 +174,18 @@ class ApiService {
|
||||
});
|
||||
}
|
||||
|
||||
updateWorkAllocationUnits(
|
||||
id: number,
|
||||
units?: number,
|
||||
completedUnits?: number,
|
||||
markComplete?: boolean,
|
||||
) {
|
||||
return this.request<any>(`/work-allocations/${id}/units`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ units, completedUnits, markComplete }),
|
||||
});
|
||||
}
|
||||
|
||||
// Attendance
|
||||
getAttendance(
|
||||
params?: {
|
||||
|
||||
@@ -56,11 +56,15 @@ export interface WorkAllocation {
|
||||
supervisor_id: number;
|
||||
contractor_id: number;
|
||||
sub_department_id?: number;
|
||||
activity?: string;
|
||||
description?: string;
|
||||
assigned_date: string;
|
||||
status: "Pending" | "InProgress" | "Completed" | "Cancelled";
|
||||
completion_date?: string;
|
||||
rate?: number;
|
||||
units?: number;
|
||||
completed_units?: number;
|
||||
total_amount?: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
employee_name?: string;
|
||||
|
||||
@@ -452,7 +452,7 @@ export const exportReportToXLSX = (
|
||||
}));
|
||||
|
||||
const ws3 = XLSX.utils.json_to_sheet(contractorData);
|
||||
ws3["!cols"] = [
|
||||
ws3["!cols"] = [.
|
||||
{ wch: 25 }, // Contractor
|
||||
{ wch: 12 }, // Employees
|
||||
{ wch: 16 }, // Total Completed
|
||||
@@ -466,7 +466,8 @@ export const exportReportToXLSX = (
|
||||
? `${dateRange.startDate}_to_${dateRange.endDate}`
|
||||
: new Date().toISOString().split("T")[0];
|
||||
const deptStr = departmentName.toLowerCase().replace(/\s+/g, "_");
|
||||
const filename = `work_report_${deptStr}_${dateStr}.xlsx`;
|
||||
const randomStr = Math.random().toString(36).substring(2, 11);
|
||||
const filename = `work_report${randomStr}_${deptStr}_${dateStr}.xlsx`;
|
||||
|
||||
// Write and download
|
||||
XLSX.writeFile(wb, filename);
|
||||
|
||||
Reference in New Issue
Block a user