krispdev-business 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dev-Business/.vscode/settings.json +3 -0
- package/Dev-Business/backend-project/config/db.js +16 -0
- package/Dev-Business/backend-project/controllers/authController.js +41 -0
- package/Dev-Business/backend-project/controllers/carController.js +62 -0
- package/Dev-Business/backend-project/controllers/packageController.js +49 -0
- package/Dev-Business/backend-project/controllers/paymentController.js +63 -0
- package/Dev-Business/backend-project/controllers/reportController.js +129 -0
- package/Dev-Business/backend-project/controllers/serviceController.js +155 -0
- package/Dev-Business/backend-project/middleware/auth.js +16 -0
- package/Dev-Business/backend-project/package-lock.json +1507 -0
- package/Dev-Business/backend-project/package.json +21 -0
- package/Dev-Business/backend-project/routes/authRoutes.js +10 -0
- package/Dev-Business/backend-project/routes/carRoutes.js +11 -0
- package/Dev-Business/backend-project/routes/packageRoutes.js +11 -0
- package/Dev-Business/backend-project/routes/paymentRoutes.js +10 -0
- package/Dev-Business/backend-project/routes/reportRoutes.js +11 -0
- package/Dev-Business/backend-project/routes/serviceRoutes.js +19 -0
- package/Dev-Business/backend-project/server.js +72 -0
- package/Dev-Business/frontend-project/README.md +18 -0
- package/Dev-Business/frontend-project/eslint.config.js +21 -0
- package/Dev-Business/frontend-project/index.html +14 -0
- package/Dev-Business/frontend-project/package-lock.json +3655 -0
- package/Dev-Business/frontend-project/package.json +34 -0
- package/Dev-Business/frontend-project/postcss.config.js +6 -0
- package/Dev-Business/frontend-project/public/favicon.svg +1 -0
- package/Dev-Business/frontend-project/public/icons.svg +24 -0
- package/Dev-Business/frontend-project/src/App.css +184 -0
- package/Dev-Business/frontend-project/src/App.jsx +84 -0
- package/Dev-Business/frontend-project/src/assets/hero.png +0 -0
- package/Dev-Business/frontend-project/src/assets/react.svg +1 -0
- package/Dev-Business/frontend-project/src/assets/vite.svg +1 -0
- package/Dev-Business/frontend-project/src/components/CarForm.jsx +146 -0
- package/Dev-Business/frontend-project/src/components/DailyReport.jsx +264 -0
- package/Dev-Business/frontend-project/src/components/Dashboard.jsx +153 -0
- package/Dev-Business/frontend-project/src/components/Login.jsx +163 -0
- package/Dev-Business/frontend-project/src/components/Navbar.jsx +99 -0
- package/Dev-Business/frontend-project/src/components/Navibar.jsx +100 -0
- package/Dev-Business/frontend-project/src/components/PaymentForm.jsx +211 -0
- package/Dev-Business/frontend-project/src/components/ServiceRecord.jsx +384 -0
- package/Dev-Business/frontend-project/src/index.css +155 -0
- package/Dev-Business/frontend-project/src/main.jsx +10 -0
- package/Dev-Business/frontend-project/src/services/api.js +23 -0
- package/Dev-Business/frontend-project/tailwind.config.js +31 -0
- package/Dev-Business/frontend-project/vite.config.js +15 -0
- package/package.json +17 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import api from '../services/api';
|
|
3
|
+
|
|
4
|
+
function ServiceRecord() {
|
|
5
|
+
const [services, setServices] = useState([]);
|
|
6
|
+
const [packages, setPackages] = useState([]);
|
|
7
|
+
const [cars, setCars] = useState([]);
|
|
8
|
+
const [formData, setFormData] = useState({
|
|
9
|
+
ServiceDate: new Date().toISOString().split('T')[0],
|
|
10
|
+
PlateNumber: '',
|
|
11
|
+
PackageNumber: ''
|
|
12
|
+
});
|
|
13
|
+
const [editingId, setEditingId] = useState(null);
|
|
14
|
+
const [message, setMessage] = useState(null);
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [showBill, setShowBill] = useState(null);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
fetchData();
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
const fetchData = async () => {
|
|
23
|
+
try {
|
|
24
|
+
const [servicesRes, packagesRes, carsRes] = await Promise.all([
|
|
25
|
+
api.get('/services'),
|
|
26
|
+
api.get('/packages'),
|
|
27
|
+
api.get('/cars')
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
setServices(servicesRes.data.data || []);
|
|
31
|
+
setPackages(packagesRes.data.data || []);
|
|
32
|
+
setCars(carsRes.data.data || []);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error fetching data:', error);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleChange = (e) => {
|
|
39
|
+
setFormData({ ...formData, [e.target.name]: e.target.value });
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleSubmit = async (e) => {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
setLoading(true);
|
|
45
|
+
setMessage(null);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
if (editingId) {
|
|
49
|
+
await api.put(`/services/${editingId}`, formData);
|
|
50
|
+
setMessage({ type: 'success', text: '✅ Service record updated successfully!' });
|
|
51
|
+
setEditingId(null);
|
|
52
|
+
} else {
|
|
53
|
+
const response = await api.post('/services', formData);
|
|
54
|
+
setMessage({ type: 'success', text: '✅ Service record created successfully!' });
|
|
55
|
+
|
|
56
|
+
// Auto-redirect to payment if needed
|
|
57
|
+
if (response.data.RecordNumber) {
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
if (window.confirm('Would you like to record payment for this service?')) {
|
|
60
|
+
window.location.href = `/payments?record=${response.data.RecordNumber}`;
|
|
61
|
+
}
|
|
62
|
+
}, 500);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
setFormData({
|
|
67
|
+
ServiceDate: new Date().toISOString().split('T')[0],
|
|
68
|
+
PlateNumber: '',
|
|
69
|
+
PackageNumber: ''
|
|
70
|
+
});
|
|
71
|
+
fetchData();
|
|
72
|
+
setTimeout(() => setMessage(null), 3000);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
setMessage({ type: 'error', text: err.response?.data?.error || 'Operation failed' });
|
|
75
|
+
} finally {
|
|
76
|
+
setLoading(false);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleEdit = (service) => {
|
|
81
|
+
setEditingId(service.RecordNumber);
|
|
82
|
+
setFormData({
|
|
83
|
+
ServiceDate: service.ServiceDate.split('T')[0],
|
|
84
|
+
PlateNumber: service.PlateNumber,
|
|
85
|
+
PackageNumber: service.PackageNumber
|
|
86
|
+
});
|
|
87
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleDelete = async (id) => {
|
|
91
|
+
if (window.confirm('⚠️ Delete this service record? This will also delete associated payment.')) {
|
|
92
|
+
setLoading(true);
|
|
93
|
+
try {
|
|
94
|
+
await api.delete(`/services/${id}`);
|
|
95
|
+
setMessage({ type: 'success', text: '🗑️ Service record deleted successfully!' });
|
|
96
|
+
fetchData();
|
|
97
|
+
setTimeout(() => setMessage(null), 3000);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
setMessage({ type: 'error', text: err.response?.data?.error || 'Delete failed' });
|
|
100
|
+
} finally {
|
|
101
|
+
setLoading(false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const handleViewBill = async (recordNumber) => {
|
|
107
|
+
try {
|
|
108
|
+
const response = await api.get(`/reports/bill/${recordNumber}`);
|
|
109
|
+
setShowBill(response.data.data);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
setMessage({ type: 'error', text: 'Bill not found. Please record payment first.' });
|
|
112
|
+
setTimeout(() => setMessage(null), 3000);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const closeBill = () => {
|
|
117
|
+
setShowBill(null);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const printBill = () => {
|
|
121
|
+
window.print();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className="max-w-7xl mx-auto animate-fade-in space-y-6">
|
|
126
|
+
{/* Bill Modal */}
|
|
127
|
+
{showBill && (
|
|
128
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in" onClick={closeBill}>
|
|
129
|
+
<div className="glass-card max-w-2xl w-full p-8 animate-slide-up" onClick={(e) => e.stopPropagation()}>
|
|
130
|
+
<div className="text-center mb-6" id="bill-content">
|
|
131
|
+
<div className="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl flex items-center justify-center">
|
|
132
|
+
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
133
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
134
|
+
</svg>
|
|
135
|
+
</div>
|
|
136
|
+
<h2 className="text-2xl font-bold text-white mb-2">SmartPark Car Wash</h2>
|
|
137
|
+
<p className="text-white/60 text-sm">{showBill.companyAddress}</p>
|
|
138
|
+
<p className="text-white/40 text-xs mt-2">Receipt: {showBill.receiptNumber}</p>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="space-y-3 text-white border-t border-b border-white/20 py-4 my-4">
|
|
142
|
+
<div className="flex justify-between">
|
|
143
|
+
<span className="text-white/60">Plate Number:</span>
|
|
144
|
+
<span className="font-mono font-bold">{showBill.PlateNumber}</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="flex justify-between">
|
|
147
|
+
<span className="text-white/60">Driver:</span>
|
|
148
|
+
<span>{showBill.DriverName}</span>
|
|
149
|
+
</div>
|
|
150
|
+
<div className="flex justify-between">
|
|
151
|
+
<span className="text-white/60">Phone:</span>
|
|
152
|
+
<span>{showBill.PhoneNumber}</span>
|
|
153
|
+
</div>
|
|
154
|
+
<div className="flex justify-between">
|
|
155
|
+
<span className="text-white/60">Service Date:</span>
|
|
156
|
+
<span>{new Date(showBill.ServiceDate).toLocaleDateString()}</span>
|
|
157
|
+
</div>
|
|
158
|
+
<div className="flex justify-between">
|
|
159
|
+
<span className="text-white/60">Package:</span>
|
|
160
|
+
<span className="font-semibold">{showBill.PackageName}</span>
|
|
161
|
+
</div>
|
|
162
|
+
<div className="flex justify-between">
|
|
163
|
+
<span className="text-white/60">Package Price:</span>
|
|
164
|
+
<span>{showBill.PackagePrice.toLocaleString()} RWF</span>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="flex justify-between pt-2 border-t border-white/20">
|
|
167
|
+
<span className="text-white/60">Amount Paid:</span>
|
|
168
|
+
<span className="text-green-400 font-bold">{showBill.AmountPaid.toLocaleString()} RWF</span>
|
|
169
|
+
</div>
|
|
170
|
+
{showBill.ChangeAmount > 0 && (
|
|
171
|
+
<div className="flex justify-between">
|
|
172
|
+
<span className="text-white/60">Change:</span>
|
|
173
|
+
<span className="text-yellow-400">{showBill.ChangeAmount.toLocaleString()} RWF</span>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
<div className="flex justify-between">
|
|
177
|
+
<span className="text-white/60">Payment Date:</span>
|
|
178
|
+
<span>{new Date(showBill.PaymentDate).toLocaleDateString()}</span>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div className="text-center text-white/40 text-sm mt-4">
|
|
183
|
+
Thank you for choosing SmartPark!
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div className="flex space-x-3 mt-6">
|
|
187
|
+
<button onClick={printBill} className="glass-button flex-1">
|
|
188
|
+
🖨️ Print
|
|
189
|
+
</button>
|
|
190
|
+
<button onClick={closeBill} className="glass-button-secondary flex-1">
|
|
191
|
+
Close
|
|
192
|
+
</button>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{/* Form Section */}
|
|
199
|
+
<div className="glass-card p-8">
|
|
200
|
+
<div className="flex items-center space-x-3 mb-6">
|
|
201
|
+
<div className="w-12 h-12 bg-gradient-to-br from-purple-500 to-pink-600 rounded-xl flex items-center justify-center">
|
|
202
|
+
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
203
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
204
|
+
</svg>
|
|
205
|
+
</div>
|
|
206
|
+
<div>
|
|
207
|
+
<h2 className="text-2xl font-bold text-white">
|
|
208
|
+
{editingId ? 'Edit Service Record' : 'New Service Record'}
|
|
209
|
+
</h2>
|
|
210
|
+
<p className="text-white/60 text-sm">
|
|
211
|
+
{editingId ? 'Update service information' : 'Record a car wash service'}
|
|
212
|
+
</p>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
{message && (
|
|
217
|
+
<div className={`mb-6 p-4 rounded-lg backdrop-blur-sm ${
|
|
218
|
+
message.type === 'success'
|
|
219
|
+
? 'bg-green-500/20 border border-green-500/50 text-green-200'
|
|
220
|
+
: 'bg-red-500/20 border border-red-500/50 text-red-200'
|
|
221
|
+
} animate-fade-in`}>
|
|
222
|
+
{message.text}
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
227
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
228
|
+
<div>
|
|
229
|
+
<label className="text-white/80 text-sm block mb-2">Service Date *</label>
|
|
230
|
+
<input
|
|
231
|
+
type="date"
|
|
232
|
+
name="ServiceDate"
|
|
233
|
+
value={formData.ServiceDate}
|
|
234
|
+
onChange={handleChange}
|
|
235
|
+
className="glass-input w-full"
|
|
236
|
+
required
|
|
237
|
+
/>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div>
|
|
241
|
+
<label className="text-white/80 text-sm block mb-2">Plate Number *</label>
|
|
242
|
+
<select
|
|
243
|
+
name="PlateNumber"
|
|
244
|
+
value={formData.PlateNumber}
|
|
245
|
+
onChange={handleChange}
|
|
246
|
+
className="glass-input w-full"
|
|
247
|
+
required
|
|
248
|
+
>
|
|
249
|
+
<option value="">Select car</option>
|
|
250
|
+
{cars.map(car => (
|
|
251
|
+
<option key={car.PlateNumber} value={car.PlateNumber}>
|
|
252
|
+
{car.PlateNumber} - {car.DriverName}
|
|
253
|
+
</option>
|
|
254
|
+
))}
|
|
255
|
+
</select>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div>
|
|
259
|
+
<label className="text-white/80 text-sm block mb-2">Service Package *</label>
|
|
260
|
+
<select
|
|
261
|
+
name="PackageNumber"
|
|
262
|
+
value={formData.PackageNumber}
|
|
263
|
+
onChange={handleChange}
|
|
264
|
+
className="glass-input w-full"
|
|
265
|
+
required
|
|
266
|
+
>
|
|
267
|
+
<option value="">Select package</option>
|
|
268
|
+
{packages.map(pkg => (
|
|
269
|
+
<option key={pkg.PackageNumber} value={pkg.PackageNumber}>
|
|
270
|
+
{pkg.PackageName} - {pkg.PackagePrice.toLocaleString()} RWF
|
|
271
|
+
</option>
|
|
272
|
+
))}
|
|
273
|
+
</select>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div className="flex justify-end space-x-3 pt-4">
|
|
278
|
+
{editingId && (
|
|
279
|
+
<button
|
|
280
|
+
type="button"
|
|
281
|
+
onClick={() => {
|
|
282
|
+
setEditingId(null);
|
|
283
|
+
setFormData({
|
|
284
|
+
ServiceDate: new Date().toISOString().split('T')[0],
|
|
285
|
+
PlateNumber: '',
|
|
286
|
+
PackageNumber: ''
|
|
287
|
+
});
|
|
288
|
+
}}
|
|
289
|
+
className="glass-button-secondary"
|
|
290
|
+
>
|
|
291
|
+
Cancel
|
|
292
|
+
</button>
|
|
293
|
+
)}
|
|
294
|
+
<button
|
|
295
|
+
type="submit"
|
|
296
|
+
disabled={loading}
|
|
297
|
+
className="glass-button disabled:opacity-50"
|
|
298
|
+
>
|
|
299
|
+
{loading ? 'Processing...' : (editingId ? 'Update Record' : 'Create Record')}
|
|
300
|
+
</button>
|
|
301
|
+
</div>
|
|
302
|
+
</form>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
{/* Services List */}
|
|
306
|
+
<div className="glass-card p-8">
|
|
307
|
+
<div className="flex justify-between items-center mb-6">
|
|
308
|
+
<h3 className="text-xl font-bold text-white">Service Records</h3>
|
|
309
|
+
<div className="text-white/60 text-sm">
|
|
310
|
+
Total: {services.length} records
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<div className="overflow-x-auto">
|
|
315
|
+
<table className="glass-table w-full">
|
|
316
|
+
<thead>
|
|
317
|
+
<tr>
|
|
318
|
+
<th>Date</th>
|
|
319
|
+
<th>Plate</th>
|
|
320
|
+
<th>Driver</th>
|
|
321
|
+
<th>Package</th>
|
|
322
|
+
<th>Price</th>
|
|
323
|
+
<th>Payment</th>
|
|
324
|
+
<th>Actions</th>
|
|
325
|
+
</tr>
|
|
326
|
+
</thead>
|
|
327
|
+
<tbody>
|
|
328
|
+
{services.length === 0 ? (
|
|
329
|
+
<tr>
|
|
330
|
+
<td colSpan="7" className="text-center text-white/40 py-8">
|
|
331
|
+
No service records found
|
|
332
|
+
</td>
|
|
333
|
+
</tr>
|
|
334
|
+
) : (
|
|
335
|
+
services.map((service) => (
|
|
336
|
+
<tr key={service.RecordNumber}>
|
|
337
|
+
<td>{new Date(service.ServiceDate).toLocaleDateString()}</td>
|
|
338
|
+
<td className="font-mono">{service.PlateNumber}</td>
|
|
339
|
+
<td>{service.DriverName}</td>
|
|
340
|
+
<td>{service.PackageName}</td>
|
|
341
|
+
<td>{service.PackagePrice?.toLocaleString()} RWF</td>
|
|
342
|
+
<td>
|
|
343
|
+
{service.AmountPaid ? (
|
|
344
|
+
<span className="text-green-400">✓ {service.AmountPaid.toLocaleString()} RWF</span>
|
|
345
|
+
) : (
|
|
346
|
+
<span className="text-yellow-400">Pending</span>
|
|
347
|
+
)}
|
|
348
|
+
</td>
|
|
349
|
+
<td>
|
|
350
|
+
<div className="flex space-x-2">
|
|
351
|
+
<button
|
|
352
|
+
onClick={() => handleEdit(service)}
|
|
353
|
+
className="text-blue-300 hover:text-blue-200 text-sm"
|
|
354
|
+
>
|
|
355
|
+
Edit
|
|
356
|
+
</button>
|
|
357
|
+
<button
|
|
358
|
+
onClick={() => handleDelete(service.RecordNumber)}
|
|
359
|
+
className="text-red-300 hover:text-red-200 text-sm"
|
|
360
|
+
>
|
|
361
|
+
Delete
|
|
362
|
+
</button>
|
|
363
|
+
{service.AmountPaid && (
|
|
364
|
+
<button
|
|
365
|
+
onClick={() => handleViewBill(service.RecordNumber)}
|
|
366
|
+
className="text-green-300 hover:text-green-200 text-sm"
|
|
367
|
+
>
|
|
368
|
+
Bill
|
|
369
|
+
</button>
|
|
370
|
+
)}
|
|
371
|
+
</div>
|
|
372
|
+
</td>
|
|
373
|
+
</tr>
|
|
374
|
+
))
|
|
375
|
+
)}
|
|
376
|
+
</tbody>
|
|
377
|
+
</table>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export default ServiceRecord;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
body {
|
|
7
|
+
background: linear-gradient(135deg, #4c1d95, #1e3a8a, #312e81);
|
|
8
|
+
min-height: 100vh;
|
|
9
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@layer components {
|
|
14
|
+
.glass-card {
|
|
15
|
+
background: rgba(255, 255, 255, 0.2);
|
|
16
|
+
backdrop-filter: blur(20px);
|
|
17
|
+
border-radius: 1rem;
|
|
18
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
19
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
20
|
+
padding: 2rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.glass-input {
|
|
24
|
+
background: rgba(255, 255, 255, 0.2);
|
|
25
|
+
backdrop-filter: blur(12px);
|
|
26
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
27
|
+
border-radius: 0.5rem;
|
|
28
|
+
padding: 0.75rem 1rem;
|
|
29
|
+
color: white;
|
|
30
|
+
width: 100%;
|
|
31
|
+
transition: all 0.3s ease;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.glass-input:focus {
|
|
35
|
+
outline: none;
|
|
36
|
+
border-color: #60a5fa;
|
|
37
|
+
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.3);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.glass-input::placeholder {
|
|
41
|
+
color: rgba(255, 255, 255, 0.5);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.glass-button {
|
|
45
|
+
background: linear-gradient(135deg, #3b82f6, #9333ea);
|
|
46
|
+
color: white;
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
padding: 0.75rem 2rem;
|
|
49
|
+
border-radius: 0.5rem;
|
|
50
|
+
transition: all 0.3s ease;
|
|
51
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
52
|
+
width: 100%;
|
|
53
|
+
border: none;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.glass-button:hover {
|
|
58
|
+
transform: scale(1.05);
|
|
59
|
+
background: linear-gradient(135deg, #2563eb, #7e22ce);
|
|
60
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.glass-button:disabled {
|
|
64
|
+
opacity: 0.5;
|
|
65
|
+
cursor: not-allowed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.glass-button-secondary {
|
|
69
|
+
background: rgba(255, 255, 255, 0.2);
|
|
70
|
+
backdrop-filter: blur(12px);
|
|
71
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
72
|
+
color: white;
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
padding: 0.5rem 1.5rem;
|
|
75
|
+
border-radius: 0.5rem;
|
|
76
|
+
transition: all 0.3s ease;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.glass-button-secondary:hover {
|
|
81
|
+
background: rgba(255, 255, 255, 0.3);
|
|
82
|
+
transform: scale(1.05);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.glass-table {
|
|
86
|
+
width: 100%;
|
|
87
|
+
color: white;
|
|
88
|
+
border-collapse: collapse;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.glass-table th {
|
|
92
|
+
background: rgba(255, 255, 255, 0.2);
|
|
93
|
+
backdrop-filter: blur(12px);
|
|
94
|
+
padding: 0.75rem;
|
|
95
|
+
text-align: left;
|
|
96
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.glass-table td {
|
|
100
|
+
padding: 0.75rem;
|
|
101
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.glass-table tr:hover {
|
|
105
|
+
background: rgba(255, 255, 255, 0.1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Custom animations */
|
|
110
|
+
@keyframes fadeIn {
|
|
111
|
+
from {
|
|
112
|
+
opacity: 0;
|
|
113
|
+
transform: translateY(-20px);
|
|
114
|
+
}
|
|
115
|
+
to {
|
|
116
|
+
opacity: 1;
|
|
117
|
+
transform: translateY(0);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@keyframes float {
|
|
122
|
+
0%, 100% {
|
|
123
|
+
transform: translateY(0px);
|
|
124
|
+
}
|
|
125
|
+
50% {
|
|
126
|
+
transform: translateY(-20px);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.animate-fade-in {
|
|
131
|
+
animation: fadeIn 0.5s ease-in-out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.animate-float {
|
|
135
|
+
animation: float 6s ease-in-out infinite;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Custom scrollbar */
|
|
139
|
+
::-webkit-scrollbar {
|
|
140
|
+
width: 8px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
::-webkit-scrollbar-track {
|
|
144
|
+
background: rgba(255, 255, 255, 0.1);
|
|
145
|
+
border-radius: 10px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
::-webkit-scrollbar-thumb {
|
|
149
|
+
background: rgba(255, 255, 255, 0.3);
|
|
150
|
+
border-radius: 10px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
::-webkit-scrollbar-thumb:hover {
|
|
154
|
+
background: rgba(255, 255, 255, 0.5);
|
|
155
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
const api = axios.create({
|
|
4
|
+
baseURL: 'http://localhost:5000/api',
|
|
5
|
+
withCredentials: true,
|
|
6
|
+
headers: {
|
|
7
|
+
'Content-Type': 'application/json',
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// Add response interceptor for error handling
|
|
12
|
+
api.interceptors.response.use(
|
|
13
|
+
(response) => response,
|
|
14
|
+
(error) => {
|
|
15
|
+
if (error.response?.status === 401) {
|
|
16
|
+
// Redirect to login if unauthorized
|
|
17
|
+
window.location.href = '/login';
|
|
18
|
+
}
|
|
19
|
+
return Promise.reject(error);
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export default api;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
content: [
|
|
4
|
+
"./index.html",
|
|
5
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
6
|
+
],
|
|
7
|
+
theme: {
|
|
8
|
+
extend: {
|
|
9
|
+
animation: {
|
|
10
|
+
'fade-in': 'fadeIn 0.5s ease-in-out',
|
|
11
|
+
'slide-up': 'slideUp 0.3s ease-out',
|
|
12
|
+
'float': 'float 6s ease-in-out infinite',
|
|
13
|
+
},
|
|
14
|
+
keyframes: {
|
|
15
|
+
fadeIn: {
|
|
16
|
+
'0%': { opacity: '0', transform: 'translateY(-20px)' },
|
|
17
|
+
'100%': { opacity: '1', transform: 'translateY(0)' },
|
|
18
|
+
},
|
|
19
|
+
slideUp: {
|
|
20
|
+
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
|
21
|
+
'100%': { transform: 'translateY(0)', opacity: '1' },
|
|
22
|
+
},
|
|
23
|
+
float: {
|
|
24
|
+
'0%, 100%': { transform: 'translateY(0px)' },
|
|
25
|
+
'50%': { transform: 'translateY(-20px)' },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
plugins: [],
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
server: {
|
|
7
|
+
port: 5173,
|
|
8
|
+
proxy: {
|
|
9
|
+
'/api': {
|
|
10
|
+
target: 'http://localhost:5000',
|
|
11
|
+
changeOrigin: true,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "krispdev-business",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "A full-stack business management application with Express backend and React frontend",
|
|
6
|
+
"main": "Dev-Business/backend-project/server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node Dev-Business/backend-project/server.js",
|
|
9
|
+
"dev": "nodemon Dev-Business/backend-project/server.js",
|
|
10
|
+
"install:all": "cd Dev-Business/backend-project && npm install && cd ../frontend-project && npm install",
|
|
11
|
+
"build:frontend": "cd Dev-Business/frontend-project && npm run build",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["business-management", "express", "react", "full-stack", "mysql"],
|
|
15
|
+
"author": "isezeranochrispin",
|
|
16
|
+
"license": "ISC"
|
|
17
|
+
}
|