wad-lab 1.2.2 → 1.2.3

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 (207) hide show
  1. package/bin/index.js +472 -631
  2. package/package.json +1 -1
  3. package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/App.vue +241 -429
  4. package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/components/ItemFormModal.vue +183 -0
  5. package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/components/ItemPatchModal.vue +193 -0
  6. package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/components/ItemTable.vue +177 -0
  7. package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/App.vue +150 -441
  8. package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/components/ItemFormModal.vue +207 -0
  9. package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/components/ItemStats.vue +64 -0
  10. package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/components/ItemTable.vue +195 -0
  11. package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/App.vue +41 -90
  12. package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/components/ItemFormModal.vue +173 -0
  13. package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/components/ItemTable.vue +102 -0
  14. package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/components/NavBar.vue +57 -0
  15. package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/views/DepartmentView.vue +132 -296
  16. package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/views/EmployeeView.vue +126 -219
  17. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/components/ItemDetail.vue +115 -0
  18. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/components/ItemTable.vue +104 -0
  19. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/components/SideBar.vue +59 -0
  20. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/CompanyLayout.vue +2 -51
  21. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/DepartmentListView.vue +92 -250
  22. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/EmployeeDetailView.vue +59 -181
  23. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/EmployeeListView.vue +67 -315
  24. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/App.vue +19 -70
  25. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/ItemFormModal.vue +173 -0
  26. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/ItemPatchModal.vue +144 -0
  27. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/ItemTable.vue +102 -0
  28. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/NavBar.vue +53 -0
  29. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/views/DepartmentView.vue +107 -229
  30. package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/views/EmployeeView.vue +143 -257
  31. package/templates/game-bingo/script.js +1 -1
  32. package/templates/game-memory-cards/script.js +2 -2
  33. package/templates/game-rock-paper-scissors/index.html +3 -3
  34. package/templates/game-rock-paper-scissors/script.js +2 -2
  35. package/templates/game-tic-tac-toe/script.js +2 -2
  36. package/templates/game-whack-a-mole/index.html +9 -9
  37. package/templates/servlet-basic/WEB-INF/ItemServlet.java +13 -0
  38. package/templates/servlet-basic/WEB-INF/classes/ItemServlet.class +0 -0
  39. package/templates/servlet-flow/WEB-INF/HelloServlet.java +34 -25
  40. package/templates/servlet-flow/WEB-INF/classes/HelloServlet.class +0 -0
  41. package/templates/servlet-jdbc/WEB-INF/JdbcItemServlet.java +13 -0
  42. package/templates/servlet-jdbc/WEB-INF/classes/JdbcItemServlet.class +0 -0
  43. package/templates/snippets/mongo-queries-cheatsheet.txt +105 -0
  44. package/templates/springboot-2model-aggregation-join/.mvn/wrapper/maven-wrapper.properties +3 -0
  45. package/templates/springboot-2model-aggregation-join/mvnw +295 -0
  46. package/templates/springboot-2model-aggregation-join/mvnw.cmd +189 -0
  47. package/templates/springboot-2model-aggregation-join/pom.xml +54 -0
  48. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/AggregationApplication.java +11 -0
  49. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/controller/DepartmentController.java +65 -0
  50. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/controller/EmployeeController.java +65 -0
  51. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/dto/DepartmentStatsDTO.java +15 -0
  52. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/dto/EmployeeJoinDTO.java +19 -0
  53. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/model/Department.java +20 -0
  54. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/model/Employee.java +21 -0
  55. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/repository/DepartmentRepository.java +9 -0
  56. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/repository/EmployeeRepository.java +9 -0
  57. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/service/DepartmentService.java +81 -0
  58. package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/service/EmployeeService.java +80 -0
  59. package/templates/springboot-2model-aggregation-join/src/main/resources/application.properties +2 -0
  60. package/templates/springboot-2model-aggregation-join/src/test/java/com/example/aggregation/AggregationApplicationTests.java +13 -0
  61. package/templates/vue-2model-crud-patch/README.md +64 -0
  62. package/templates/vue-2model-crud-patch/index.html +12 -0
  63. package/templates/vue-2model-crud-patch/package-lock.json +1785 -0
  64. package/templates/vue-2model-crud-patch/package.json +21 -0
  65. package/templates/vue-2model-crud-patch/src/App.vue +63 -0
  66. package/templates/vue-2model-crud-patch/src/components/AnalyticsBoard.vue +291 -0
  67. package/templates/vue-2model-crud-patch/src/components/DepartmentFormModal.vue +138 -0
  68. package/templates/vue-2model-crud-patch/src/components/DepartmentPatchModal.vue +210 -0
  69. package/templates/vue-2model-crud-patch/src/components/DepartmentTable.vue +133 -0
  70. package/templates/vue-2model-crud-patch/src/components/EmployeeFormModal.vue +177 -0
  71. package/templates/vue-2model-crud-patch/src/components/EmployeePatchModal.vue +221 -0
  72. package/templates/vue-2model-crud-patch/src/components/EmployeeTable.vue +171 -0
  73. package/templates/vue-2model-crud-patch/src/components/NavBar.vue +156 -0
  74. package/templates/vue-2model-crud-patch/src/main.js +12 -0
  75. package/templates/vue-2model-crud-patch/src/router/index.js +33 -0
  76. package/templates/vue-2model-crud-patch/src/services/api.js +64 -0
  77. package/templates/vue-2model-crud-patch/src/stores/departmentStore.js +149 -0
  78. package/templates/vue-2model-crud-patch/src/stores/employeeStore.js +143 -0
  79. package/templates/vue-2model-crud-patch/src/style.css +415 -0
  80. package/templates/vue-2model-crud-patch/src/views/AnalyticsView.vue +59 -0
  81. package/templates/vue-2model-crud-patch/src/views/DepartmentsView.vue +135 -0
  82. package/templates/vue-2model-crud-patch/src/views/EmployeesView.vue +143 -0
  83. package/templates/vue-2model-crud-patch/vite.config.js +9 -0
  84. package/templates/vue-axios/src/App.vue +255 -264
  85. package/templates/vue-axios/src/components/ItemFormModal.vue +183 -0
  86. package/templates/vue-axios/src/components/ItemPatchModal.vue +193 -0
  87. package/templates/vue-axios/src/components/ItemTable.vue +177 -0
  88. package/templates/vue-axios-basic/src/App.vue +241 -429
  89. package/templates/vue-axios-basic/src/components/ItemFormModal.vue +183 -0
  90. package/templates/vue-axios-basic/src/components/ItemPatchModal.vue +193 -0
  91. package/templates/vue-axios-basic/src/components/ItemTable.vue +177 -0
  92. package/templates/vue-axios-pinia/src/App.vue +150 -441
  93. package/templates/vue-axios-pinia/src/components/ItemFormModal.vue +207 -0
  94. package/templates/vue-axios-pinia/src/components/ItemStats.vue +64 -0
  95. package/templates/vue-axios-pinia/src/components/ItemTable.vue +195 -0
  96. package/templates/vue-axios-router/src/App.vue +41 -90
  97. package/templates/vue-axios-router/src/components/ItemFormModal.vue +173 -0
  98. package/templates/vue-axios-router/src/components/ItemTable.vue +102 -0
  99. package/templates/vue-axios-router/src/components/NavBar.vue +57 -0
  100. package/templates/vue-axios-router/src/views/DepartmentView.vue +132 -296
  101. package/templates/vue-axios-router/src/views/EmployeeView.vue +126 -219
  102. package/templates/vue-axios-router-children/src/components/ItemDetail.vue +115 -0
  103. package/templates/vue-axios-router-children/src/components/ItemTable.vue +104 -0
  104. package/templates/vue-axios-router-children/src/components/SideBar.vue +59 -0
  105. package/templates/vue-axios-router-children/src/views/CompanyLayout.vue +2 -51
  106. package/templates/vue-axios-router-children/src/views/DepartmentListView.vue +92 -250
  107. package/templates/vue-axios-router-children/src/views/EmployeeDetailView.vue +59 -181
  108. package/templates/vue-axios-router-children/src/views/EmployeeListView.vue +67 -315
  109. package/templates/vue-axios-router-pinia/src/App.vue +19 -70
  110. package/templates/vue-axios-router-pinia/src/components/ItemFormModal.vue +173 -0
  111. package/templates/vue-axios-router-pinia/src/components/ItemPatchModal.vue +144 -0
  112. package/templates/vue-axios-router-pinia/src/components/ItemTable.vue +102 -0
  113. package/templates/vue-axios-router-pinia/src/components/NavBar.vue +53 -0
  114. package/templates/vue-axios-router-pinia/src/views/DepartmentView.vue +107 -229
  115. package/templates/vue-axios-router-pinia/src/views/EmployeeView.vue +143 -257
  116. package/templates/vue-banking-app/src/components/AccountOverview.vue +75 -0
  117. package/templates/vue-banking-app/src/components/TransactionForm.vue +89 -0
  118. package/templates/vue-banking-app/src/components/TransactionTable.vue +113 -0
  119. package/templates/vue-banking-app/src/stores/bankStore.js +16 -3
  120. package/templates/vue-banking-app/src/views/DashboardView.vue +23 -71
  121. package/templates/vue-banking-app/src/views/StatementView.vue +11 -158
  122. package/templates/vue-banking-app/src/views/TransactionFormView.vue +23 -121
  123. package/templates/vue-basics/src/App.vue +105 -76
  124. package/templates/vue-basics/src/components/CounterCard.vue +107 -0
  125. package/templates/vue-basics/src/components/ItemList.vue +99 -0
  126. package/templates/vue-basics/src/components/UserForm.vue +138 -0
  127. package/templates/vue-cart-router-pinia/src/components/CartSummary.vue +139 -0
  128. package/templates/vue-cart-router-pinia/src/components/CheckoutModal.vue +149 -0
  129. package/templates/vue-cart-router-pinia/src/components/ProductCard.vue +63 -0
  130. package/templates/vue-cart-router-pinia/src/stores/cartStore.js +11 -0
  131. package/templates/vue-cart-router-pinia/src/views/CartView.vue +52 -118
  132. package/templates/vue-cart-router-pinia/src/views/CheckoutView.vue +2 -5
  133. package/templates/vue-cart-router-pinia/src/views/ProductCatalogView.vue +20 -56
  134. package/templates/vue-employee-system/src/App.vue +75 -344
  135. package/templates/vue-employee-system/src/components/DepartmentFilter.vue +108 -0
  136. package/templates/vue-employee-system/src/components/EmployeeModal.vue +183 -0
  137. package/templates/vue-employee-system/src/components/EmployeeTable.vue +91 -0
  138. package/templates/vue-employee-system/src/stores/employeeStore.js +12 -0
  139. package/templates/vue-fetch/src/App.vue +261 -285
  140. package/templates/vue-fetch/src/components/FetchFormModal.vue +183 -0
  141. package/templates/vue-fetch/src/components/FetchPatchModal.vue +193 -0
  142. package/templates/vue-fetch/src/components/FetchTable.vue +177 -0
  143. package/templates/vue-food-ordering/src/components/CartSummary.vue +134 -0
  144. package/templates/vue-food-ordering/src/components/FoodMenu.vue +126 -0
  145. package/templates/vue-food-ordering/src/components/OrderModal.vue +150 -0
  146. package/templates/vue-food-ordering/src/views/MenuView.vue +11 -132
  147. package/templates/vue-food-ordering/src/views/OrderCartView.vue +31 -164
  148. package/templates/vue-game-rps/src/App.vue +29 -88
  149. package/templates/vue-game-tictactoe/src/App.vue +23 -53
  150. package/templates/vue-game-whackamole/src/App.vue +44 -63
  151. package/templates/vue-hospital-portal/src/components/AppointmentForm.vue +144 -0
  152. package/templates/vue-hospital-portal/src/components/DoctorCard.vue +73 -0
  153. package/templates/vue-hospital-portal/src/components/PatientTable.vue +123 -0
  154. package/templates/vue-hospital-portal/src/stores/hospitalStore.js +10 -1
  155. package/templates/vue-hospital-portal/src/views/OPDRegistrationView.vue +40 -416
  156. package/templates/vue-hospital-portal/src/views/PatientListView.vue +11 -301
  157. package/templates/vue-library-system/src/components/BookForm.vue +125 -0
  158. package/templates/vue-library-system/src/components/BookTable.vue +112 -0
  159. package/templates/vue-library-system/src/components/BorrowModal.vue +131 -0
  160. package/templates/vue-library-system/src/stores/libraryStore.js +26 -1
  161. package/templates/vue-library-system/src/views/BookCatalogView.vue +34 -415
  162. package/templates/vue-memory-game/src/App.vue +36 -40
  163. package/templates/vue-memory-game/src/stores/memoryStore.js +32 -37
  164. package/templates/vue-router/src/App.vue +24 -14
  165. package/templates/vue-router/src/components/AboutCard.vue +38 -0
  166. package/templates/vue-router/src/components/HomeHero.vue +75 -0
  167. package/templates/vue-router/src/components/NavBar.vue +54 -0
  168. package/templates/vue-router/src/views/ItemsLayout.vue +17 -9
  169. package/templates/vue-router/src/views/ListView.vue +106 -18
  170. package/templates/vue-router-pinia/src/App.vue +24 -14
  171. package/templates/vue-router-pinia/src/components/ItemFormModal.vue +163 -0
  172. package/templates/vue-router-pinia/src/components/ItemTable.vue +90 -0
  173. package/templates/vue-router-pinia/src/components/NavBar.vue +54 -0
  174. package/templates/vue-router-pinia/src/stores/itemStore.js +8 -1
  175. package/templates/vue-router-pinia/src/views/ListView.vue +78 -14
  176. package/templates/vue-router-pinia-springboot/frontend/src/App.vue +25 -8
  177. package/templates/vue-router-pinia-springboot/frontend/src/components/ItemFormModal.vue +183 -0
  178. package/templates/vue-router-pinia-springboot/frontend/src/components/ItemPatchModal.vue +193 -0
  179. package/templates/vue-router-pinia-springboot/frontend/src/components/ItemTable.vue +177 -0
  180. package/templates/vue-router-pinia-springboot/frontend/src/components/NavBar.vue +54 -0
  181. package/templates/vue-router-pinia-springboot/frontend/src/views/ListView.vue +92 -76
  182. package/templates/vue-springboot-mongo-aggregation/frontend/src/App.vue +184 -193
  183. package/templates/vue-springboot-mongo-aggregation/frontend/src/components/AnalyticsCards.vue +56 -0
  184. package/templates/vue-springboot-mongo-aggregation/frontend/src/components/CategorySummaryTable.vue +71 -0
  185. package/templates/vue-springboot-mongo-aggregation/frontend/src/components/ItemTable.vue +114 -0
  186. package/templates/vue-springboot-mongo-crud/frontend/src/App.vue +228 -158
  187. package/templates/vue-springboot-mongo-crud/frontend/src/components/ItemFormModal.vue +183 -0
  188. package/templates/vue-springboot-mongo-crud/frontend/src/components/ItemPatchModal.vue +193 -0
  189. package/templates/vue-springboot-mongo-crud/frontend/src/components/ItemTable.vue +177 -0
  190. package/templates/vue-symposium-system/src/App.vue +41 -256
  191. package/templates/vue-symposium-system/src/components/AttendeeTable.vue +100 -0
  192. package/templates/vue-symposium-system/src/components/EventCard.vue +67 -0
  193. package/templates/vue-symposium-system/src/components/RegistrationForm.vue +118 -0
  194. package/templates/vue-symposium-system/src/stores/symposiumStore.js +10 -0
  195. package/templates/vue-todo-pinia/src/App.vue +40 -138
  196. package/templates/vue-todo-pinia/src/components/TodoFilter.vue +74 -0
  197. package/templates/vue-todo-pinia/src/components/TodoInput.vue +53 -0
  198. package/templates/vue-todo-pinia/src/components/TodoList.vue +101 -0
  199. package/templates/vue-todo-pinia/src/stores/todoStore.js +6 -0
  200. package/templates/vue-todo-router/src/App.vue +5 -36
  201. package/templates/vue-todo-router/src/components/TodoForm.vue +78 -0
  202. package/templates/vue-todo-router/src/components/TodoList.vue +84 -0
  203. package/templates/vue-todo-router/src/components/TodoNavBar.vue +47 -0
  204. package/templates/vue-todo-router/src/data/todoState.js +7 -0
  205. package/templates/vue-todo-router/src/views/AddTodoView.vue +7 -66
  206. package/templates/vue-todo-router/src/views/TodoDetailView.vue +15 -1
  207. package/templates/vue-todo-router/src/views/TodoListView.vue +22 -64
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "vue-2model-crud-patch",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "axios": "^1.7.9",
13
+ "pinia": "^2.3.1",
14
+ "vue": "^3.5.13",
15
+ "vue-router": "^4.5.0"
16
+ },
17
+ "devDependencies": {
18
+ "@vitejs/plugin-vue": "^5.2.1",
19
+ "vite": "^6.0.7"
20
+ }
21
+ }
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <div class="app-wrapper">
3
+ <!-- Modular Navigation Header -->
4
+ <NavBar :current-tab="currentTab" @tab-change="handleTabChange" />
5
+
6
+ <!-- Main Content Area -->
7
+ <main class="main-container">
8
+ <router-view v-if="hasRouter" />
9
+ <template v-else>
10
+ <DepartmentsView v-if="currentTab === 'departments'" />
11
+ <EmployeesView v-else-if="currentTab === 'employees'" />
12
+ <AnalyticsView v-else-if="currentTab === 'analytics'" />
13
+ </template>
14
+ </main>
15
+ </div>
16
+ </template>
17
+
18
+ <script>
19
+ import NavBar from './components/NavBar.vue'
20
+ import DepartmentsView from './views/DepartmentsView.vue'
21
+ import EmployeesView from './views/EmployeesView.vue'
22
+ import AnalyticsView from './views/AnalyticsView.vue'
23
+
24
+ export default {
25
+ name: 'App',
26
+ components: {
27
+ NavBar,
28
+ DepartmentsView,
29
+ EmployeesView,
30
+ AnalyticsView
31
+ },
32
+ data() {
33
+ return {
34
+ currentTab: 'departments'
35
+ }
36
+ },
37
+ computed: {
38
+ hasRouter() {
39
+ return !!this.$route
40
+ }
41
+ },
42
+ watch: {
43
+ '$route.path': {
44
+ immediate: true,
45
+ handler(path) {
46
+ if (!path) return
47
+ if (path.includes('employees')) {
48
+ this.currentTab = 'employees'
49
+ } else if (path.includes('analytics')) {
50
+ this.currentTab = 'analytics'
51
+ } else {
52
+ this.currentTab = 'departments'
53
+ }
54
+ }
55
+ }
56
+ },
57
+ methods: {
58
+ handleTabChange(tab) {
59
+ this.currentTab = tab
60
+ }
61
+ }
62
+ }
63
+ </script>
@@ -0,0 +1,291 @@
1
+ <template>
2
+ <div class="analytics-board">
3
+ <!-- Top Bar -->
4
+ <div class="card">
5
+ <div class="card-header">
6
+ <div>
7
+ <h2 class="card-title">Analytics and Aggregation Board</h2>
8
+ <p class="card-subtitle">
9
+ MongoDB-style $lookup joined records and $group departmental statistical aggregations
10
+ </p>
11
+ </div>
12
+ <button
13
+ type="button"
14
+ class="btn btn-secondary"
15
+ @click="$emit('refresh')"
16
+ >
17
+ Refresh Data
18
+ </button>
19
+ </div>
20
+
21
+ <!-- Top KPI Cards -->
22
+ <div class="metric-grid">
23
+ <div class="metric-card">
24
+ <div class="metric-card-label">Total Headcount</div>
25
+ <div class="metric-card-value">{{ totalEmployees }}</div>
26
+ <div class="metric-card-hint">Active personnel across all departments</div>
27
+ </div>
28
+
29
+ <div class="metric-card">
30
+ <div class="metric-card-label">Total Departments</div>
31
+ <div class="metric-card-value">{{ totalDepartments }}</div>
32
+ <div class="metric-card-hint">Registered functional units</div>
33
+ </div>
34
+
35
+ <div class="metric-card">
36
+ <div class="metric-card-label">Total Payroll ($group sum)</div>
37
+ <div class="metric-card-value">{{ formatCurrency(totalPayroll) }}</div>
38
+ <div class="metric-card-hint">Combined annual salary expenditure</div>
39
+ </div>
40
+
41
+ <div class="metric-card">
42
+ <div class="metric-card-label">Average Salary ($group avg)</div>
43
+ <div class="metric-card-value">{{ formatCurrency(averageSalary) }}</div>
44
+ <div class="metric-card-hint">Mean compensation per employee</div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+
49
+ <!-- Section 1: $group Aggregation Table -->
50
+ <div class="card">
51
+ <div class="card-header">
52
+ <div>
53
+ <h3 class="card-title">Department Aggregations ($group Stage)</h3>
54
+ <p class="card-subtitle">
55
+ Aggregated by departmentId: headcount, total payroll, average salary, and budget variance
56
+ </p>
57
+ </div>
58
+ <span class="badge badge-primary">MongoDB Aggregation Pipeline</span>
59
+ </div>
60
+
61
+ <div v-if="departmentStats.length === 0" class="alert alert-info">
62
+ No department statistics available. Ensure departments and employees are created.
63
+ </div>
64
+
65
+ <div v-else class="table-responsive">
66
+ <table class="data-table">
67
+ <thead>
68
+ <tr>
69
+ <th>Department</th>
70
+ <th>Location</th>
71
+ <th>Headcount</th>
72
+ <th>Total Payroll</th>
73
+ <th>Avg Salary</th>
74
+ <th>Allocated Budget</th>
75
+ <th>Budget Variance</th>
76
+ </tr>
77
+ </thead>
78
+ <tbody>
79
+ <tr v-for="stat in departmentStats" :key="stat.departmentId || stat.departmentName">
80
+ <td>
81
+ <strong>{{ stat.departmentName }}</strong>
82
+ </td>
83
+ <td>{{ stat.location || 'N/A' }}</td>
84
+ <td>
85
+ <span class="badge badge-secondary">
86
+ {{ stat.headcount }} Member{{ stat.headcount === 1 ? '' : 's' }}
87
+ </span>
88
+ </td>
89
+ <td>
90
+ <strong>{{ formatCurrency(stat.totalPayroll) }}</strong>
91
+ </td>
92
+ <td>{{ formatCurrency(stat.avgSalary) }}</td>
93
+ <td>{{ formatCurrency(stat.budget) }}</td>
94
+ <td>
95
+ <span
96
+ class="badge"
97
+ :class="stat.budgetVariance >= 0 ? 'badge-success' : 'badge-danger'"
98
+ >
99
+ {{ stat.budgetVariance >= 0 ? '+' : '' }}{{ formatCurrency(stat.budgetVariance) }}
100
+ </span>
101
+ </td>
102
+ </tr>
103
+ </tbody>
104
+ </table>
105
+ </div>
106
+ </div>
107
+
108
+ <!-- Section 2: $lookup Joined Data Table -->
109
+ <div class="card">
110
+ <div class="card-header">
111
+ <div>
112
+ <h3 class="card-title">Joined Employee and Department Records ($lookup Stage)</h3>
113
+ <p class="card-subtitle">
114
+ Relational join between Employee document and foreign Department document
115
+ </p>
116
+ </div>
117
+ <div class="search-bar" style="margin-bottom: 0;">
118
+ <input
119
+ type="text"
120
+ class="form-control"
121
+ placeholder="Search joined records..."
122
+ v-model="joinedSearch"
123
+ style="width: 240px;"
124
+ />
125
+ </div>
126
+ </div>
127
+
128
+ <div v-if="filteredJoinedData.length === 0" class="alert alert-info">
129
+ No joined records found.
130
+ </div>
131
+
132
+ <div v-else class="table-responsive">
133
+ <table class="data-table">
134
+ <thead>
135
+ <tr>
136
+ <th>Employee ID</th>
137
+ <th>Employee Name</th>
138
+ <th>Email</th>
139
+ <th>Role</th>
140
+ <th>Salary</th>
141
+ <th>Department ($lookup)</th>
142
+ <th>Dept Location</th>
143
+ <th>Dept Budget</th>
144
+ </tr>
145
+ </thead>
146
+ <tbody>
147
+ <tr v-for="item in filteredJoinedData" :key="item.id">
148
+ <td>
149
+ <code>{{ item.id }}</code>
150
+ </td>
151
+ <td>
152
+ <strong>{{ item.name }}</strong>
153
+ </td>
154
+ <td>{{ item.email }}</td>
155
+ <td>
156
+ <span class="badge badge-primary">{{ item.role || 'Staff' }}</span>
157
+ </td>
158
+ <td>{{ formatCurrency(item.salary) }}</td>
159
+ <td>
160
+ <span class="badge badge-secondary">
161
+ {{ item.departmentName }}
162
+ </span>
163
+ </td>
164
+ <td>{{ item.departmentLocation }}</td>
165
+ <td>{{ formatCurrency(item.departmentBudget) }}</td>
166
+ </tr>
167
+ </tbody>
168
+ </table>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </template>
173
+
174
+ <script>
175
+ export default {
176
+ name: 'AnalyticsBoard',
177
+ props: {
178
+ stats: {
179
+ type: Array,
180
+ default: () => []
181
+ },
182
+ joinedData: {
183
+ type: Array,
184
+ default: () => []
185
+ },
186
+ employees: {
187
+ type: Array,
188
+ default: () => []
189
+ },
190
+ departments: {
191
+ type: Array,
192
+ default: () => []
193
+ }
194
+ },
195
+ emits: ['refresh'],
196
+ data() {
197
+ return {
198
+ joinedSearch: ''
199
+ }
200
+ },
201
+ computed: {
202
+ totalEmployees() {
203
+ return this.employees.length
204
+ },
205
+ totalDepartments() {
206
+ return this.departments.length
207
+ },
208
+ totalPayroll() {
209
+ return this.employees.reduce((sum, e) => sum + (Number(e.salary) || 0), 0)
210
+ },
211
+ averageSalary() {
212
+ if (this.totalEmployees === 0) return 0
213
+ return Math.round(this.totalPayroll / this.totalEmployees)
214
+ },
215
+ departmentStats() {
216
+ if (this.stats && this.stats.length > 0) {
217
+ return this.stats
218
+ }
219
+ // Compute aggregation dynamically if not pre-provided
220
+ return this.departments.map((dept) => {
221
+ const deptEmps = this.employees.filter(
222
+ (e) => e.departmentId === dept.id || e.departmentId === dept.name
223
+ )
224
+ const headcount = deptEmps.length
225
+ const totalPayroll = deptEmps.reduce(
226
+ (sum, e) => sum + (Number(e.salary) || 0),
227
+ 0
228
+ )
229
+ const avgSalary = headcount > 0 ? Math.round(totalPayroll / headcount) : 0
230
+ const budget = Number(dept.budget) || 0
231
+ return {
232
+ departmentId: dept.id,
233
+ departmentName: dept.name,
234
+ location: dept.location,
235
+ budget,
236
+ headcount,
237
+ totalPayroll,
238
+ avgSalary,
239
+ budgetVariance: budget - totalPayroll
240
+ }
241
+ })
242
+ },
243
+ effectiveJoinedData() {
244
+ if (this.joinedData && this.joinedData.length > 0) {
245
+ return this.joinedData
246
+ }
247
+ return this.employees.map((emp) => {
248
+ const dept = this.departments.find(
249
+ (d) => d.id === emp.departmentId || d.name === emp.departmentId
250
+ )
251
+ return {
252
+ id: emp.id,
253
+ name: emp.name,
254
+ email: emp.email,
255
+ role: emp.role,
256
+ salary: emp.salary,
257
+ departmentId: emp.departmentId,
258
+ departmentName: dept ? dept.name : 'Unassigned',
259
+ departmentLocation: dept ? dept.location : 'N/A',
260
+ departmentBudget: dept ? dept.budget : 0
261
+ }
262
+ })
263
+ },
264
+ filteredJoinedData() {
265
+ if (!this.joinedSearch.trim()) {
266
+ return this.effectiveJoinedData
267
+ }
268
+ const term = this.joinedSearch.toLowerCase()
269
+ return this.effectiveJoinedData.filter((item) => {
270
+ const name = (item.name || '').toLowerCase()
271
+ const email = (item.email || '').toLowerCase()
272
+ const role = (item.role || '').toLowerCase()
273
+ const dept = (item.departmentName || '').toLowerCase()
274
+ return (
275
+ name.includes(term) ||
276
+ email.includes(term) ||
277
+ role.includes(term) ||
278
+ dept.includes(term)
279
+ )
280
+ })
281
+ }
282
+ },
283
+ methods: {
284
+ formatCurrency(val) {
285
+ const num = Number(val)
286
+ if (isNaN(num)) return '$0'
287
+ return '$' + num.toLocaleString()
288
+ }
289
+ }
290
+ }
291
+ </script>
@@ -0,0 +1,138 @@
1
+ <template>
2
+ <div v-if="show" class="modal-overlay" @click.self="$emit('close')">
3
+ <div class="modal-container">
4
+ <div class="modal-header">
5
+ <h3 class="modal-title">
6
+ {{ isEdit ? 'Edit Department (PUT)' : 'Create Department (POST)' }}
7
+ </h3>
8
+ <button type="button" class="modal-close-btn" @click="$emit('close')">&times;</button>
9
+ </div>
10
+
11
+ <form @submit.prevent="handleSubmit">
12
+ <div class="modal-body">
13
+ <div v-if="isEdit" class="form-group">
14
+ <label class="form-label">Department ID</label>
15
+ <input
16
+ type="text"
17
+ class="form-control"
18
+ :value="formData.id"
19
+ disabled
20
+ />
21
+ </div>
22
+
23
+ <div class="form-group">
24
+ <label class="form-label">Department Name</label>
25
+ <input
26
+ type="text"
27
+ class="form-control"
28
+ v-model="formData.name"
29
+ placeholder="e.g. Engineering"
30
+ required
31
+ />
32
+ </div>
33
+
34
+ <div class="form-group">
35
+ <label class="form-label">Location</label>
36
+ <input
37
+ type="text"
38
+ class="form-control"
39
+ v-model="formData.location"
40
+ placeholder="e.g. Building A, Floor 3"
41
+ required
42
+ />
43
+ </div>
44
+
45
+ <div class="form-group">
46
+ <label class="form-label">Annual Budget (USD)</label>
47
+ <input
48
+ type="number"
49
+ class="form-control"
50
+ v-model.number="formData.budget"
51
+ placeholder="e.g. 500000"
52
+ min="0"
53
+ step="1000"
54
+ required
55
+ />
56
+ </div>
57
+ </div>
58
+
59
+ <div class="modal-footer">
60
+ <button type="button" class="btn btn-secondary" @click="$emit('close')">
61
+ Cancel
62
+ </button>
63
+ <button type="submit" class="btn btn-primary" :disabled="submitting">
64
+ {{ submitting ? 'Saving...' : (isEdit ? 'Save Changes (PUT)' : 'Create Department (POST)') }}
65
+ </button>
66
+ </div>
67
+ </form>
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <script>
73
+ export default {
74
+ name: 'DepartmentFormModal',
75
+ props: {
76
+ show: {
77
+ type: Boolean,
78
+ default: false
79
+ },
80
+ department: {
81
+ type: Object,
82
+ default: null
83
+ }
84
+ },
85
+ emits: ['close', 'save'],
86
+ data() {
87
+ return {
88
+ formData: {
89
+ id: '',
90
+ name: '',
91
+ location: '',
92
+ budget: 0
93
+ },
94
+ submitting: false
95
+ }
96
+ },
97
+ computed: {
98
+ isEdit() {
99
+ return !!(this.department && this.department.id)
100
+ }
101
+ },
102
+ watch: {
103
+ department: {
104
+ immediate: true,
105
+ handler(val) {
106
+ if (val && val.id) {
107
+ this.formData = {
108
+ id: val.id,
109
+ name: val.name || '',
110
+ location: val.location || '',
111
+ budget: val.budget !== undefined ? Number(val.budget) : 0
112
+ }
113
+ } else {
114
+ this.formData = {
115
+ id: '',
116
+ name: '',
117
+ location: '',
118
+ budget: 100000
119
+ }
120
+ }
121
+ }
122
+ }
123
+ },
124
+ methods: {
125
+ async handleSubmit() {
126
+ this.submitting = true
127
+ try {
128
+ await this.$emit('save', {
129
+ data: { ...this.formData },
130
+ isEdit: this.isEdit
131
+ })
132
+ } finally {
133
+ this.submitting = false
134
+ }
135
+ }
136
+ }
137
+ }
138
+ </script>