We use cookies to enhance your experience on the site
CodeWorlds

Project: Lab Manager with Composables

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>
Go to CodeWorlds