Skip to content

Commit 00820aa

Browse files
committed
Add categories and status badges
1 parent fcb4373 commit 00820aa

File tree

5 files changed

+7081
-4826
lines changed

5 files changed

+7081
-4826
lines changed

components/ui/Checkbox.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const emit = defineEmits(["update:modelValue"])
6363
transition: all 0.1s ease;
6464
6565
&.active {
66-
background: var(--green);
66+
background: var(--brand);
6767
}
6868
}
6969
</style>

pages/rollups.vue

Lines changed: 228 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@ import { DateTime } from "luxon"
44
55
/** UI */
66
import Button from "@/components/ui/Button.vue"
7+
import Checkbox from "@/components/ui/Checkbox.vue"
8+
import Popover from "@/components/ui/Popover.vue"
79
import Tooltip from "@/components/ui/Tooltip.vue"
810
911
/** Components */
1012
import AmountInCurrency from "@/components/AmountInCurrency.vue"
1113
1214
/** Services */
13-
import { formatBytes, comma, truncateDecimalPart } from "@/services/utils"
15+
import { formatBytes, comma, truncateDecimalPart, capitilize } from "@/services/utils"
1416
1517
/** API */
1618
import { fetchRollups, fetchRollupsCount } from "@/services/api/rollup"
1719
20+
/** Stores */
21+
import { useEnumStore } from "@/store/enums"
22+
const enumStore = useEnumStore()
23+
1824
useHead({
1925
title: "Rollups - Celestia Explorer",
2026
link: [
@@ -80,6 +86,60 @@ const sort = reactive({
8086
by: "size",
8187
dir: "desc",
8288
})
89+
const categories = computed(() => {
90+
let res = []
91+
if (enumStore.enums.rollupCategories.length) {
92+
res = enumStore.enums.rollupCategories.slice(1)
93+
res.push('other')
94+
}
95+
96+
return res
97+
})
98+
const filters = reactive({
99+
categories: categories.value?.reduce((a, b) => ({ ...a, [b]: false }), {}),
100+
})
101+
const savedFiltersBeforeChanges = ref(null)
102+
103+
const isCategoriesPopoverOpen = ref(false)
104+
const handleOpenCategoriesPopover = () => {
105+
isCategoriesPopoverOpen.value = true
106+
107+
if (Object.keys(filters.categories).find((f) => filters.categories[f])) {
108+
savedFiltersBeforeChanges.value = { ...filters.categories }
109+
}
110+
}
111+
const onCategoriesPopoverClose = () => {
112+
isCategoriesPopoverOpen.value = false
113+
114+
if (savedFiltersBeforeChanges.value) {
115+
filters.categories = savedFiltersBeforeChanges.value
116+
savedFiltersBeforeChanges.value = null
117+
} else {
118+
resetFilters("categories")
119+
}
120+
}
121+
const handleApplyCategoriesFilters = () => {
122+
savedFiltersBeforeChanges.value = null
123+
isCategoriesPopoverOpen.value = false
124+
125+
if (page.value !== 1) {
126+
page.value = 1
127+
} else {
128+
getRollups()
129+
}
130+
}
131+
132+
const resetFilters = (target) => {
133+
Object.keys(filters[target]).forEach((f) => {
134+
filters[target][f] = false
135+
})
136+
137+
if (page.value !== 1) {
138+
page.value = 1
139+
} else {
140+
getRollups()
141+
}
142+
}
83143
84144
const getRollupsCount = async () => {
85145
const { data: rollupsCount } = await fetchRollupsCount()
@@ -95,6 +155,12 @@ const getRollups = async () => {
95155
isRefetching.value = true
96156
97157
const data = await fetchRollups({
158+
categories:
159+
Object.keys(filters.categories).find((f) => filters.categories[f]) &&
160+
Object.keys(filters.categories)
161+
.filter((f) => filters.categories[f])
162+
.map(c => c === 'other' ? 'uncategorized' : c)
163+
.join(","),
98164
limit: 20,
99165
offset: (page.value - 1) * 20,
100166
sort: sort.dir,
@@ -107,16 +173,6 @@ const getRollups = async () => {
107173
108174
getRollups()
109175
110-
/** Refetch rollups */
111-
watch(
112-
() => page.value,
113-
async () => {
114-
getRollups()
115-
116-
router.replace({ query: { page: page.value } })
117-
},
118-
)
119-
120176
const handleSort = (by) => {
121177
switch (sort.dir) {
122178
case "desc":
@@ -130,8 +186,11 @@ const handleSort = (by) => {
130186
}
131187
132188
sort.by = by
133-
134-
getRollups()
189+
if (page.value !== 1) {
190+
page.value = 1
191+
} else {
192+
getRollups()
193+
}
135194
}
136195
137196
const handlePrev = () => {
@@ -145,6 +204,23 @@ const handleNext = () => {
145204
146205
page.value += 1
147206
}
207+
208+
watch(
209+
() => categories.value,
210+
() => {
211+
filters.categories = categories.value?.reduce((a, b) => ({ ...a, [b]: false }), {})
212+
}
213+
)
214+
215+
/** Refetch rollups */
216+
watch(
217+
() => page.value,
218+
async () => {
219+
getRollups()
220+
221+
router.replace({ query: { page: page.value } })
222+
},
223+
)
148224
</script>
149225
150226
<template>
@@ -191,14 +267,58 @@ const handleNext = () => {
191267
</Flex>
192268
</Flex>
193269
270+
<Flex align="center" justify="between" wrap="wrap" gap="8" :class="$style.settings">
271+
<Flex wrap="wrap" align="center" gap="8">
272+
<Popover :open="isCategoriesPopoverOpen" @on-close="onCategoriesPopoverClose" width="200">
273+
<Button @click="handleOpenCategoriesPopover" type="secondary" size="mini">
274+
<Icon name="plus-circle" size="12" color="tertiary" />
275+
<Text color="secondary">Category</Text>
276+
277+
<template v-if="Object.keys(filters.categories).find((c) => filters.categories[c])">
278+
<div :class="$style.vertical_divider" />
279+
280+
<Text size="12" weight="600" color="primary" style="text-transform: capitalize">
281+
{{ Object.keys(filters.categories)
282+
.filter((c) => filters.categories[c])
283+
.map(c => c === 'nft' ? c.toUpperCase() : capitilize(c))
284+
.join(", ")
285+
}}
286+
</Text>
287+
288+
<Icon @click.stop="resetFilters('categories', true)" name="close-circle" size="12" color="secondary" />
289+
</template>
290+
</Button>
291+
292+
<template #content>
293+
<Flex direction="column" gap="12">
294+
<Text size="12" weight="500" color="secondary">Filter by Category</Text>
295+
296+
<Flex direction="column" gap="8" :class="$style.filters_list">
297+
<Checkbox
298+
v-for="c in Object.keys(filters.categories)"
299+
v-model="filters.categories[c]"
300+
>
301+
<Text size="12" weight="500" color="primary">
302+
{{ c === 'nft' ? c.toUpperCase() : capitilize(c) }}
303+
</Text>
304+
</Checkbox>
305+
</Flex>
306+
307+
<Button @click="handleApplyCategoriesFilters" type="secondary" size="mini" wide>Apply</Button>
308+
</Flex>
309+
</template>
310+
</Popover>
311+
</Flex>
312+
</Flex>
313+
194314
<Flex direction="column" gap="16" wide :class="[$style.table, isRefetching && $style.disabled]">
195-
<div v-if="rollups.length" :class="$style.table_scroller">
315+
<div v-if="rollups?.length" :class="$style.table_scroller">
196316
<table>
197317
<thead>
198318
<tr>
199319
<th><Text size="12" weight="600" color="tertiary" noWrap>#</Text></th>
200320
<th><Text size="12" weight="600" color="tertiary" noWrap>Rollup</Text></th>
201-
<th @click="handleSort('time')" :class="$style.sortable">
321+
<!-- <th @click="handleSort('time')" :class="$style.sortable">
202322
<Flex align="center" gap="6">
203323
<Text size="12" weight="600" color="tertiary" noWrap>Last Active</Text>
204324
<Icon
@@ -209,6 +329,18 @@ const handleNext = () => {
209329
:style="{ transform: `rotate(${sort.dir === 'asc' ? '180' : '0'}deg)` }"
210330
/>
211331
</Flex>
332+
</th> -->
333+
<th>
334+
<Flex align="center" gap="6">
335+
<Text size="12" weight="600" color="tertiary" noWrap>Category</Text>
336+
<!-- <Icon
337+
v-if="sort.by === 'time'"
338+
name="chevron"
339+
size="12"
340+
color="secondary"
341+
:style="{ transform: `rotate(${sort.dir === 'asc' ? '180' : '0'}deg)` }"
342+
/> -->
343+
</Flex>
212344
</th>
213345
<th @click="handleSort('size')" :class="$style.sortable">
214346
<Flex align="center" gap="6">
@@ -262,17 +394,47 @@ const handleNext = () => {
262394
<td style="width: 1px">
263395
<NuxtLink :to="`/rollup/${r.slug}`">
264396
<Flex align="center" gap="8">
265-
<Flex v-if="r.logo" align="center" justify="center" :class="$style.avatar_container">
266-
<img :src="r.logo" :class="$style.avatar_image" />
397+
<Flex v-if="r.logo" align="center" :class="$style.avatar_wrapper">
398+
<div :class="$style.avatar_container">
399+
<img :src="r.logo" :class="$style.avatar_image" />
400+
</div>
401+
402+
<Tooltip :class="$style.status_dot_wrapper">
403+
<div
404+
:class="$style.status_dot"
405+
:style="{
406+
background: `${Math.abs(DateTime.fromISO(r.last_message_time).diffNow('days').days) < 1
407+
? ''
408+
: Math.abs(DateTime.fromISO(r.last_message_time).diffNow('days').days) < 7
409+
? 'var(--light-orange)'
410+
: 'var(--red)'
411+
}`
412+
}"
413+
/>
414+
415+
<template #content>
416+
<Flex align="end" gap="8">
417+
<Text size="12" weight="600" color="tertiary"> {{
418+
`Was active
419+
${Math.abs(DateTime.fromISO(r.last_message_time).diffNow('days').days) < 1
420+
? 'less than a day ago'
421+
: Math.abs(DateTime.fromISO(r.last_message_time).diffNow('days').days) < 7
422+
? 'more than a day ago'
423+
: 'more than a week ago'
424+
}`
425+
}} </Text>
426+
</Flex>
427+
</template>
428+
</Tooltip>
267429
</Flex>
268-
430+
269431
<Text size="12" weight="600" color="primary" mono>
270432
{{ r.name }}
271433
</Text>
272434
</Flex>
273435
</NuxtLink>
274436
</td>
275-
<td>
437+
<!-- <td>
276438
<NuxtLink :to="`/rollup/${r.slug}`">
277439
<Flex direction="column" justify="center" gap="4">
278440
<Text size="12" weight="600" color="primary">
@@ -284,6 +446,13 @@ const handleNext = () => {
284446
</Text>
285447
</Flex>
286448
</NuxtLink>
449+
</td> -->
450+
<td>
451+
<NuxtLink :to="`/rollup/${r.slug}`">
452+
<Flex align="center">
453+
<Text size="13" weight="600" color="primary"> {{ capitilize(r.category) }} </Text>
454+
</Flex>
455+
</NuxtLink>
287456
</td>
288457
<td>
289458
<NuxtLink :to="`/rollup/${r.slug}`">
@@ -387,6 +556,23 @@ const handleNext = () => {
387556
padding: 0 16px;
388557
}
389558
559+
.settings {
560+
border-radius: 4px;
561+
background: var(--card-background);
562+
563+
padding: 8px 16px;
564+
}
565+
566+
.vertical_divider {
567+
min-width: 2px;
568+
height: 12px;
569+
background: var(--op-10);
570+
}
571+
572+
.filters_list {
573+
height: auto;
574+
}
575+
390576
.table_scroller {
391577
overflow-x: auto;
392578
}
@@ -469,10 +655,16 @@ const handleNext = () => {
469655
}
470656
}
471657
658+
.avatar_wrapper {
659+
position: relative;
660+
width: 25px;
661+
height: 25px;
662+
}
663+
472664
.avatar_container {
473665
position: relative;
474-
width: 25px;
475-
height: 25px;
666+
width: 100%;
667+
height: 100%;
476668
overflow: hidden;
477669
border-radius: 50%;
478670
}
@@ -483,6 +675,21 @@ const handleNext = () => {
483675
object-fit: cover;
484676
}
485677
678+
.status_dot_wrapper {
679+
position: absolute;
680+
bottom: 0px;
681+
right: 0px;
682+
z-index: 1;
683+
}
684+
685+
.status_dot {
686+
width: 10px;
687+
height: 10px;
688+
border-radius: 50%;
689+
background: var(--brand);
690+
border: 1px solid var(--card-background);
691+
}
692+
486693
.table.disabled {
487694
opacity: 0.5;
488695
pointer-events: none;

0 commit comments

Comments
 (0)