A comprehensive NOVA LAB laboratory management system utilizing all the techniques we've learned!
1// composables/useExperiments.js
2import { ref, computed } from 'vue'
3
4export function useExperiments() {
5 const experiments = ref([])
6 const loading = ref(false)
7 const error = ref(null)
8
9 const criticalExperiments = computed(() =>
10 experiments.value.filter(exp => exp.critical)
11 )
12
13 const categoriesCount = computed(() =>
14 new Set(experiments.value.map(exp => exp.category)).size
15 )
16
17 async function fetchExperiments() {
18 loading.value = true
19 error.value = null
20
21 try {
22 const response = await fetch('/api/mars/experiments')
23 if (!response.ok) throw new Error('Failed to fetch')
24
25 experiments.value = await response.json()
26 } catch (e) {
27 error.value = e.message
28 } finally {
29 loading.value = false
30 }
31 }
32
33 async function addExperiment(experiment) {
34 const response = await fetch('/api/mars/experiments', {
35 method: 'POST',
36 headers: { 'Content-Type': 'application/json' },
37 body: JSON.stringify(experiment)
38 })
39
40 const newExperiment = await response.json()
41 experiments.value.push(newExperiment)
42 }
43
44 async function deleteExperiment(id) {
45 await fetch(`/api/mars/experiments/${id}`, { method: 'DELETE' })
46 experiments.value = experiments.value.filter(exp => exp.id !== id)
47 }
48
49 return {
50 experiments,
51 loading,
52 error,
53 criticalExperiments,
54 categoriesCount,
55 fetchExperiments,
56 addExperiment,
57 deleteExperiment
58 }
59}
60
61// composables/useLabFilters.js
62import { ref, computed } from 'vue'
63
64export function useLabFilters(experiments) {
65 const searchQuery = ref('')
66 const selectedCategory = ref('')
67 const sortBy = ref('name')
68 const sortOrder = ref('asc')
69
70 const filteredExperiments = computed(() => {
71 let result = experiments.value
72
73 // Search
74 if (searchQuery.value) {
75 const query = searchQuery.value.toLowerCase()
76 result = result.filter(exp =>
77 exp.name.toLowerCase().includes(query) ||
78 exp.category.toLowerCase().includes(query)
79 )
80 }
81
82 // Filter by category
83 if (selectedCategory.value) {
84 result = result.filter(exp => exp.category === selectedCategory.value)
85 }
86
87 // Sort
88 result = [...result].sort((a, b) => {
89 const aVal = a[sortBy.value]
90 const bVal = b[sortBy.value]
91 const order = sortOrder.value === 'asc' ? 1 : -1
92
93 if (typeof aVal === 'string') {
94 return aVal.localeCompare(bVal) * order
95 }
96 return (aVal - bVal) * order
97 })
98
99 return result
100 })
101
102 const uniqueCategories = computed(() =>
103 [...new Set(experiments.value.map(exp => exp.category))].sort()
104 )
105
106 function resetFilters() {
107 searchQuery.value = ''
108 selectedCategory.value = ''
109 sortBy.value = 'name'
110 sortOrder.value = 'asc'
111 }
112
113 return {
114 searchQuery,
115 selectedCategory,
116 sortBy,
117 sortOrder,
118 filteredExperiments,
119 uniqueCategories,
120 resetFilters
121 }
122}
123
124// composables/usePagination.js
125import { ref, computed } from 'vue'
126
127export function usePagination(items, itemsPerPage = 12) {
128 const currentPage = ref(1)
129 const perPage = ref(itemsPerPage)
130
131 const totalPages = computed(() =>
132 Math.ceil(items.value.length / perPage.value)
133 )
134
135 const paginatedItems = computed(() => {
136 const start = (currentPage.value - 1) * perPage.value
137 const end = start + perPage.value
138 return items.value.slice(start, end)
139 })
140
141 function goToPage(page) {
142 if (page >= 1 && page <= totalPages.value) {
143 currentPage.value = page
144 }
145 }
146
147 function nextPage() {
148 goToPage(currentPage.value + 1)
149 }
150
151 function prevPage() {
152 goToPage(currentPage.value - 1)
153 }
154
155 return {
156 currentPage,
157 perPage,
158 totalPages,
159 paginatedItems,
160 goToPage,
161 nextPage,
162 prevPage
163 }
164}1<!-- LabManager.vue -->
2<template>
3 <div class="lab-manager">
4 <!-- Header -->
5 <header>
6 <h1>NOVA LAB - Experiment Management</h1>
7 <p>{{ categoriesCount }} categories, {{ experiments.length }} experiments</p>
8 </header>
9
10 <!-- Filters -->
11 <aside class="filters">
12 <input v-model="searchQuery" placeholder="Search experiments..." />
13
14 <select v-model="selectedCategory">
15 <option value="">All categories</option>
16 <option v-for="category in uniqueCategories" :key="category" :value="category">
17 {{ category }}
18 </option>
19 </select>
20
21 <select v-model="sortBy">
22 <option value="name">Name</option>
23 <option value="category">Category</option>
24 <option value="year">Year</option>
25 </select>
26
27 <button @click="sortOrder = sortOrder === 'asc' ? 'desc' : 'asc'">
28 {{ sortOrder === 'asc' ? '↑' : '↓' }}
29 </button>
30
31 <button @click="resetFilters">Reset</button>
32 </aside>
33
34 <!-- Loading/Error -->
35 <div v-if="loading">Loading experiments...</div>
36 <div v-else-if="error">Error: {{ error }}</div>
37
38 <!-- Lab Dashboard -->
39 <main v-else class="lab-grid">
40 <ExperimentCard
41 v-for="experiment in paginatedItems"
42 :key="experiment.id"
43 :experiment="experiment"
44 @delete="deleteExperiment(experiment.id)"
45 />
46 </main>
47
48 <!-- Pagination -->
49 <footer class="pagination">
50 <button @click="prevPage" :disabled="currentPage === 1">
51 Previous
52 </button>
53
54 <button
55 v-for="page in totalPages"
56 :key="page"
57 @click="goToPage(page)"
58 :class="{ active: currentPage === page }"
59 >
60 {{ page }}
61 </button>
62
63 <button @click="nextPage" :disabled="currentPage === totalPages">
64 Next
65 </button>
66 </footer>
67 </div>
68</template>
69
70<script setup>
71import { onMounted } from 'vue'
72import { useExperiments } from './composables/useExperiments'
73import { useLabFilters } from './composables/useLabFilters'
74import { usePagination } from './composables/usePagination'
75
76// Composables
77const {
78 experiments,
79 loading,
80 error,
81 categoriesCount,
82 fetchExperiments,
83 deleteExperiment
84} = useExperiments()
85
86const {
87 searchQuery,
88 selectedCategory,
89 sortBy,
90 sortOrder,
91 filteredExperiments,
92 uniqueCategories,
93 resetFilters
94} = useLabFilters(experiments)
95
96const {
97 currentPage,
98 totalPages,
99 paginatedItems,
100 goToPage,
101 nextPage,
102 prevPage
103} = usePagination(filteredExperiments, 12)
104
105// Lifecycle
106onMounted(() => {
107 fetchExperiments()
108})
109</script>