updated siliconpin

This commit is contained in:
suvodip ghosh
2025-05-27 05:59:30 +00:00
parent 9091219729
commit 4e79dde03e
61 changed files with 8676 additions and 1143 deletions

View File

@@ -3,13 +3,14 @@ import { useIsLoggedIn } from '../../lib/isLoggedIn';
import Loader from "../../components/ui/loader";
import { PDFDownloadLink } from '@react-pdf/renderer';
import InvoicePDF from "../../lib/InvoicePDF";
import { Eye, Pencil, Trash2, Download, ChevronUp, ChevronDown, Search, ArrowLeft, FileText } from "lucide-react";
import { Eye, Pencil, Trash2, Download, ChevronUp, ChevronDown, Search, ArrowLeft, FileText, ArrowRight } from "lucide-react";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../ui/dialog";
import * as XLSX from 'xlsx';
export default function AllSellingList() {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const { isLoggedIn, loading, error, sessionData } = useIsLoggedIn();
const [billingData, setBillingData] = useState([]);
const [usersData, setUsersData] = useState([]);
@@ -18,17 +19,7 @@ export default function AllSellingList() {
const [selectedItem, setSelectedItem] = useState(null);
const [dialogOpen, setDialogOpen] = useState(false);
const [editMode, setEditMode] = useState(false);
const [formData, setFormData] = useState({
service: '',
serviceId: 'service-1',
cycle: 'monthly',
amount: '',
user: '',
siliconId: '',
name: '',
status: 'pending',
remarks: ''
});
const [formData, setFormData] = useState({ service: '', serviceId: 'service-1', cycle: 'monthly', amount: '', user: '', siliconId: '', name: '', status: 'pending', remarks: '' });
const [currentStep, setCurrentStep] = useState(1);
const [selectedCustomer, setSelectedCustomer] = useState(null);
const [customerSearchTerm, setCustomerSearchTerm] = useState('');
@@ -127,11 +118,11 @@ export default function AllSellingList() {
setDialogOpen(true);
};
const handleDeleteItem = async (id) => {
const handleDeleteItem = async (billingId, serviceType) => {
if (!window.confirm('Are you sure you want to delete this billing record?')) return;
try {
const res = await fetch(`${INVOICE_API_URL}?query=delete-billing&id=${id}`, {
const res = await fetch(`${INVOICE_API_URL}?query=delete-billing&billingId=${billingId}&serviceType=${serviceType}`, {
method: 'DELETE',
credentials: 'include',
headers: {
@@ -159,9 +150,7 @@ export default function AllSellingList() {
const handleSubmit = async (e) => {
e.preventDefault();
try {
const url = selectedItem
? `${INVOICE_API_URL}?query=update-billing&id=${selectedItem.id}`
: `${INVOICE_API_URL}?query=create-billing`;
const url = selectedItem ? `${INVOICE_API_URL}?query=update-billing&id=${selectedItem.id}` : `${INVOICE_API_URL}?query=create-billing`;
const method = selectedItem ? 'PUT' : 'POST';
@@ -218,6 +207,7 @@ export default function AllSellingList() {
'Amount': item.amount,
'cycle': item.cycle,
'Status': item.status,
'Service Status': item.service_status,
'Created At': formatDate(item.created_at),
'Remarks': item.remarks
})));
@@ -367,20 +357,29 @@ export default function AllSellingList() {
}).format(parseFloat(amount));
};
if (loading || dataLoading) return <Loader />;
if (error || apiError) return <p>Error: {error?.message || apiError}</p>;
if (loading || (isLoggedIn && sessionData?.user_type === 'admin' && dataLoading)) {
return <Loader />;
}
if (error || apiError) return <p>Error: {error?.message || apiError.message}</p>;
if (!isLoggedIn || sessionData?.user_type !== 'admin') {
return <p className="text-center mt-8">You are not authorized to view this page. <a href="/" className="text-[#6d9e37]">Click Here</a> to go to the homepage.</p>;
}
return (
<section className="container mx-auto px-4 py-8">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">Billing Management</h1>
<div className="flex gap-2">
<Button onClick={handleCreateNew} variant="outline">
Create New
</Button>
<div className="relative inline-block">
<Button onClick={() => setIsDropdownOpen(!isDropdownOpen)} variant={isDropdownOpen ? 'default' : 'outline'}>Manage Users</Button>
{isDropdownOpen && (
<div className="absolute mt-1 w-44 bg-white border rounded-md shadow-lg z-10">
<a href="/manager/customer-lists" className="block px-4 py-2 hover:bg-gray-100 rounded-md text-sm text-[#6d9e37] font-medium">Manage Customer</a>
<a href="/manager/selling-list" className="block px-4 py-2 hover:bg-gray-100 rounded-md text-sm text-[#6d9e37] font-medium">Manage Billing</a>
</div>
)}
</div>
<Button onClick={handleCreateNew} variant="outline">Create New</Button>
<Button onClick={exportToExcel} variant="outline" className="flex items-center gap-2">
<FileText className="w-4 h-4" /> Export to Excel
</Button>
@@ -532,8 +531,7 @@ export default function AllSellingList() {
</th>
<th
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={() => requestSort('status')}
>
onClick={() => requestSort('status')}>
<div className="flex items-center">
Status
{sortConfig.key === 'status' && (
@@ -545,8 +543,17 @@ export default function AllSellingList() {
</th>
<th
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={() => requestSort('created_at')}
>
onClick={() => requestSort('service_status')}>
<div className="flex items-center">
Ser.. Status
{sortConfig.key === 'service_status' && (
sortConfig.direction === 'asc' ?
<ChevronUp className="ml-1 h-4 w-4" /> :
<ChevronDown className="ml-1 h-4 w-4" />
)}
</div>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" onClick={() => requestSort('created_at')}>
<div className="flex items-center">
Created At
{sortConfig.key === 'created_at' && (
@@ -556,35 +563,28 @@ export default function AllSellingList() {
)}
</div>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{currentItems.length > 0 ? (
currentItems.map((item) => (
<tr key={item.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{item.billing_id}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{item.service}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{item.name ?? item.name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{item.user}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-medium">
{formatCurrency(item.amount)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{item.billing_id}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{item.service}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{item.name ?? item.name}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{item.user}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-medium">{formatCurrency(item.amount)}</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${getStatusColor(item.status)}`}>
{item.status.charAt(0).toUpperCase() + item.status.slice(1)}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${item.service_status == 1 ? 'bg-green-100 text-green-800' : item.service_status == 0 ? 'bg-red-100 text-red-800' : ''}`}>
{item.service_status == 1 ? 'Active' : item.service_status == 0 ? 'Deactivate' : ''}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{formatDate(item.created_at)}
</td>
@@ -605,7 +605,7 @@ export default function AllSellingList() {
</button>
<button
title="Delete Items"
onClick={() => handleDeleteItem(item.id)}
onClick={() => handleDeleteItem(item.billing_id, item.service_type)}
className="text-red-600 hover:text-red-900 mr-2"
>
<Trash2 className="w-5 h-5" />
@@ -640,59 +640,29 @@ export default function AllSellingList() {
Page {currentPage} of {totalPages}
</div>
<div className="flex flex-wrap gap-2">
<Button
onClick={() => paginate(1)}
disabled={currentPage === 1}
variant="outline"
size="sm"
>
First
</Button>
<Button
onClick={() => paginate(currentPage - 1)}
disabled={currentPage === 1}
variant="outline"
size="sm"
>
Previous
</Button>
{getPaginationRange().map((item, index) => {
if (item === '...') {
{
currentPage > 1 ? (
<>
<Button onClick={() => paginate(1)} disabled={currentPage === 1} variant="outline" size="sm">First</Button>
<Button onClick={() => paginate(currentPage - 1)} disabled={currentPage === 1} variant="outline" size="sm">Previous</Button >
</>
) : ''
}
{
getPaginationRange().map((item, index) => {
if (item === '...') {
return (
<span key={`ellipsis-${index}`} className="px-2 py-1">...</span>
);
}
return (
<span key={`ellipsis-${index}`} className="px-2 py-1">
...
</span>
<Button key={`page-${item}`} onClick={() => paginate(item)} variant={currentPage === item ? "default" : "outline"} size="sm">{item}</Button>
);
}
return (
<Button
key={`page-${item}`}
onClick={() => paginate(item)}
variant={currentPage === item ? "default" : "outline"}
size="sm"
>
{item}
</Button>
);
})}
})
}
<Button
onClick={() => paginate(currentPage + 1)}
disabled={currentPage === totalPages}
variant="outline"
size="sm"
>
Next
</Button>
<Button
onClick={() => paginate(totalPages)}
disabled={currentPage === totalPages}
variant="outline"
size="sm"
>
Last
</Button>
<Button onClick={() => paginate(currentPage + 1)} disabled={currentPage === totalPages} variant="outline" size="sm">Next</Button>
<Button onClick={() => paginate(totalPages)} disabled={currentPage === totalPages} variant="outline" size="sm">Last</Button>
</div>
</div>
)}
@@ -727,22 +697,23 @@ export default function AllSellingList() {
/>
</div>
<h3 className="font-semibold mb-4">Select Customer</h3>
<h3 className="font-semibold mb-4 text-neutral-400">Select Customer</h3>
{filteredCustomers.length > 0 ? (
<div className="space-y-2 max-h-[400px] overflow-y-auto">
{filteredCustomers.map(user => (
<div
key={user.id}
onClick={() => handleCustomerSelect(user)}
className="p-3 border rounded-md cursor-pointer hover:bg-gray-50"
className="group p-3 mx-2 border rounded-md cursor-pointer hover:bg-gray-100 hover:border-[#6d9e37] transition-border duration-700"
>
<div className="flex justify-between items-center">
<div>
<p className="font-medium">{user.name}</p>
<p className="text-sm text-gray-600">{user.email}</p>
<p className="font-medium text-neutral-950">{user.name}</p>
<p className="text-sm text-neutral-400">{user.email}</p>
</div>
<div className="text-sm">
<span className="bg-gray-100 px-2 py-1 rounded">ID: {user.siliconId}</span>
<div className="text-sm inline-flex gap-2">
<span className="bg-gray-100 px-2 py-1 rounded text-neutral-950">SiliconId: {user.siliconId}</span>
<ArrowRight className="text-[#6d9e37] opacity-0 group-hover:opacity-100 transition-opacity duration-700" />
</div>
</div>
</div>
@@ -758,19 +729,13 @@ export default function AllSellingList() {
<div className="mb-6 p-4 bg-gray-50 rounded-md">
<div className="flex justify-between items-start">
<div>
<h3 className="font-semibold">Customer Information</h3>
<p className="text-sm"><span className="font-medium">Name:</span> {selectedCustomer.name}</p>
<p className="text-sm"><span className="font-medium">Email:</span> {selectedCustomer.email}</p>
<p className="text-sm"><span className="font-medium">Silicon ID:</span> {selectedCustomer.siliconId}</p>
<h3 className="font-semibold text-neutral-950">Customer Information</h3>
<p className="text-sm text-neutral-400"><span className="font-medium">Name:</span> {selectedCustomer.name}</p>
<p className="text-sm text-neutral-400"><span className="font-medium">Email:</span> {selectedCustomer.email}</p>
<p className="text-sm text-neutral-400"><span className="font-medium">Silicon ID:</span> {selectedCustomer.siliconId}</p>
</div>
{!selectedItem && (
<Button
type="button"
onClick={handleBackToCustomerSelect}
variant="ghost"
size="sm"
className="text-gray-500"
>
<Button type="button" onClick={handleBackToCustomerSelect} variant="ghost" size="sm" className="text-gray-500">
<ArrowLeft className="w-4 h-4 mr-1" /> Change Customer
</Button>
)}
@@ -781,23 +746,11 @@ export default function AllSellingList() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Service</label>
<input
type="text"
name="service"
value={formData.service}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none"
required
/>
<input type="text" name="service" value={formData.service} onChange={handleInputChange} className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none text-neutral-950" required />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">cycle</label>
<select
name="cycle"
value={formData.cycle}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none"
>
<select name="cycle" value={formData.cycle} onChange={handleInputChange} className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none text-neutral-950 bg-white" >
<option value="monthly">Monthly</option>
<option value="yearly">Yearly</option>
<option value="one-time">One-time</option>
@@ -805,25 +758,11 @@ export default function AllSellingList() {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Amount</label>
<input
type="number"
name="amount"
value={formData.amount}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none"
step="0.01"
min="0"
required
/>
<input type="number" name="amount" value={formData.amount} onChange={handleInputChange} className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none text-neutral-950 bg-white" step="0.01" min="0" required />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Status</label>
<select
name="status"
value={formData.status}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none"
>
<select name="status" value={formData.status} onChange={handleInputChange} className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none text-neutral-950 bg-white" >
<option value="pending">Pending</option>
<option value="completed">Completed</option>
<option value="failed">Failed</option>
@@ -831,45 +770,17 @@ export default function AllSellingList() {
</div>
<div className="">
<label htmlFor="billing_date" className="block text-sm font-medium text-gray-700 mb-1">Billing Date</label>
<input
type="date"
name="billing_date"
value={formData.billing_date}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none"
required
/>
<input type="date" name="billing_date" value={formData.billing_date} onChange={handleInputChange} className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none text-neutral-950" />
</div>
<div className="">
<label htmlFor="remarks" className="block text-sm font-medium text-gray-700 mb-1">Remarks</label>
<input
type="text"
name="remarks"
value={formData.remarks}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none"
required
/>
<input type="text" name="remarks" value={formData.remarks} onChange={handleInputChange} className="w-full px-3 py-2 border border-[#6d9e37] rounded-md outline-none text-neutral-950" />
</div>
</div>
<DialogFooter>
<Button
type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
{selectedItem ? 'Update' : 'Create'}
</Button>
<Button
type="button"
onClick={() => {
setDialogOpen(false);
setCurrentStep(1);
}}
variant="outline"
>
Cancel
</Button>
<Button type="submit" className="" >{selectedItem ? 'Update' : 'Create'}</Button>
<Button type="button" onClick={() => { setDialogOpen(false); setCurrentStep(1);}} variant="outline">Cancel</Button>
</DialogFooter>
</form>
) : (