Browse Source

feat(newtab): add more logs filters

Ahmad Kholid 3 years ago
parent
commit
0278e891b3

+ 97 - 0
src/components/newtab/logs/LogsFilters.vue

@@ -0,0 +1,97 @@
+<template>
+  <div class="flex items-center mb-6 space-x-4">
+    <ui-input
+      :model-value="filters.query"
+      prepend-icon="riSearch2Line"
+      placeholder="Search..."
+      class="flex-1"
+      @change="updateFilters('query', $event)"
+    />
+    <div class="flex items-center workflow-sort">
+      <ui-button
+        icon
+        class="rounded-r-none border-gray-300 border-r"
+        @click="updateSorts('order', sorts.order === 'asc' ? 'desc' : 'asc')"
+      >
+        <v-remixicon
+          :name="sorts.order === 'asc' ? 'riSortAsc' : 'riSortDesc'"
+        />
+      </ui-button>
+      <ui-select
+        :model-value="sorts.by"
+        placeholder="Sort by"
+        @change="updateSorts('by', $event)"
+      >
+        <option v-for="sort in sortsList" :key="sort.id" :value="sort.id">
+          {{ sort.name }}
+        </option>
+      </ui-select>
+    </div>
+    <ui-popover>
+      <template #trigger>
+        <ui-button>
+          <v-remixicon name="riFilter2Line" class="mr-2 -ml-1" />
+          <span>Filters</span>
+        </ui-button>
+      </template>
+      <div class="w-48">
+        <p class="flex-1 mb-2 font-semibold">Filters</p>
+        <p class="mb-2 text-sm text-gray-600">By status</p>
+        <div class="grid grid-cols-2 gap-2">
+          <ui-radio
+            v-for="status in filterByStatus"
+            :key="status"
+            :model-value="filters.byStatus"
+            :value="status"
+            class="capitalize text-sm"
+            @change="updateFilters('byStatus', $event)"
+          >
+            {{ status }}
+          </ui-radio>
+        </div>
+        <p class="mb-1 text-sm text-gray-600 mt-3">By date</p>
+        <ui-select
+          :model-value="filters.byDate"
+          class="w-full"
+          @change="updateFilters('byDate', $event)"
+        >
+          <option v-for="date in filterByDate" :key="date.id" :value="date.id">
+            {{ date.name }}
+          </option>
+        </ui-select>
+      </div>
+    </ui-popover>
+  </div>
+</template>
+<script setup>
+defineProps({
+  filters: {
+    type: Object,
+    default: () => ({}),
+  },
+  sorts: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+const emit = defineEmits(['updateSorts', 'updateFilters']);
+
+const filterByStatus = ['all', 'success', 'stopped', 'error'];
+const filterByDate = [
+  { id: 0, name: 'All' },
+  { id: 1, name: 'Last day' },
+  { id: 7, name: 'Last 7 days' },
+  { id: 30, name: 'Last 30 days' },
+];
+const sortsList = [
+  { id: 'name', name: 'Name' },
+  { id: 'startedAt', name: 'Created date' },
+];
+
+function updateFilters(key, value) {
+  emit('updateFilters', { key, value });
+}
+function updateSorts(key, value) {
+  emit('updateSorts', { key, value });
+}
+</script>

+ 54 - 0
src/components/ui/UiExpand.vue

@@ -0,0 +1,54 @@
+<template>
+  <div :aria-expanded="show" class="ui-expand">
+    <button :class="headerClass" @click="toggleExpand">
+      <v-remixicon
+        :rotate="show ? 90 : -90"
+        name="riArrowLeftSLine"
+        class="mr-2 transition-transform -ml-1"
+      />
+      <slot name="header" />
+    </button>
+    <transition-expand>
+      <div v-if="show" :class="panelClass" class="ui-expand__panel">
+        <slot></slot>
+      </div>
+    </transition-expand>
+  </div>
+</template>
+<script setup>
+import { watch, ref } from 'vue';
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false,
+  },
+  panelClass: {
+    type: String,
+    default: '',
+  },
+  headerClass: {
+    type: String,
+    default: 'px-4 py-2 w-full flex items-center h-full',
+  },
+});
+const emit = defineEmits(['update:modelValue']);
+
+const show = ref(false);
+
+function toggleExpand() {
+  show.value = !show.value;
+
+  emit('update:modelValue', show.value);
+}
+
+watch(
+  () => props.modelValue,
+  (value) => {
+    if (value === show.value) return;
+
+    show.value = value;
+  },
+  { immediate: true }
+);
+</script>

