cardamon 0.0.2

Cardamon is a tool to help development teams measure the power consumption and carbon emissions of their software.
<template>
  <MainLayout :show-add-widget="false">
    <template #title>
      <h1 class="main-layout__title">Cardamon Dashboard</h1>
    </template>
    <template #dashboard>
      <div class="dashboard__header">
        <p class="dashboard__header-title">Scenarios</p>
        <p class="dashboard__header-subtitle">Total scenarios: {{ totalScenarios }}</p>
      </div>
      <div class="dashboard__filters">
        <div class="dashboard__filters-group">
          <!-- <div class="dashboard__filters-input-container dashboard__filters-search-input">
            <input
              type="text"
              v-model="searchQuery"
              placeholder="Search by scenario name"
              class="input-with-icon"
              @input="debouncedSearch"
            />
            <font-awesome-icon icon="fa-solid fa-search" class="input-icon" />
          </div> -->
          <div class="dashboard__filters-input-container dashboard__filters-date-input">
            <select v-model="dateFilter" @change="handleDateFilterChange" class="input-with-icon">
              <option value="all">All time</option>
              <option value="5m">Last 5 minutes</option>
              <option value="30m">Last 30 minutes</option>
              <option value="1d">Last 1 day</option>
              <option value="1w">Last week</option>
              <option value="1m">Last month</option>
              <option value="custom">Custom date</option>
            </select>
            <font-awesome-icon icon="fa-solid fa-calendar" class="input-icon pr-3" />
          </div>
          <div v-if="dateFilter === 'custom'" class="dashboard__filters-dates">
            <div class="dashboard__filters-input-container">
              <input type="date" v-model="fromDate" class="input-with-icon" />
            </div>
            <span class="font-light text-sm text-gray-500 dark:text-gray-400">to</span>
            <div class="dashboard__filters-input-container">
              <input type="date" v-model="toDate" class="input-with-icon" />
            </div>
          </div>
        </div>
      </div>
      <div class="main-layout__dashboard">
        <ListTable
          :data="scenarios"
          :loading="loading"
          :totalPages="totalPages"
          v-model:currentPage="currentPage"
        />
      </div>
    </template>
  </MainLayout>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import MainLayout from '@/layouts/MainLayout.vue'
import ListTable from '@/components/widgets/Table/ListTable.vue'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { useScenarioStore } from '@/stores/scenario'
// import { debounce } from '@/utils/debounce.utils'

const store = useScenarioStore()

const searchQuery = ref('')
const dateFilter = ref('all')
const fromDate = ref('')
const toDate = ref('')

const loading = computed(() => store.loading)
const scenarios = computed(() => store.scenariosData?.scenarios || [])
const totalPages = computed(() => store.scenariosData?.pagination?.totalPages || 1)
const currentPage = ref(1)
const totalScenarios = computed(() => store.scenariosData?.pagination?.totalScenarios || 0)

const fetchScenarios = async (params: { page?: number } = {}) => {
  if (params.page === undefined) {
    currentPage.value = 1
  }
  await store.fetchScenarios({ ...params, page: currentPage.value })
}

// const debouncedSearch = debounce(async () => {
//   currentPage.value = 1
//   await fetchScenarios({
//     searchQuery: searchQuery.value ? searchQuery.value : undefined,
//     fromDate: fromDate.value ? new Date(fromDate.value).getTime() : 0,
//     toDate: toDate.value ? new Date(toDate.value).getTime() : Date.now()
//   })
// }, 500)

const handleDateFilterChange = async () => {
  if (dateFilter.value === 'all') {
    await fetchScenarios()
  }
  if (dateFilter.value === 'custom') return
  currentPage.value = 1
  applyDateFilter()
}

const applyDateFilter = async () => {
  const now = new Date()
  toDate.value = now.toISOString().split('T')[0]
  switch (dateFilter.value) {
    case '5m':
      fromDate.value = new Date(now.getTime() - 5 * 60000).toISOString().split('T')[0]
      break
    case '30m':
      fromDate.value = new Date(now.getTime() - 30 * 60000).toISOString().split('T')[0]
      break
    case '1d':
      fromDate.value = new Date(now.getTime() - 24 * 60 * 60000).toISOString().split('T')[0]
      break
    case '1w':
      fromDate.value = new Date(now.getTime() - 7 * 24 * 60 * 60000).toISOString().split('T')[0]
      break
    case '1m':
      fromDate.value = new Date(now.getTime() - 30 * 24 * 60 * 60000).toISOString().split('T')[0]
      break
    default:
      fromDate.value = ''
  }

  await fetchScenarios({
    fromDate: new Date(fromDate.value).getTime(),
    toDate: new Date(toDate.value).getTime()
    // searchQuery: searchQuery.value ? searchQuery.value : undefined
  })
}

const applyCustomDateFilter = async () => {
  currentPage.value = 1
  await fetchScenarios({
    fromDate: fromDate.value ? new Date(fromDate.value).getTime() : 0,
    toDate: toDate.value ? new Date(toDate.value).getTime() : Date.now(),
    searchQuery: searchQuery.value ? searchQuery.value : undefined
  })
}

onMounted(async () => {
  await fetchScenarios()
})

watch([fromDate, toDate], async () => {
  if (dateFilter.value === 'custom') {
    await applyCustomDateFilter()
  }
})

watch(currentPage, (newPage) => {
  fetchScenarios({ page: newPage })
})
</script>

<style scoped>
.main-layout__title {
  @apply text-2xl font-semibold text-gray-900 dark:text-gray-200 mb-10;
}

.dashboard__header {
  @apply mb-8;
}

.dashboard__header-title {
  @apply text-xl font-semibold text-gray-900 dark:text-gray-200;
}

.dashboard__header-subtitle {
  @apply text-sm text-gray-500 dark:text-gray-400;
}

.dashboard__filters {
  @apply flex justify-between items-center mb-4 w-full;
}

.dashboard__filters-group {
  @apply flex space-x-2 items-center w-full;
}

.dashboard__filters-input-container {
  @apply relative;
}

.dashboard__filters-dates {
  @apply flex space-x-2 justify-center items-center;
}

.dashboard__filter-button {
  @apply py-2 px-5 bg-blue-500 font-light text-white rounded-lg hover:bg-blue-600 dark:bg-blue-700 dark:hover:bg-blue-800 dark:text-gray-100;
}

.main-layout__dashboard {
  @apply mt-4;
}

.main-layout__loading-spinner {
  @apply flex items-center justify-center h-full;
}

.input-with-icon {
  @apply p-2 w-full rounded-md border pl-10 text-sm font-light dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300 dark:[color-scheme:dark];
}

.input-icon {
  @apply absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500;
}

.dashboard__filters-search-input {
  @apply w-1/4;
}

.dashboard__filters-date-input {
  @apply w-48;
}

.dark-mode-date {
  @apply bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500;
}

.dark-mode-text {
  @apply text-gray-500 dark:text-gray-400;
}
</style>