wad-lab 1.0.8 → 1.1.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.
Files changed (62) hide show
  1. package/bin/index.js +15 -8
  2. package/package.json +1 -1
  3. package/templates/springboot-hospital-api/.mvn/wrapper/maven-wrapper.properties +3 -0
  4. package/templates/springboot-hospital-api/mvnw +295 -0
  5. package/templates/springboot-hospital-api/mvnw.cmd +189 -0
  6. package/templates/springboot-hospital-api/pom.xml +41 -0
  7. package/templates/springboot-hospital-api/src/main/java/com/example/hospital/HospitalApplication.java +12 -0
  8. package/templates/springboot-hospital-api/src/main/java/com/example/hospital/controller/HospitalController.java +50 -0
  9. package/templates/springboot-hospital-api/src/main/java/com/example/hospital/model/Appointment.java +95 -0
  10. package/templates/springboot-hospital-api/src/main/java/com/example/hospital/repository/AppointmentRepository.java +15 -0
  11. package/templates/springboot-hospital-api/src/main/java/com/example/hospital/service/HospitalService.java +54 -0
  12. package/templates/springboot-hospital-api/src/main/resources/application.properties +3 -0
  13. package/templates/springboot-hospital-api/src/test/java/com/example/hospital/HospitalApplicationTests.java +12 -0
  14. package/templates/springboot-library-api/.mvn/wrapper/maven-wrapper.properties +3 -0
  15. package/templates/springboot-library-api/mvnw +295 -0
  16. package/templates/springboot-library-api/mvnw.cmd +189 -0
  17. package/templates/springboot-library-api/pom.xml +42 -0
  18. package/templates/springboot-library-api/src/main/java/com/example/library/LibraryApplication.java +12 -0
  19. package/templates/springboot-library-api/src/main/java/com/example/library/controller/LibraryController.java +51 -0
  20. package/templates/springboot-library-api/src/main/java/com/example/library/model/Book.java +76 -0
  21. package/templates/springboot-library-api/src/main/java/com/example/library/model/CategoryAnalytics.java +41 -0
  22. package/templates/springboot-library-api/src/main/java/com/example/library/repository/BookRepository.java +13 -0
  23. package/templates/springboot-library-api/src/main/java/com/example/library/service/LibraryService.java +60 -0
  24. package/templates/springboot-library-api/src/main/resources/application.properties +3 -0
  25. package/templates/vue-game-rps/index.html +12 -0
  26. package/templates/vue-game-rps/package.json +19 -0
  27. package/templates/vue-game-rps/src/App.vue +125 -0
  28. package/templates/vue-game-rps/src/main.js +5 -0
  29. package/templates/vue-game-rps/src/stores/rpsStore.js +43 -0
  30. package/templates/vue-game-rps/vite.config.js +6 -0
  31. package/templates/vue-game-tictactoe/index.html +12 -0
  32. package/templates/vue-game-tictactoe/package.json +19 -0
  33. package/templates/vue-game-tictactoe/src/App.vue +105 -0
  34. package/templates/vue-game-tictactoe/src/main.js +5 -0
  35. package/templates/vue-game-tictactoe/src/stores/tictactoeStore.js +61 -0
  36. package/templates/vue-game-tictactoe/vite.config.js +6 -0
  37. package/templates/vue-game-whackamole/index.html +12 -0
  38. package/templates/vue-game-whackamole/package.json +19 -0
  39. package/templates/vue-game-whackamole/src/App.vue +104 -0
  40. package/templates/vue-game-whackamole/src/main.js +7 -0
  41. package/templates/vue-game-whackamole/src/stores/moleStore.js +71 -0
  42. package/templates/vue-game-whackamole/vite.config.js +6 -0
  43. package/templates/vue-hospital-portal/index.html +12 -0
  44. package/templates/vue-hospital-portal/package.json +20 -0
  45. package/templates/vue-hospital-portal/src/App.vue +107 -0
  46. package/templates/vue-hospital-portal/src/main.js +9 -0
  47. package/templates/vue-hospital-portal/src/router/index.js +17 -0
  48. package/templates/vue-hospital-portal/src/stores/hospitalStore.js +91 -0
  49. package/templates/vue-hospital-portal/src/views/HospitalBillingView.vue +271 -0
  50. package/templates/vue-hospital-portal/src/views/OPDRegistrationView.vue +453 -0
  51. package/templates/vue-hospital-portal/src/views/PatientListView.vue +315 -0
  52. package/templates/vue-hospital-portal/vite.config.js +6 -0
  53. package/templates/vue-library-system/index.html +12 -0
  54. package/templates/vue-library-system/package.json +20 -0
  55. package/templates/vue-library-system/src/App.vue +91 -0
  56. package/templates/vue-library-system/src/main.js +9 -0
  57. package/templates/vue-library-system/src/router/index.js +29 -0
  58. package/templates/vue-library-system/src/stores/libraryStore.js +87 -0
  59. package/templates/vue-library-system/src/views/BookCatalogView.vue +432 -0
  60. package/templates/vue-library-system/src/views/LoanManagerView.vue +296 -0
  61. package/templates/vue-library-system/src/views/MembershipView.vue +269 -0
  62. package/templates/vue-library-system/vite.config.js +6 -0
