DONE: layout letter with letterhead and footer, notification for newly...

This commit is contained in:
ISMAIL MASSERAN
2026-06-28 02:16:49 +00:00
parent 94ecbe5887
commit 034ecf947f
400 changed files with 29802 additions and 5446 deletions
+3 -13
View File
@@ -21,7 +21,7 @@ import {
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
@@ -95,12 +95,7 @@ const sport = ref(false)
<div class="mt-5">
<Box>
<CarouselItemGroup>
<CarouselItem
as-child
v-for="(faker, fakerKey) in _.take(fakers, 4)"
:key="fakerKey"
:index="fakerKey"
>
<CarouselItem as-child v-for="(faker, fakerKey) in _.take(fakers, 4)" :key="fakerKey" :index="fakerKey">
<div>
<div class="truncate text-base font-medium">
{{ faker['news'][0]!['title'] }}
@@ -113,12 +108,7 @@ const sport = ref(false)
<Button variant="ghost" type="button" size="sm" class="border border-foreground/20">
View Notes
</Button>
<Button
variant="ghost"
class="ms-auto border border-foreground/20"
type="button"
size="sm"
>
<Button variant="ghost" class="ms-auto border border-foreground/20" type="button" size="sm">
Dismiss
</Button>
</div>
@@ -1,9 +1,12 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { Box } from '@/components/ui/box'
import { Lucide } from '@/components/ui/lucide'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import fakers from '@/utils/faker'
import _ from 'lodash'
import { Lucide, type Icon } from '@/components/ui/lucide'
import {
resolveNotificationRoute,
useNotifications,
type NotificationItem,
} from '@/modules/notification'
interface Props {
class?: string
@@ -11,6 +14,60 @@ interface Props {
}
const props = defineProps<Props>()
const router = useRouter()
const {
notifications,
unreadCount,
loading,
error,
markAsRead,
markAllAsRead,
} = useNotifications()
function getNotificationIcon(notification: NotificationItem): Icon {
if (notification.type === 'user_activation_required') {
return 'UserPlus'
}
return 'Bell'
}
function getIconClass(color: string): string {
switch (color) {
case 'success':
return 'bg-success/15 text-success'
case 'warning':
return 'bg-warning/15 text-warning'
case 'info':
return 'bg-info/15 text-info'
case 'purple':
return 'bg-purple-500/15 text-purple-600 dark:text-purple-400'
case 'orange':
return 'bg-orange-500/15 text-orange-600 dark:text-orange-400'
default:
return 'bg-primary/15 text-primary'
}
}
async function handleNotificationClick(
event: MouseEvent,
notification: NotificationItem,
) {
event.preventDefault()
await markAsRead(notification.id)
const route = resolveNotificationRoute(notification)
if (route) {
await router.push(route)
}
}
async function handleMarkAllAsRead(event: MouseEvent) {
event.preventDefault()
await markAllAsRead()
}
</script>
<template>
@@ -25,29 +82,65 @@ const props = defineProps<Props>()
:class="`before:shadow-foreground/5 z-50 flex w-96 flex-col gap-2.5 px-6 py-5 before:rounded-2xl before:shadow-xl before:backdrop-blur after:rounded-2xl ${props.boxClass ?? ''}`"
>
<div class="flex place-content-between items-center">
<div class="font-medium">Notifications</div>
<a class="text-primary" href="">View More</a>
</div>
<div class="mt-1 flex flex-col gap-2.5">
<a
v-for="(faker, fakerKey) in _.take(fakers, 5)"
:key="fakerKey"
class="hover:border-foreground/10 hover:bg-foreground/5 -mx-2 flex items-center gap-3.5 rounded-2xl border border-transparent p-2"
href=""
<div class="font-medium">
Notifikasi
<span v-if="unreadCount > 0" class="text-primary ml-1 text-sm">({{ unreadCount }})</span>
</div>
<button
v-if="unreadCount > 0"
type="button"
class="text-primary text-sm hover:underline"
@click="handleMarkAllAsRead"
>
<AvatarRoot class="size-11 flex-none rounded-full">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="faker['photos'][2]" alt="avatar" />
</AvatarRoot>
<div class="flex flex-col gap-1">
<div class="flex place-content-between items-center">
<div class="font-medium">{{ faker['users'][0]!['name'] }}</div>
<div class="text-xs opacity-70">{{ faker['times'][0] }}</div>
Tandakan semua dibaca
</button>
</div>
<div v-if="loading" class="text-foreground/70 mt-1 py-6 text-center text-sm">
Memuatkan notifikasi...
</div>
<div v-else-if="error" class="text-danger mt-1 py-4 text-center text-sm">
{{ error }}
</div>
<div
v-else-if="notifications.length === 0"
class="text-foreground/70 mt-1 flex flex-col items-center gap-2 py-8 text-center text-sm"
>
<Lucide class="size-8 opacity-40" icon="Bell" />
<span>Tiada notifikasi buat masa ini.</span>
</div>
<div v-else class="mt-1 flex max-h-96 flex-col gap-1 overflow-y-auto">
<a
v-for="notification in notifications"
:key="notification.id"
class="hover:border-foreground/10 hover:bg-foreground/5 -mx-2 flex items-start gap-3.5 rounded-2xl border border-transparent p-2"
:class="!notification.is_read && 'bg-primary/5'"
href=""
@click="handleNotificationClick($event, notification)"
>
<div
class="flex size-11 flex-none items-center justify-center rounded-full"
:class="getIconClass(notification.color)"
>
<Lucide class="size-5" :icon="getNotificationIcon(notification)" />
</div>
<div class="min-w-0 flex-1 flex-col gap-1">
<div class="flex place-content-between items-start gap-2">
<div class="font-medium leading-snug">{{ notification.title }}</div>
<div class="shrink-0 text-xs opacity-70">{{ notification.time_ago }}</div>
</div>
<div class="line-clamp-2 opacity-70">
{{ faker['news'][0]!['shortContent'] }}
<div class="line-clamp-2 text-sm opacity-70">
{{ notification.message }}
</div>
</div>
<span
v-if="!notification.is_read"
class="bg-primary mt-2 size-2 shrink-0 rounded-full"
aria-hidden="true"
/>
</a>
</div>
</Box>
@@ -21,7 +21,7 @@ import {
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
@@ -77,75 +77,67 @@ const sport = ref(false)
<div class="block h-10 items-center sm:flex">
<h2 class="me-5 truncate text-lg font-medium">Official Store</h2>
<div class="relative mt-3 sm:ms-auto sm:mt-0">
<Lucide
class="[--color:var(--color-foreground)]/50 absolute inset-y-0 start-0 z-10 my-auto ms-3"
icon="MapPin"
/>
<Lucide class="[--color:var(--color-foreground)]/50 absolute inset-y-0 start-0 z-10 my-auto ms-3" icon="MapPin" />
<Input class="box pl-10 sm:w-56" type="text" placeholder="Filter by city" />
</div>
</div>
<Box class="mt-12 p-5 sm:mt-5">
<div>250 Official stores in 21 countries, click the marker to see location details.</div>
<Map
class="mt-5 w-full h-[327px]"
:center="[13.388, 52.517]"
:zoom="14"
:markers="{
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: { name: 'Cafe Berlin', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.388, 52.517] },
},
{
type: 'Feature',
properties: { name: 'Restaurant Alex', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.39, 52.518] },
},
{
type: 'Feature',
properties: { name: 'Coffee House', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.385, 52.515] },
},
{
type: 'Feature',
properties: { name: 'Pizza Place', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.392, 52.519] },
},
{
type: 'Feature',
properties: { name: 'Burger Joint', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.387, 52.516] },
},
{
type: 'Feature',
properties: { name: 'Starbucks', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.391, 52.52] },
},
{
type: 'Feature',
properties: { name: 'Sushi Bar', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.395, 52.522] },
},
{
type: 'Feature',
properties: { name: 'Bakery', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.383, 52.514] },
},
{
type: 'Feature',
properties: { name: 'Italian Restaurant', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.398, 52.525] },
},
{
type: 'Feature',
properties: { name: 'Tea House', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.38, 52.512] },
},
],
}"
/>
<Map class="mt-5 w-full h-[327px]" :center="[13.388, 52.517]" :zoom="14" :markers="{
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: { name: 'Cafe Berlin', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.388, 52.517] },
},
{
type: 'Feature',
properties: { name: 'Restaurant Alex', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.39, 52.518] },
},
{
type: 'Feature',
properties: { name: 'Coffee House', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.385, 52.515] },
},
{
type: 'Feature',
properties: { name: 'Pizza Place', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.392, 52.519] },
},
{
type: 'Feature',
properties: { name: 'Burger Joint', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.387, 52.516] },
},
{
type: 'Feature',
properties: { name: 'Starbucks', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.391, 52.52] },
},
{
type: 'Feature',
properties: { name: 'Sushi Bar', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.395, 52.522] },
},
{
type: 'Feature',
properties: { name: 'Bakery', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.383, 52.514] },
},
{
type: 'Feature',
properties: { name: 'Italian Restaurant', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.398, 52.525] },
},
{
type: 'Feature',
properties: { name: 'Tea House', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.38, 52.512] },
},
],
}" />
</Box>
<!-- END: Official Stores -->
</template>
@@ -21,7 +21,7 @@ import {
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
@@ -79,12 +79,9 @@ const sport = ref(false)
<a class="text-primary ms-auto truncate" href=""> Show More </a>
</div>
<div
class="before:bg-foreground/10 relative mt-5 before:absolute before:ms-5 before:mt-5 before:block before:h-[85%] before:w-px"
>
class="before:bg-foreground/10 relative mt-5 before:absolute before:ms-5 before:mt-5 before:block before:h-[85%] before:w-px">
<div class="relative mb-3 flex items-center">
<div
class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20"
>
<div class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20">
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[9]!['photos'][0]" alt="avatar" />
@@ -101,9 +98,7 @@ const sport = ref(false)
</Box>
</div>
<div class="relative mb-3 flex items-center">
<div
class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20"
>
<div class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20">
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[8]!['photos'][0]" alt="avatar" />
@@ -137,9 +132,7 @@ const sport = ref(false)
</div>
<div class="my-4 text-center text-xs opacity-70">12 November</div>
<div class="relative mb-3 flex items-center">
<div
class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20"
>
<div class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20">
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[7]!['photos'][0]" alt="avatar" />
@@ -162,9 +155,7 @@ const sport = ref(false)
</Box>
</div>
<div class="relative mb-3 flex items-center">
<div
class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20"
>
<div class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20">
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[6]!['photos'][0]" alt="avatar" />
+7 -7
View File
@@ -21,7 +21,7 @@ import {
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
@@ -141,22 +141,22 @@ const sport = ref(false)
<div class="mt-5 border-t border-foreground/10 pt-5">
<div class="flex items-center">
<div
class="bg-(--color)/20 border-(--color)/60 me-3 size-2 rounded-full border [--color:var(--color-pending)]"
></div>
class="bg-(--color)/20 border-(--color)/60 me-3 size-2 rounded-full border [--color:var(--color-pending)]">
</div>
<span class="truncate">UI/UX Workshop</span>
<span class="font-medium xl:ms-auto">23th</span>
</div>
<div class="mt-4 flex items-center">
<div
class="bg-(--color)/20 border-(--color)/60 me-3 size-2 rounded-full border [--color:var(--color-primary)]"
></div>
class="bg-(--color)/20 border-(--color)/60 me-3 size-2 rounded-full border [--color:var(--color-primary)]">
</div>
<span class="truncate"> VueJs Frontend Development </span>
<span class="font-medium xl:ms-auto">10th</span>
</div>
<div class="mt-4 flex items-center">
<div
class="bg-(--color)/20 border-(--color)/60 me-3 size-2 rounded-full border [--color:var(--color-warning)]"
></div>
class="bg-(--color)/20 border-(--color)/60 me-3 size-2 rounded-full border [--color:var(--color-warning)]">
</div>
<span class="truncate">Laravel Rest API</span>
<span class="font-medium xl:ms-auto">31th</span>
</div>
@@ -21,7 +21,7 @@ import {
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
@@ -78,10 +78,7 @@ const sport = ref(false)
<h2 class="me-5 truncate text-lg font-medium">Transactions</h2>
</div>
<div class="mt-5">
<Box
v-for="(faker, fakerKey) in _.take(fakers, 5)"
class="mb-3 flex items-center px-5 py-3 before:hidden"
>
<Box v-for="(faker, fakerKey) in _.take(fakers, 5)" class="mb-3 flex items-center px-5 py-3 before:hidden">
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="faker['photos'][0]" alt="avatar" />
@@ -92,12 +89,10 @@ const sport = ref(false)
{{ faker['dates'][0] }}
</div>
</div>
<div
:class="{
'text-success': faker['trueFalse'][0],
'text-danger': !faker['trueFalse'][0],
}"
>
<div :class="{
'text-success': faker['trueFalse'][0],
'text-danger': !faker['trueFalse'][0],
}">
{{ faker['trueFalse'][0] ? '+' : '-' }}${{ faker['totals'][0] }}
</div>
</Box>
@@ -1,10 +1,6 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { datePickerMonthSelect } from "@mykopkb/core/styles/datepicker.styles";
import {
NativeSelect,
NativeSelectOption,
} from "@/components/ui/native-select";
import type { Api } from "@zag-js/date-picker";
import { inject } from "vue";
@@ -1,9 +1,6 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import {
NativeSelect,
NativeSelectOption,
} from "@/components/ui/native-select";
import { datePickerYearSelect } from "@mykopkb/core/styles/datepicker.styles";
import type { Api } from "@zag-js/date-picker";
import { inject } from "vue";
@@ -1,15 +0,0 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { input } from "@mykopkb/core/styles/input.styles";
import { nativeSelect } from "@mykopkb/core/styles/native-select.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<select :class="cn(input, nativeSelect, className)" v-bind="{ ...props, ...$attrs }">
<slot />
</select>
</template>
@@ -1,14 +0,0 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { nativeSelectOption } from "@mykopkb/core/styles/native-select.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<option :class="cn(nativeSelectOption, className)" v-bind="{ ...props, ...$attrs }">
<slot />
</option>
</template>
@@ -1,14 +0,0 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { NativeSelectOptGroup } from "@mykopkb/core/styles/native-select.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<optgroup :class="cn(NativeSelectOptGroup, className)" v-bind="{ ...props, ...$attrs }">
<slot />
</optgroup>
</template>
@@ -1,3 +0,0 @@
export { default as NativeSelect } from "./NativeSelect.vue";
export { default as NativeSelectOption } from "./NativeSelectOption.vue";
export { default as NativeSelectOptionGroup } from "./NativeSelectOptionGroup.vue";
+4 -2
View File
@@ -2,11 +2,13 @@
import { cn } from "@mykopkb/core/utils/cn";
import { textarea } from "@mykopkb/core/styles/textarea.styles";
const { class: className, ...props } = defineProps<{
const modelValue = defineModel<string>();
const { class: className } = defineProps<{
class?: string;
}>();
</script>
<template>
<textarea :class="cn(textarea, className)" v-bind="{ ...props }" />
<textarea v-model="modelValue" :class="cn(textarea, className)" v-bind="$attrs" />
</template>
+57 -14
View File
@@ -43,10 +43,17 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import * as select from '@zag-js/select';
import {
NativeSelect,
NativeSelectOption,
} from '@/components/ui/native-select';
SelectRoot,
SelectControl,
SelectTrigger,
SelectValueText,
SelectContent,
SelectItemGroup,
SelectItem,
SelectItemText,
} from '@/components/ui/select';
// Type definitions
type ColumnType = 'index' | 'select' | 'action' | 'status' | 'id' | 'date' | 'email' | 'phone' | 'priority' | 'category' | 'registration' | 'model' | 'unit' | 'text';
@@ -129,6 +136,22 @@ const resizingColumn = ref<string | null>(null);
const startX = ref(0);
const perPageOptions = [10, 25, 50, 100, 250, 500, 1000];
type PerPageSelectOption = { label: string; value: number };
const perPageSelectOptions: PerPageSelectOption[] = perPageOptions.map((option) => ({
label: String(option),
value: option,
}));
const perPageCollection = select.collection({
items: perPageSelectOptions,
itemToValue: (item) => item.label,
});
const perPageInitial = computed(() => [
String(itemsPerPageModel.value ?? props.pagination?.per_page ?? props.itemsPerPage ?? 10),
]);
const startWidth = ref(0);
// Helper function to detect column type from key and title
@@ -136,9 +159,10 @@ const detectColumnType = (header: TableHeader): ColumnType => {
const key = header.key.toLowerCase();
const title = header.title.toLowerCase();
// Index/Number columns
if (key === '#' || key === 'index' || key === 'bil' || key === 'no' ||
title.includes('bil') || title.includes('no') || title.includes('#')) {
// Index/Number columns (Bil./# row counters — not field labels like "No. Permohonan")
if (key === '#' || key === 'index' || key === 'bil' ||
title.includes('bil') || title === '#' ||
/^no\.?\s*$/i.test(title.trim())) {
return 'index';
}
@@ -416,8 +440,9 @@ const headerAlignClass = (align?: TableHeader['align']) => {
return 'text-left';
};
const handleItemsPerPageChange = (event: Event) => {
const value = Number((event.target as HTMLSelectElement).value);
const setItemsPerPageValue = (details: { value: string[] }) => {
const value = Number(details.value[0]);
if (Number.isNaN(value)) return;
itemsPerPageModel.value = value;
emit('update:items-per-page', value);
};
@@ -791,12 +816,30 @@ watch(sortBy, (newSort) => {
class="flex flex-wrap items-center justify-between gap-4 border-t border-foreground/10 bg-foreground/2 px-4 py-3">
<div class="flex items-center gap-2 text-sm text-muted-foreground">
<span>Bilangan rekod per halaman:</span>
<NativeSelect class="h-9 w-auto min-w-20 px-2 py-1 text-sm"
:value="String(itemsPerPageModel ?? pagination.per_page)" @change="handleItemsPerPageChange">
<NativeSelectOption v-for="option in perPageOptions" :key="option" :value="String(option)">
{{ option }}
</NativeSelectOption>
</NativeSelect>
<SelectRoot
:key="perPageInitial[0]"
class="w-auto min-w-20"
:collection="perPageCollection"
:default-value="perPageInitial"
@value-change="setItemsPerPageValue"
>
<SelectControl>
<SelectTrigger class="h-9 w-auto min-w-20">
<SelectValueText />
</SelectTrigger>
</SelectControl>
<SelectContent>
<SelectItemGroup>
<SelectItem
v-for="item in perPageCollection.items"
:key="item.label"
:item="item"
>
<SelectItemText>{{ item.label }}</SelectItemText>
</SelectItem>
</SelectItemGroup>
</SelectContent>
</SelectRoot>
</div>
<div class="text-sm text-muted-foreground">
@@ -1,72 +0,0 @@
<script setup lang="ts">
import {
NativeSelect,
NativeSelectOption,
NativeSelectOptionGroup,
} from "@/components/ui/native-select";
</script>
<template>
<div class="flex flex-col gap-20">
<div class="grid sm:grid-cols-2">
<div
class="justify-center items-center flex gap-2 border-b border-e border-foreground/10 p-5 flex-wrap"
>
<NativeSelect class="w-56">
<NativeSelectOption value="">Select status</NativeSelectOption>
<NativeSelectOption value="todo">Todo</NativeSelectOption>
<NativeSelectOption value="in-progress"
>In Progress</NativeSelectOption
>
<NativeSelectOption value="done">Done</NativeSelectOption>
<NativeSelectOption value="cancelled">Cancelled</NativeSelectOption>
</NativeSelect>
</div>
<div
class="justify-center items-center flex gap-2 border-b border-e border-foreground/10 p-5 flex-wrap"
>
<NativeSelect class="w-56">
<NativeSelectOption value="">Select department</NativeSelectOption>
<NativeSelectOptionGroup label="Engineering">
<NativeSelectOption value="frontend">Frontend</NativeSelectOption>
<NativeSelectOption value="backend">Backend</NativeSelectOption>
<NativeSelectOption value="devops">DevOps</NativeSelectOption>
</NativeSelectOptionGroup>
<NativeSelectOptionGroup label="Sales">
<NativeSelectOption value="sales-rep">Sales Rep</NativeSelectOption>
<NativeSelectOption value="account-manager">
Account Manager
</NativeSelectOption>
<NativeSelectOption value="sales-director">
Sales Director
</NativeSelectOption>
</NativeSelectOptionGroup>
<NativeSelectOptionGroup label="Operations">
<NativeSelectOption value="support">
Customer Support
</NativeSelectOption>
<NativeSelectOption value="product-manager">
Product Manager
</NativeSelectOption>
<NativeSelectOption value="ops-manager">
Operations Manager
</NativeSelectOption>
</NativeSelectOptionGroup>
</NativeSelect>
</div>
<div
class="justify-center items-center flex gap-2 border-b border-e border-foreground/10 p-5 flex-wrap"
>
<NativeSelect class="w-56" disabled>
<NativeSelectOption value="">Select status</NativeSelectOption>
<NativeSelectOption value="todo">Todo</NativeSelectOption>
<NativeSelectOption value="in-progress"
>In Progress</NativeSelectOption
>
<NativeSelectOption value="done">Done</NativeSelectOption>
<NativeSelectOption value="cancelled">Cancelled</NativeSelectOption>
</NativeSelect>
</div>
</div>
</div>
</template>
@@ -21,7 +21,7 @@ import {
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
@@ -78,10 +78,7 @@ const sport = ref(false)
<h2 class="me-5 truncate text-lg font-medium">Weekly Best Sellers</h2>
</div>
<div class="mt-5">
<Box
v-for="(faker, fakerKey) in _.take(fakers, 4)"
class="mb-3 flex items-center px-4 py-4 before:hidden"
>
<Box v-for="(faker, fakerKey) in _.take(fakers, 4)" class="mb-3 flex items-center px-4 py-4 before:hidden">
<AvatarRoot class="rounded-xl size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="faker['photos'][0]" alt="avatar" />
@@ -21,7 +21,6 @@ import {
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
@@ -147,12 +146,7 @@ const sport = ref(false)
</Table>
</div>
<div class="mt-3 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
<PaginationRoot
:count="5000"
:pageSize="10"
:siblingCount="2"
class="w-full sm:me-auto sm:w-auto"
>
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2" class="w-full sm:me-auto sm:w-auto">
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
<PaginationContext v-slot="{ pagination }">
<template v-for="(page, index) in pagination?.pages" :key="index">