+ 2 - 0
src/lib/v-remixicon.js

@@ -4,6 +4,7 @@ import {
   riErrorWarningLine,
   riCalendarLine,
   riFileTextLine,
+  riFilter2Line,
   riArrowGoBackLine,
   riArrowGoForwardLine,
   riDatabase2Line,
@@ -63,6 +64,7 @@ export const icons = {
   riErrorWarningLine,
   riCalendarLine,
   riFileTextLine,
+  riFilter2Line,
   riArrowGoBackLine,
   riArrowGoForwardLine,
   riDatabase2Line,

+ 42 - 59
src/newtab/pages/logs.vue

@@ -1,41 +1,18 @@
 <template>
   <div class="container pt-8 pb-4 logs-list">
     <h1 class="text-2xl font-semibold mb-6">Logs</h1>
-    <div class="flex items-center mb-6 space-x-4">
-      <ui-input
-        v-model="state.query"
-        prepend-icon="riSearch2Line"
-        placeholder="Search..."
-        class="flex-1"
-      />
-      <div class="flex items-center workflow-sort">
-        <ui-button
-          icon
-          class="rounded-r-none border-gray-300 border-r"
-          @click="state.sortOrder = state.sortOrder === 'asc' ? 'desc' : 'asc'"
-        >
-          <v-remixicon
-            :name="state.sortOrder === 'asc' ? 'riSortAsc' : 'riSortDesc'"
-          />
-        </ui-button>
-        <ui-select v-model="state.sortBy" placeholder="Sort by">
-          <option v-for="sort in sorts" :key="sort.id" :value="sort.id">
-            {{ sort.name }}
-          </option>
-        </ui-select>
-      </div>
-      <ui-select v-model="state.filterBy" placeholder="Filter by status">
-        <option v-for="filter in filters" :key="filter" :value="filter">
-          {{ filter }}
-        </option>
-      </ui-select>
-    </div>
+    <logs-filters
+      :sorts="sortsBuilder"
+      :filters="filtersBuilder"
+      @updateSorts="sortsBuilder[$event.key] = $event.value"
+      @updateFilters="filtersBuilder[$event.key] = $event.value"
+    />
     <table class="w-full logs-table">
       <tbody>
         <tr v-for="log in logs" :key="log.id" class="hoverable border-b">
           <td class="w-8">
             <ui-checkbox
-              :model-value="state.selectedLogs.includes(log.id)"
+              :model-value="selectedLogs.includes(log.id)"
               class="align-text-bottom"
               @change="toggleSelectedLog($event, log.id)"
             />
@@ -95,15 +72,15 @@
       </tbody>
     </table>
     <ui-card
-      v-if="state.selectedLogs.length > 1"
+      v-if="selectedLogs.length !== 0"
       class="fixed right-0 bottom-0 m-5 shadow-xl space-x-2"
     >
       <ui-button @click="selectAllLogs">
-        {{ state.selectedLogs.length >= logs.length ? 'Deselect' : 'Select' }}
+        {{ selectedLogs.length >= logs.length ? 'Deselect' : 'Select' }}
         all
       </ui-button>
       <ui-button variant="danger" @click="deleteSelectedLogs">
-        Delete selected logs ({{ state.selectedLogs.length }})
+        Delete selected logs ({{ selectedLogs.length }})
       </ui-button>
     </ui-card>
     <ui-modal v-model="exportDataModal.show">
@@ -116,30 +93,28 @@
   </div>
 </template>
 <script setup>
-import { shallowReactive, reactive, computed } from 'vue';
+import { shallowReactive, ref, computed } from 'vue';
 import { useStore } from 'vuex';
 import { useDialog } from '@/composable/dialog';
 import { countDuration } from '@/utils/helper';
 import { statusColors } from '@/utils/shared';
 import Log from '@/models/log';
 import dayjs from '@/lib/dayjs';
+import LogsFilters from '@/components/newtab/logs/LogsFilters.vue';
 import LogsDataViewer from '@/components/newtab/logs/LogsDataViewer.vue';
 
-const filters = ['all', 'success', 'stopped', 'error'];
-const sorts = [
-  { id: 'name', name: 'Alphabetical' },
-  { id: 'startedAt', name: 'Created date' },
-];
-
 const store = useStore();
 const dialog = useDialog();
 
-const state = reactive({
+const selectedLogs = ref([]);
+const filtersBuilder = shallowReactive({
   query: '',
-  filterBy: 'all',
-  selectedLogs: [],
-  sortOrder: 'desc',
-  sortBy: 'startedAt',
+  byDate: 0,
+  byStatus: 'all',
+});
+const sortsBuilder = shallowReactive({
+  order: 'desc',
+  by: 'startedAt',
 });
 const exportDataModal = shallowReactive({
   show: false,
@@ -148,18 +123,26 @@ const exportDataModal = shallowReactive({
 
 const logs = computed(() =>
   Log.query()
-    .where(({ name, status }) => {
+    .where(({ name, status, startedAt }) => {
+      let statusFilter = true;
+      let dateFilter = true;
       const searchFilter = name
         .toLocaleLowerCase()
-        .includes(state.query.toLocaleLowerCase());
+        .includes(filtersBuilder.query.toLocaleLowerCase());
+
+      if (filtersBuilder.byStatus !== 'all') {
+        statusFilter = status === filtersBuilder.byStatus;
+      }
+
+      if (filtersBuilder.byDate > 0) {
+        const date = Date.now() - filtersBuilder.byDate * 24 * 60 * 60 * 1000;
 
-      if (state.filterBy !== 'all') {
-        return searchFilter && status === state.filterBy;
+        dateFilter = date <= startedAt;
       }
 
-      return searchFilter;
+      return searchFilter && statusFilter && dateFilter;
     })
-    .orderBy(state.sortBy, state.sortOrder)
+    .orderBy(sortsBuilder.by, sortsBuilder.order)
     .get()
 );
 
@@ -175,13 +158,13 @@ function deleteLog(id) {
 }
 function toggleSelectedLog(selected, logId) {
   if (selected) {
-    state.selectedLogs.push(logId);
+    selectedLogs.value.push(logId);
     return;
   }
 
-  const index = state.selectedLogs.indexOf(logId);
+  const index = selectedLogs.value.indexOf(logId);
 
-  if (index !== -1) state.selectedLogs.splice(index, 1);
+  if (index !== -1) selectedLogs.value.splice(index, 1);
 }
 function deleteSelectedLogs() {
   dialog.confirm({
@@ -189,24 +172,24 @@ function deleteSelectedLogs() {
     okVariant: 'danger',
     body: `Are you sure want to delete all the selected logs?`,
     onConfirm: () => {
-      const promises = state.selectedLogs.map((logId) => Log.delete(logId));
+      const promises = selectedLogs.value.map((logId) => Log.delete(logId));
 
       Promise.allSettled(promises).then(() => {
-        state.selectedLogs = [];
+        selectedLogs.value = [];
         store.dispatch('saveToStorage', 'logs');
       });
     },
   });
 }
 function selectAllLogs() {
-  if (state.selectedLogs.length >= logs.value.length) {
-    state.selectedLogs = [];
+  if (selectedLogs.value.length >= logs.value.length) {
+    selectedLogs.value = [];
     return;
   }
 
   const logIds = logs.value.map(({ id }) => id);
 
-  state.selectedLogs = logIds;
+  selectedLogs.value = logIds;
 }
 </script>
 <style>