@@ -0,0 +1,432 @@
1
+ <script setup>
2
+ import { ref, computed } from 'vue'
3
+ import { useLibraryStore } from '../stores/libraryStore'
4
+
5
+ const libraryStore = useLibraryStore()
6
+
7
+ const searchQuery = ref('')
8
+ const selectedCategory = ref('')
9
+ const selectedMemberId = ref({})
10
+ const notification = ref('')
11
+
12
+ const newBook = ref({
13
+ title: '',
14
+ author: '',
15
+ isbn: '',
16
+ category: 'Computer Science',
17
+ copies: 1
18
+ })
19
+
20
+ const categories = ['Computer Science', 'Web Dev', 'Algorithms', 'Fiction', 'Science']
21
+
22
+ const filteredBooks = computed(() => {
23
+ return libraryStore.books.filter((book) => {
24
+ const matchesSearch =
25
+ book.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
26
+ book.author.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
27
+ book.isbn.includes(searchQuery.value)
28
+ const matchesCategory =
29
+ !selectedCategory.value || book.category === selectedCategory.value
30
+ return matchesSearch && matchesCategory
31
+ })
32
+ })
33
+
34
+ function handleIssueBook(bookId) {
35
+ const memberId = selectedMemberId.value[bookId]
36
+ if (!memberId) {
37
+ notification.value = 'Please select a member before issuing.'
38
+ return
39
+ }
40
+ const success = libraryStore.issueBook(bookId, memberId)
41
+ if (success) {
42
+ notification.value = 'Book successfully issued!'
43
+ selectedMemberId.value[bookId] = ''
44
+ } else {
45
+ notification.value = 'Unable to issue book. Check copy availability.'
46
+ }
47
+ setTimeout(() => {
48
+ notification.value = ''
49
+ }, 3000)
50
+ }
51
+
52
+ function handleAddBook() {
53
+ if (!newBook.value.title || !newBook.value.author || !newBook.value.isbn) {
54
+ return
55
+ }
56
+ libraryStore.books.push({
57
+ id: Date.now(),
58
+ title: newBook.value.title,
59
+ author: newBook.value.author,
60
+ isbn: newBook.value.isbn,
61
+ category: newBook.value.category,
62
+ copies: Number(newBook.value.copies) || 1
63
+ })
64
+ newBook.value = {
65
+ title: '',
66
+ author: '',
67
+ isbn: '',
68
+ category: 'Computer Science',
69
+ copies: 1
70
+ }
71
+ notification.value = 'New book added to catalog.'
72
+ setTimeout(() => {
73
+ notification.value = ''
74
+ }, 3000)
75
+ }
76
+ </script>
77
+
78
+ <template>
79
+ <div class="catalog-page">
80
+ <div class="stats-bar">
81
+ <div class="stat-card">
82
+ <span class="stat-num">{{ libraryStore.books.length }}</span>
83
+ <span class="stat-label">Total Titles</span>
84
+ </div>
85
+ <div class="stat-card">
86
+ <span class="stat-num">{{ libraryStore.availableBooks.length }}</span>
87
+ <span class="stat-label">Available Titles</span>
88
+ </div>
89
+ <div class="stat-card">
90
+ <span class="stat-num">{{ libraryStore.activeLoansCount }}</span>
91
+ <span class="stat-label">Active Loans</span>
92
+ </div>
93
+ <div class="stat-card">
94
+ <span class="stat-num">${{ libraryStore.totalFines }}</span>
95
+ <span class="stat-label">Accrued Fines</span>
96
+ </div>
97
+ </div>
98
+
99
+ <div v-if="notification" class="alert-box">
100
+ {{ notification }}
101
+ </div>
102
+
103
+ <div class="controls-row">
104
+ <input
105
+ v-model="searchQuery"
106
+ type="text"
107
+ placeholder="Search title, author, or ISBN..."
108
+ class="search-input"
109
+ />
110
+ <select v-model="selectedCategory" class="filter-select">
111
+ <option value="">All Categories</option>
112
+ <option v-for="cat in categories" :key="cat" :value="cat">
113
+ {{ cat }}
114
+ </option>
115
+ </select>
116
+ </div>
117
+
118
+ <div class="table-card">
119
+ <h2>Book Inventory</h2>
120
+ <table class="data-table">
121
+ <thead>
122
+ <tr>
123
+ <th>ID</th>
124
+ <th>Title</th>
125
+ <th>Author</th>
126
+ <th>ISBN</th>
127
+ <th>Category</th>
128
+ <th>Copies</th>
129
+ <th>Availability</th>
130
+ <th>Issue Book</th>
131
+ </tr>
132
+ </thead>
133
+ <tbody>
134
+ <tr v-for="book in filteredBooks" :key="book.id">
135
+ <td>#{{ book.id }}</td>
136
+ <td class="font-bold">{{ book.title }}</td>
137
+ <td>{{ book.author }}</td>
138
+ <td><code>{{ book.isbn }}</code></td>
139
+ <td><span class="tag">{{ book.category }}</span></td>
140
+ <td>
141
+ <span class="copies-count" :class="{ 'zero-copies': book.copies === 0 }">
142
+ {{ book.copies }}
143
+ </span>
144
+ </td>
145
+ <td>
146
+ <span :class="['status-badge', book.copies > 0 ? 'in-stock' : 'out-stock']">
147
+ {{ book.copies > 0 ? 'In Stock' : 'Out of Stock' }}
148
+ </span>
149
+ </td>
150
+ <td>
151
+ <div class="issue-controls">
152
+ <select v-model="selectedMemberId[book.id]" class="member-select" :disabled="book.copies === 0">
153
+ <option value="" disabled selected>Select Member</option>
154
+ <option v-for="member in libraryStore.members" :key="member.id" :value="member.id">
155
+ {{ member.name }} ({{ member.id }})
156
+ </option>
157
+ </select>
158
+ <button
159
+ class="btn-issue"
160
+ :disabled="book.copies === 0 || !selectedMemberId[book.id]"
161
+ @click="handleIssueBook(book.id)"
162
+ >
163
+ Issue
164
+ </button>
165
+ </div>
166
+ </td>
167
+ </tr>
168
+ <tr v-if="filteredBooks.length === 0">
169
+ <td colspan="8" class="text-center">No books match your criteria.</td>
170
+ </tr>
171
+ </tbody>
172
+ </table>
173
+ </div>
174
+
175
+ <div class="form-card">
176
+ <h3>Add New Book to Inventory</h3>
177
+ <form class="add-form" @submit.prevent="handleAddBook">
178
+ <div class="form-group">
179
+ <label>Book Title</label>
180
+ <input v-model="newBook.title" type="text" required placeholder="e.g. Design Patterns" />
181
+ </div>
182
+ <div class="form-group">
183
+ <label>Author</label>
184
+ <input v-model="newBook.author" type="text" required placeholder="e.g. Gang of Four" />
185
+ </div>
186
+ <div class="form-group">
187
+ <label>ISBN</label>
188
+ <input v-model="newBook.isbn" type="text" required placeholder="e.g. 978-0201633610" />
189
+ </div>
190
+ <div class="form-group">
191
+ <label>Category</label>
192
+ <select v-model="newBook.category">
193
+ <option v-for="cat in categories" :key="cat" :value="cat">{{ cat }}</option>
194
+ </select>
195
+ </div>
196
+ <div class="form-group">
197
+ <label>Copies</label>
198
+ <input v-model.number="newBook.copies" type="number" min="1" required />
199
+ </div>
200
+ <button type="submit" class="btn-submit">Add Book</button>
201
+ </form>
202
+ </div>
203
+ </div>
204
+ </template>
205
+
206
+ <style scoped>
207
+ .catalog-page {
208
+ display: flex;
209
+ flex-direction: column;
210
+ gap: 20px;
211
+ }
212
+
213
+ .stats-bar {
214
+ display: grid;
215
+ grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
216
+ gap: 15px;
217
+ }
218
+
219
+ .stat-card {
220
+ background: white;
221
+ padding: 16px;
222
+ border-radius: 6px;
223
+ border: 1px solid #e2e8f0;
224
+ display: flex;
225
+ flex-direction: column;
226
+ align-items: center;
227
+ }
228
+
229
+ .stat-num {
230
+ font-size: 24px;
231
+ font-weight: 700;
232
+ color: #1e293b;
233
+ }
234
+
235
+ .stat-label {
236
+ font-size: 13px;
237
+ color: #64748b;
238
+ margin-top: 4px;
239
+ }
240
+
241
+ .alert-box {
242
+ background-color: #e0f2fe;
243
+ color: #0369a1;
244
+ border: 1px solid #bae6fd;
245
+ padding: 12px 16px;
246
+ border-radius: 6px;
247
+ font-size: 14px;
248
+ }
249
+
250
+ .controls-row {
251
+ display: flex;
252
+ gap: 12px;
253
+ }
254
+
255
+ .search-input {
256
+ flex: 1;
257
+ padding: 8px 12px;
258
+ border: 1px solid #cbd5e1;
259
+ border-radius: 4px;
260
+ font-size: 14px;
261
+ }
262
+
263
+ .filter-select {
264
+ padding: 8px 12px;
265
+ border: 1px solid #cbd5e1;
266
+ border-radius: 4px;
267
+ font-size: 14px;
268
+ background-color: white;
269
+ }
270
+
271
+ .table-card {
272
+ background: white;
273
+ border: 1px solid #e2e8f0;
274
+ border-radius: 6px;
275
+ padding: 20px;
276
+ overflow-x: auto;
277
+ }
278
+
279
+ .table-card h2 {
280
+ margin: 0 0 16px 0;
281
+ font-size: 18px;
282
+ color: #1e293b;
283
+ }
284
+
285
+ .data-table {
286
+ width: 100%;
287
+ border-collapse: collapse;
288
+ font-size: 14px;
289
+ text-align: left;
290
+ }
291
+
292
+ .data-table th,
293
+ .data-table td {
294
+ padding: 10px 12px;
295
+ border-bottom: 1px solid #e2e8f0;
296
+ }
297
+
298
+ .data-table th {
299
+ background-color: #f8fafc;
300
+ color: #475569;
301
+ font-weight: 600;
302
+ }
303
+
304
+ .font-bold {
305
+ font-weight: 600;
306
+ }
307
+
308
+ .tag {
309
+ background-color: #f1f5f9;
310
+ color: #475569;
311
+ padding: 3px 8px;
312
+ border-radius: 4px;
313
+ font-size: 12px;
314
+ }
315
+
316
+ .copies-count {
317
+ font-weight: 600;
318
+ color: #0f172a;
319
+ }
320
+
321
+ .copies-count.zero-copies {
322
+ color: #ef4444;
323
+ }
324
+
325
+ .status-badge {
326
+ display: inline-block;
327
+ padding: 3px 8px;
328
+ border-radius: 4px;
329
+ font-size: 12px;
330
+ font-weight: 600;
331
+ }
332
+
333
+ .status-badge.in-stock {
334
+ background-color: #dcfce7;
335
+ color: #166534;
336
+ }
337
+
338
+ .status-badge.out-stock {
339
+ background-color: #fee2e2;
340
+ color: #991b1b;
341
+ }
342
+
343
+ .issue-controls {
344
+ display: flex;
345
+ gap: 8px;
346
+ align-items: center;
347
+ }
348
+
349
+ .member-select {
350
+ padding: 6px 8px;
351
+ border: 1px solid #cbd5e1;
352
+ border-radius: 4px;
353
+ font-size: 13px;
354
+ max-width: 150px;
355
+ }
356
+
357
+ .btn-issue {
358
+ background-color: #2563eb;
359
+ color: white;
360
+ border: none;
361
+ padding: 6px 12px;
362
+ border-radius: 4px;
363
+ cursor: pointer;
364
+ font-size: 13px;
365
+ }
366
+
367
+ .btn-issue:disabled {
368
+ background-color: #94a3b8;
369
+ cursor: not-allowed;
370
+ }
371
+
372
+ .text-center {
373
+ text-align: center;
374
+ color: #64748b;
375
+ padding: 20px 0;
376
+ }
377
+
378
+ .form-card {
379
+ background: white;
380
+ border: 1px solid #e2e8f0;
381
+ border-radius: 6px;
382
+ padding: 20px;
383
+ }
384
+
385
+ .form-card h3 {
386
+ margin: 0 0 16px 0;
387
+ font-size: 16px;
388
+ color: #1e293b;
389
+ }
390
+
391
+ .add-form {
392
+ display: grid;
393
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
394
+ gap: 12px;
395
+ align-items: flex-end;
396
+ }
397
+
398
+ .form-group {
399
+ display: flex;
400
+ flex-direction: column;
401
+ gap: 6px;
402
+ }
403
+
404
+ .form-group label {
405
+ font-size: 12px;
406
+ font-weight: 600;
407
+ color: #475569;
408
+ }
409
+
410
+ .form-group input,
411
+ .form-group select {
412
+ padding: 8px 10px;
413
+ border: 1px solid #cbd5e1;
414
+ border-radius: 4px;
415
+ font-size: 14px;
416
+ }
417
+
418
+ .btn-submit {
419
+ background-color: #0f172a;
420
+ color: white;
421
+ border: none;
422
+ padding: 9px 16px;
423
+ border-radius: 4px;
424
+ font-size: 14px;
425
+ cursor: pointer;
426
+ font-weight: 500;
427
+ }
428
+
429
+ .btn-submit:hover {
430
+ background-color: #1e293b;
431
+ }
432
+ </style>
@@ -0,0 +1,296 @@
1
+ <script setup>
2
+ import { ref } from 'vue'
3
+ import { useLibraryStore } from '../stores/libraryStore'
4
+
5
+ const libraryStore = useLibraryStore()
6
+
7
+ const daysLateMap = ref({})
8
+ const message = ref('')
9
+
10
+ function handleReturn(recordId) {
11
+ const daysLate = daysLateMap.value[recordId] || 0
12
+ const success = libraryStore.returnBook(recordId, daysLate)
13
+ if (success) {
14
+ const fineCalculated = Math.max(0, Number(daysLate) || 0) * 2
15
+ if (fineCalculated > 0) {
16
+ message.value = `Book returned with ${daysLate} day(s) overdue. Fine assessed: $${fineCalculated}.`
17
+ } else {
18
+ message.value = 'Book returned on time. No fine assessed.'
19
+ }
20
+ }
21
+ setTimeout(() => {
22
+ message.value = ''
23
+ }, 4000)
24
+ }
25
+ </script>
26
+
27
+ <template>
28
+ <div class="loans-page">
29
+ <div class="stats-bar">
30
+ <div class="stat-card">
31
+ <span class="stat-num">{{ libraryStore.activeLoansCount }}</span>
32
+ <span class="stat-label">Active Loans</span>
33
+ </div>
34
+ <div class="stat-card">
35
+ <span class="stat-num">{{ libraryStore.borrowedRecords.length }}</span>
36
+ <span class="stat-label">Total Loan Records</span>
37
+ </div>
38
+ <div class="stat-card">
39
+ <span class="stat-num fine-text">${{ libraryStore.totalFines }}</span>
40
+ <span class="stat-label">Total Fines Collected</span>
41
+ </div>
42
+ </div>
43
+
44
+ <div v-if="message" class="alert-box">
45
+ {{ message }}
46
+ </div>
47
+
48
+ <div class="table-card">
49
+ <div class="card-header">
50
+ <h2>Loan Records & Fine Calculation</h2>
51
+ <span class="rate-badge">Rate: $2.00 / day overdue</span>
52
+ </div>
53
+
54
+ <table class="data-table">
55
+ <thead>
56
+ <tr>
57
+ <th>Loan ID</th>
58
+ <th>Member ID</th>
59
+ <th>Book Title</th>
60
+ <th>Issue Date</th>
61
+ <th>Due Date</th>
62
+ <th>Status</th>
63
+ <th>Fine Assessed</th>
64
+ <th>Days Late</th>
65
+ <th>Action</th>
66
+ </tr>
67
+ </thead>
68
+ <tbody>
69
+ <tr
70
+ v-for="record in libraryStore.borrowedRecords"
71
+ :key="record.id"
72
+ :class="{ 'row-returned': record.returned }"
73
+ >
74
+ <td>#{{ record.id }}</td>
75
+ <td><code>{{ record.memberId }}</code></td>
76
+ <td class="font-bold">{{ record.bookTitle }}</td>
77
+ <td>{{ record.issueDate }}</td>
78
+ <td>{{ record.returnDue }}</td>
79
+ <td>
80
+ <span :class="['status-badge', record.returned ? 'returned' : 'active']">
81
+ {{ record.returned ? 'Returned' : 'Active' }}
82
+ </span>
83
+ </td>
84
+ <td>
85
+ <span :class="['fine-badge', record.fine > 0 ? 'has-fine' : 'zero-fine']">
86
+ ${{ record.fine }}
87
+ </span>
88
+ </td>
89
+ <td>
90
+ <template v-if="!record.returned">
91
+ <input
92
+ v-model.number="daysLateMap[record.id]"
93
+ type="number"
94
+ min="0"
95
+ placeholder="0"
96
+ class="days-input"
97
+ />
98
+ </template>
99
+ <template v-else>
100
+ <span class="text-muted">-</span>
101
+ </template>
102
+ </td>
103
+ <td>
104
+ <button
105
+ v-if="!record.returned"
106
+ class="btn-return"
107
+ @click="handleReturn(record.id)"
108
+ >
109
+ Return Book
110
+ </button>
111
+ <span v-else class="text-muted">Completed</span>
112
+ </td>
113
+ </tr>
114
+ <tr v-if="libraryStore.borrowedRecords.length === 0">
115
+ <td colspan="9" class="text-center">No loan records available.</td>
116
+ </tr>
117
+ </tbody>
118
+ </table>
119
+ </div>
120
+ </div>
121
+ </template>
122
+
123
+ <style scoped>
124
+ .loans-page {
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: 20px;
128
+ }
129
+
130
+ .stats-bar {
131
+ display: grid;
132
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
133
+ gap: 15px;
134
+ }
135
+
136
+ .stat-card {
137
+ background: white;
138
+ padding: 16px;
139
+ border-radius: 6px;
140
+ border: 1px solid #e2e8f0;
141
+ display: flex;
142
+ flex-direction: column;
143
+ align-items: center;
144
+ }
145
+
146
+ .stat-num {
147
+ font-size: 24px;
148
+ font-weight: 700;
149
+ color: #1e293b;
150
+ }
151
+
152
+ .stat-num.fine-text {
153
+ color: #b91c1c;
154
+ }
155
+
156
+ .stat-label {
157
+ font-size: 13px;
158
+ color: #64748b;
159
+ margin-top: 4px;
160
+ }
161
+
162
+ .alert-box {
163
+ background-color: #eff6ff;
164
+ color: #1e40af;
165
+ border: 1px solid #bfdbfe;
166
+ padding: 12px 16px;
167
+ border-radius: 6px;
168
+ font-size: 14px;
169
+ }
170
+
171
+ .table-card {
172
+ background: white;
173
+ border: 1px solid #e2e8f0;
174
+ border-radius: 6px;
175
+ padding: 20px;
176
+ overflow-x: auto;
177
+ }
178
+
179
+ .card-header {
180
+ display: flex;
181
+ justify-content: space-between;
182
+ align-items: center;
183
+ margin-bottom: 16px;
184
+ }
185
+
186
+ .card-header h2 {
187
+ margin: 0;
188
+ font-size: 18px;
189
+ color: #1e293b;
190
+ }
191
+
192
+ .rate-badge {
193
+ background-color: #fef2f2;
194
+ color: #991b1b;
195
+ border: 1px solid #fecaca;
196
+ padding: 4px 10px;
197
+ border-radius: 12px;
198
+ font-size: 12px;
199
+ font-weight: 600;
200
+ }
201
+
202
+ .data-table {
203
+ width: 100%;
204
+ border-collapse: collapse;
205
+ font-size: 14px;
206
+ text-align: left;
207
+ }
208
+
209
+ .data-table th,
210
+ .data-table td {
211
+ padding: 10px 12px;
212
+ border-bottom: 1px solid #e2e8f0;
213
+ }
214
+
215
+ .data-table th {
216
+ background-color: #f8fafc;
217
+ color: #475569;
218
+ font-weight: 600;
219
+ }
220
+
221
+ .row-returned {
222
+ background-color: #fafafa;
223
+ }
224
+
225
+ .font-bold {
226
+ font-weight: 600;
227
+ }
228
+
229
+ .status-badge {
230
+ display: inline-block;
231
+ padding: 3px 8px;
232
+ border-radius: 4px;
233
+ font-size: 12px;
234
+ font-weight: 600;
235
+ }
236
+
237
+ .status-badge.active {
238
+ background-color: #fef3c7;
239
+ color: #92400e;
240
+ }
241
+
242
+ .status-badge.returned {
243
+ background-color: #e2e8f0;
244
+ color: #475569;
245
+ }
246
+
247
+ .fine-badge {
248
+ display: inline-block;
249
+ font-weight: 600;
250
+ padding: 2px 6px;
251
+ border-radius: 4px;
252
+ }
253
+
254
+ .fine-badge.has-fine {
255
+ background-color: #fee2e2;
256
+ color: #b91c1c;
257
+ }
258
+
259
+ .fine-badge.zero-fine {
260
+ color: #64748b;
261
+ }
262
+
263
+ .days-input {
264
+ width: 60px;
265
+ padding: 4px 6px;
266
+ border: 1px solid #cbd5e1;
267
+ border-radius: 4px;
268
+ font-size: 13px;
269
+ }
270
+
271
+ .btn-return {
272
+ background-color: #10b981;
273
+ color: white;
274
+ border: none;
275
+ padding: 6px 12px;
276
+ border-radius: 4px;
277
+ cursor: pointer;
278
+ font-size: 13px;
279
+ font-weight: 500;
280
+ }
281
+
282
+ .btn-return:hover {
283
+ background-color: #059669;
284
+ }
285
+
286
+ .text-muted {
287
+ color: #94a3b8;
288
+ font-size: 13px;
289
+ }
290
+
291
+ .text-center {
292
+ text-align: center;
293
+ color: #64748b;
294
+ padding: 20px 0;
295
+ }
296
+ </style>