884054fb
“wangming”
项目初始化
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
import React, { useState } from 'react';
import { Download } from 'lucide-react';
import { Button } from '../ui/button';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../ui/table';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../ui/select';
// Notes:
// 1. Query purchasing modules under Partner, Group and Location; fees and payment status
// 2. Invoice permission for finance: view and export bills after login
// 3. Report generation: historical bills, unpaid bills, rates, overdue situations
export type InvoiceStatus = 'Paid' | 'Overdue';
export interface InvoiceRow {
id: string;
locationId: string;
subscription: string;
ratePerMonth: string;
discountRate: string;
totalAmount: string;
period: string;
status: InvoiceStatus;
isOverdue?: boolean; // highlight rate and total in red when overdue
}
const MOCK_INVOICES: InvoiceRow[] = [
{ id: '1', locationId: '12345', subscription: 'Labeling', ratePerMonth: '9.99', discountRate: '1', totalAmount: '$9.99', period: 'October', status: 'Paid' },
{ id: '2', locationId: '12345', subscription: 'Tasks', ratePerMonth: '20', discountRate: '0.5', totalAmount: '10', period: 'October', status: 'Paid' },
{ id: '3', locationId: '12345', subscription: 'Sensor', ratePerMonth: '30', discountRate: '1', totalAmount: '30', period: 'October', status: 'Paid' },
{ id: '4', locationId: '12345', subscription: 'e-label', ratePerMonth: '25', discountRate: '1', totalAmount: '50', period: 'October', status: 'Overdue', isOverdue: true },
{ id: '5', locationId: '12345', subscription: 'Food Waste', ratePerMonth: '35', discountRate: '1', totalAmount: '35', period: 'October', status: 'Paid' },
];
export function InvoicesView() {
const [invoices] = useState<InvoiceRow[]>(MOCK_INVOICES);
const [periodFilter, setPeriodFilter] = useState('October');
const [subscriptionFilter, setSubscriptionFilter] = useState('all');
const filteredInvoices = invoices.filter((row) => {
const matchPeriod = !periodFilter || row.period === periodFilter;
const matchSub = subscriptionFilter === 'all' || row.subscription === subscriptionFilter;
return matchPeriod && matchSub;
});
const handleExportBills = () => {
// Finance personnel: export bills (e.g. CSV or PDF)
const csv = [
['Location ID', 'Subscription', 'Rate/month', 'Discount Rate', 'Total Amount', 'Period', 'Status'].join(','),
...filteredInvoices.map((r) => [r.locationId, r.subscription, r.ratePerMonth, r.discountRate, r.totalAmount, r.period, r.status].join(',')),
].join('\n');
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `invoices-${periodFilter || 'all'}.csv`;
link.click();
URL.revokeObjectURL(link.href);
};
return (
<div className="h-full flex flex-col bg-white">
{/* Toolbar:与 Products/Locations 一致 — 单行、圆角、细边框、bg-gray-50、无标题、框内文字黑色 */}
<div className="border-b border-gray-200 py-4 bg-gray-50">
<div className="flex flex-nowrap items-center gap-2 overflow-x-auto min-w-0">
<Select defaultValue="partner-a">
<SelectTrigger className="w-[140px] h-9 rounded-lg border border-black font-medium text-black bg-white shrink-0">
<SelectValue placeholder="Partner" />
</SelectTrigger>
<SelectContent>
<SelectItem value="partner-a">Partner A</SelectItem>
</SelectContent>
</Select>
<Select defaultValue="group-b">
<SelectTrigger className="w-[140px] h-9 rounded-lg border border-black font-medium text-black bg-white shrink-0">
<SelectValue placeholder="Group" />
</SelectTrigger>
<SelectContent>
<SelectItem value="group-b">Group B</SelectItem>
</SelectContent>
</Select>
<Select defaultValue="loc-12">
<SelectTrigger className="w-[160px] h-9 rounded-lg border border-black font-medium text-black bg-white shrink-0">
<SelectValue placeholder="Location" />
</SelectTrigger>
<SelectContent>
<SelectItem value="loc-12">Location 12</SelectItem>
<SelectItem value="all">All Locations</SelectItem>
</SelectContent>
</Select>
<Select value={subscriptionFilter} onValueChange={setSubscriptionFilter}>
<SelectTrigger className="w-[140px] h-9 rounded-lg border border-black font-medium text-black bg-white shrink-0">
<SelectValue placeholder="Subscription" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Subscription (All)</SelectItem>
<SelectItem value="Labeling">Labeling</SelectItem>
<SelectItem value="Tasks">Tasks</SelectItem>
<SelectItem value="Sensor">Sensor</SelectItem>
<SelectItem value="e-label">e-label</SelectItem>
<SelectItem value="Food Waste">Food Waste</SelectItem>
</SelectContent>
</Select>
<Select value={periodFilter} onValueChange={setPeriodFilter}>
<SelectTrigger className="w-[120px] h-9 rounded-lg border border-black font-medium text-black bg-white shrink-0">
<SelectValue placeholder="Period" />
</SelectTrigger>
<SelectContent>
<SelectItem value="October">October</SelectItem>
<SelectItem value="September">September</SelectItem>
<SelectItem value="August">August</SelectItem>
</SelectContent>
</Select>
<div className="flex-1 min-w-0" />
<Button variant="outline" className="h-9 rounded-lg border border-black text-black gap-2 bg-white hover:bg-gray-50 shrink-0" onClick={handleExportBills}>
<Download className="w-4 h-4" /> Export Bills
</Button>
</div>
</div>
{/* Content Area:与 Products 一致,无内边距、表格包在白色圆角框内 */}
<div className="flex-1 overflow-auto bg-gray-50">
<div className="bg-white border border-gray-200 shadow-sm rounded-sm overflow-hidden">
<Table>
<TableHeader>
<TableRow className="bg-gray-100 hover:bg-gray-100">
<TableHead className="font-bold text-gray-900 border-r">Location ID</TableHead>
<TableHead className="font-bold text-gray-900 border-r">Subscription</TableHead>
<TableHead className="font-bold text-gray-900 border-r">Rate/month</TableHead>
<TableHead className="font-bold text-gray-900 border-r">Discount Rate</TableHead>
<TableHead className="font-bold text-gray-900 border-r">Total Amount</TableHead>
<TableHead className="font-bold text-gray-900 border-r">Period</TableHead>
<TableHead className="font-bold text-gray-900 text-center">Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredInvoices.map((row) => (
<TableRow key={row.id} className="hover:bg-gray-50">
<TableCell className="text-gray-700">{row.locationId}</TableCell>
<TableCell className="text-gray-700">{row.subscription}</TableCell>
<TableCell>
<span className={row.isOverdue ? 'text-red-600' : 'text-gray-700'}>
{row.ratePerMonth}
</span>
</TableCell>
<TableCell className="text-gray-700">{row.discountRate}</TableCell>
<TableCell>
<span className={row.isOverdue ? 'text-red-600' : 'text-gray-700'}>
{row.totalAmount}
</span>
</TableCell>
<TableCell className="text-gray-700">{row.period}</TableCell>
<TableCell>
<span className={row.status === 'Overdue' ? 'text-red-600 font-medium' : 'text-green-600'}>
{row.status}
</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</div>
);
}
|