This commit is contained in:
Viet An
2026-04-14 22:44:52 +07:00
parent 1d30ca3408
commit e5b80124fc
23 changed files with 1045 additions and 158 deletions

View File

@@ -129,18 +129,12 @@ $size: (
// Block Layout // Block Layout
.blockdiv { .blockdiv {
max-width: 1900px !important; max-width: 1900px !important;
padding: 60px 15px 40px 15px; padding: 1rem 2rem 2rem;
@include mobile { padding: 1rem; }
@include until($desktop) { padding: 65px 20px 30px 20px; }
@include mobile { padding: 65px 16px 30px 16px; }
.columns .column { .columns .column {
@include mobile { padding-left: 0; padding-right: 0; } @include mobile { padding-left: 0; padding-right: 0; }
} }
.padding-desktop {
@media screen and (min-width: $desktop) { padding-left: 20px; padding-right: 20px; }
}
} }
// Tooltip Styles // Tooltip Styles

View File

@@ -1,81 +1,81 @@
<template> <template>
<nav class="navbar is-fixed-top has-shadow px-3" role="navigation"> <nav class="navbar has-shadow sticky px-3" style="top: 0" role="navigation">
<div class="navbar-brand mr-5"> <div class="navbar-brand mr-5">
<span class="navbar-item is-gap-1"> <span class="navbar-item is-gap-1">
<div style="width: 16px; height: 16px" class="has-background-primary rounded-full"></div> <div style="width: 16px; height: 16px" class="has-background-primary rounded-full"></div>
<span class="fs-17 font-semibold has-text-primary">{{$dayjs().format('DD/MM')}}</span> <span class="fs-17 font-semibold has-text-primary">{{$dayjs().format('DD/MM')}}</span>
</span> </span>
<a <a
class="navbar-item p-0 has-text-primary" class="navbar-item p-0 has-text-primary"
@click="changeTab(leftmenu[0])" @click="changeTab(leftmenu[0])"
> >
<svg <svg
style="max-height: none; width: 44px" style="max-height: none; width: 44px"
width="80" height="80" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg"> width="80" height="80" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M40 8C57.6731 8 72 22.3269 72 40C72 57.6731 57.6731 72 40 72C22.3269 72 8 57.6731 8 40C8 22.3269 22.3269 8 40 8ZM15.7285 31.5762V49.2266H21.9854C23.778 49.2266 25.3185 48.8727 26.6055 48.166C27.898 47.4593 28.8887 46.4453 29.5781 45.124C30.2733 43.8025 30.6211 42.2224 30.6211 40.3838C30.6211 38.5511 30.2733 36.9768 29.5781 35.6611C28.8887 34.3455 27.9033 33.3367 26.6221 32.6357C25.3409 31.9292 23.8123 31.5762 22.0371 31.5762H15.7285ZM33.3857 49.2266H45.3135V46.1494H37.1172V41.9355H44.667V38.8584H37.1172V34.6533H45.2793V31.5762H33.3857V49.2266ZM53.3818 49.2266H58.1914L64.2754 31.5762H60.1387L55.8643 44.9863H55.7002L51.4346 31.5762H47.2891L53.3818 49.2266ZM21.8389 34.7734C22.942 34.7734 23.8704 34.9687 24.623 35.3594C25.3756 35.75 25.9411 36.3593 26.3203 37.1865C26.7052 38.0138 26.8984 39.0797 26.8984 40.3838C26.8984 41.6995 26.7053 42.7743 26.3203 43.6074C25.9411 44.4346 25.3726 45.047 24.6143 45.4434C23.8617 45.8339 22.9339 46.0292 21.8311 46.0293H19.4609V34.7734H21.8389Z" /> <path fill="currentColor" d="M40 8C57.6731 8 72 22.3269 72 40C72 57.6731 57.6731 72 40 72C22.3269 72 8 57.6731 8 40C8 22.3269 22.3269 8 40 8ZM15.7285 31.5762V49.2266H21.9854C23.778 49.2266 25.3185 48.8727 26.6055 48.166C27.898 47.4593 28.8887 46.4453 29.5781 45.124C30.2733 43.8025 30.6211 42.2224 30.6211 40.3838C30.6211 38.5511 30.2733 36.9768 29.5781 35.6611C28.8887 34.3455 27.9033 33.3367 26.6221 32.6357C25.3409 31.9292 23.8123 31.5762 22.0371 31.5762H15.7285ZM33.3857 49.2266H45.3135V46.1494H37.1172V41.9355H44.667V38.8584H37.1172V34.6533H45.2793V31.5762H33.3857V49.2266ZM53.3818 49.2266H58.1914L64.2754 31.5762H60.1387L55.8643 44.9863H55.7002L51.4346 31.5762H47.2891L53.3818 49.2266ZM21.8389 34.7734C22.942 34.7734 23.8704 34.9687 24.623 35.3594C25.3756 35.75 25.9411 36.3593 26.3203 37.1865C26.7052 38.0138 26.8984 39.0797 26.8984 40.3838C26.8984 41.6995 26.7053 42.7743 26.3203 43.6074C25.9411 44.4346 25.3726 45.047 24.6143 45.4434C23.8617 45.8339 22.9339 46.0292 21.8311 46.0293H19.4609V34.7734H21.8389Z" />
</svg> </svg>
</a> </a>
<a <a
role="button" role="button"
class="navbar-burger" class="navbar-burger"
id="burger" id="burger"
aria-label="menu" aria-label="menu"
aria-expanded="false" aria-expanded="false"
data-target="navMenu" data-target="navMenu"
@click="handleClick()" @click="handleClick()"
> >
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
</a> </a>
</div> </div>
<div class="navbar-menu" id="navMenu"> <div class="navbar-menu" id="navMenu">
<div class="navbar-start is-gap-1 is-align-items-center" style="min-width: 650px;"> <div class="navbar-start is-gap-1 is-align-items-center" >
<template v-for="(v, i) in leftmenu" :key="i" :id="v.code"> <template v-for="(v, i) in leftmenu" :key="i" :id="v.code">
<a class="navbar-item rounded-lg is-clipped p-0" v-if="!v.submenu" @click="changeTab(v)"> <a class="navbar-item rounded-lg is-clipped p-0" v-if="!v.submenu" @click="changeTab(v)">
<span :class="[
'px-3 py-2 fs-14 font-medium',
currentTab.code === v.code ? 'has-text-primary-50 has-background-primary-95' : 'has-text-grey-30'
]">
{{ v[lang] }}
</span>
</a>
<div class="navbar-item has-dropdown is-hoverable" v-else>
<a class="navbar-item px-2" @click="changeTab(v)">
<span :class="[ <span :class="[
'px-3 py-2 fs-14 font-medium', 'px-3 py-1 font-medium',
currentTab.code === v.code ? 'has-text-primary-50 has-background-primary-95' : 'has-text-grey-30' currentTab.code === v.code ? 'has-text-primary-bold has-background-primary-soft' : 'has-text-grey-30'
]"> ]">
{{ v[lang] }} <span>{{ v[lang] }}</span>
<SvgIcon
style="padding-top: 5px"
v-bind="{ name: 'down2.svg', type: currentTab.code === v.code ? 'white' : 'dark', size: 15 }"
>
</SvgIcon>
</span> </span>
</a> </a>
<div class="navbar-item has-dropdown is-hoverable" v-else> <div class="navbar-dropdown has-background-light">
<a class="navbar-item px-2" @click="changeTab(v)"> <a
<span :class="[ class="navbar-item has-background-light py-1 border-bottom"
'px-3 py-1 font-medium', v-for="x in v.submenu"
currentTab.code === v.code ? 'has-text-primary-bold has-background-primary-soft' : 'has-text-grey-30' @click="changeTab(v, x)"
]"> >
<span>{{ v[lang] }}</span> {{ x[lang] }}
<SvgIcon
style="padding-top: 5px"
v-bind="{ name: 'down2.svg', type: currentTab.code === v.code ? 'white' : 'dark', size: 15 }"
>
</SvgIcon>
</span>
</a> </a>
<div class="navbar-dropdown has-background-light">
<a
class="navbar-item has-background-light py-1 border-bottom"
v-for="x in v.submenu"
@click="changeTab(v, x)"
>
{{ x[lang] }}
</a>
</div>
</div> </div>
</template> </div>
</div> </template>
<div class="navbar-end">
<a class="navbar-item" @click="changeTab(tabConfig)" v-if="tabConfig">
<SvgIcon v-bind="{ name: 'configuration.svg', type: 'findata', size: 24 }"></SvgIcon>
</a>
<a class="navbar-item" @click="openProfile()" v-if="avatar">
<Avatarbox v-bind="avatar"></Avatarbox>
</a>
</div>
</div> </div>
<div v-if="false" class="navbar-end">
<a class="navbar-item" @click="changeTab(tabConfig)" v-if="tabConfig">
<SvgIcon v-bind="{ name: 'configuration.svg', type: 'findata', size: 24 }"></SvgIcon>
</a>
<a class="navbar-item" @click="openProfile()" v-if="avatar">
<Avatarbox v-bind="avatar"></Avatarbox>
</a>
</div>
</div>
</nav> </nav>
</template> </template>
<script setup> <script setup>
@@ -127,9 +127,9 @@ const menu = [
index: 0, index: 0,
}, },
] ]
if($store.rights.length>0) { // if($store.rights.length>0) {
menu = menu.filter(v=>$findIndex($store.rights, {setting: v.id})>=0) // menu = menu.filter(v=>$findIndex($store.rights, {setting: v.id})>=0)
} // }
if(menu.length===0) { if(menu.length===0) {
$snackbar($store.lang==='vi'? 'Bạn không có quyền truy cập' : 'You do not have permission to access.') $snackbar($store.lang==='vi'? 'Bạn không có quyền truy cập' : 'You do not have permission to access.')
} }

View File

@@ -68,11 +68,12 @@ const highlights = [
<div class="grid"> <div class="grid">
<DashboardHighlightCard <DashboardHighlightCard
v-for="highlight in highlights" v-for="highlight in highlights"
:key="highlight.name"
v-bind="highlight" v-bind="highlight"
/> />
</div> </div>
</div> </div>
<div class="fixed-grid has-3-cols"> <div class="fixed-grid has-1-cols-mobile has-3-cols">
<div class="grid"> <div class="grid">
<div class="cell is-col-span-2"> <div class="cell is-col-span-2">
<RevenueChart /> <RevenueChart />

View File

@@ -1,4 +1,5 @@
<script setup> <script setup>
import DeliveryInteractive from '@/components/dashboard/DeliveryInteractive.vue';
import Driver from '@/components/dashboard/Driver.vue'; import Driver from '@/components/dashboard/Driver.vue';
const drivers = [ const drivers = [
@@ -32,11 +33,11 @@ const drivers = [
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<p class="fs-17 font-semibold mb-4">Giao nhận & Tài xế</p> <p class="fs-17 font-semibold mb-4">Giao nhận & Tài xế</p>
<div class="fixed-grid has-3-cols"> <div class="fixed-grid has-1-cols-mobile has-3-cols">
<div class="grid"> <div class="grid">
<div class="cell is-col-span-2"> <div class="cell is-col-span-2">
<div style="border-radius: 0.5rem; overflow: hidden"> <div>
<NuxtImg src="/map-demo.png" class="w-full" /> <DeliveryInteractive />
</div> </div>
</div> </div>
<div class="cell is-flex is-flex-direction-column is-gap-2"> <div class="cell is-flex is-flex-direction-column is-gap-2">

View File

@@ -0,0 +1,477 @@
<template>
<div
class="relative w-full has-background-blue-95 rounded-lg is-clipped"
style="height: 360px"
>
<div class="absolute inset-0 w-full h-full opacity-20"><div class="absolute w-full border-t border-gray-300" style="top: 20%;"></div><div class="absolute w-full border-t border-gray-300" style="top: 40%;"></div><div class="absolute w-full border-t border-gray-300" style="top: 60%;"></div><div class="absolute w-full border-t border-gray-300" style="top: 80%;"></div><div class="absolute w-full border-t border-gray-300" style="top: 100%;"></div><div class="absolute h-full border-l border-gray-300" style="left: 20%;"></div><div class="absolute h-full border-l border-gray-300" style="left: 40%;"></div><div class="absolute h-full border-l border-gray-300" style="left: 60%;"></div><div class="absolute h-full border-l border-gray-300" style="left: 80%;"></div><div class="absolute h-full border-l border-gray-300" style="left: 100%;"></div>
</div>
<div>
<div
class="absolute transform -translate-x-1/2 -translate-y-full"
style="left: 33.3333%; top: 40%"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-map-pin w-6 h-6 has-text-orange-50"
>
<path
d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
</div>
<div
class="absolute transform -translate-x-1/2 -translate-y-full"
style="left: 56.6667%; top: 60%"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-map-pin w-6 h-6 has-text-green-50"
>
<path
d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
</div>
<div
class="absolute transform -translate-x-1/2 -translate-y-full"
style="left: 10%; top: 28%"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-map-pin w-6 h-6 has-text-orange-50"
>
<path
d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
</div>
<div
class="absolute transform -translate-x-1/2 -translate-y-full"
style="left: 83.3333%; top: 80%"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-map-pin w-6 h-6 has-text-green-50"
>
<path
d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
</div>
<div
class="absolute transform -translate-x-1/2 -translate-y-full"
style="left: 40%; top: 52%"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-map-pin w-6 h-6 has-text-blue-50"
>
<path
d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
</div>
<div
class="absolute transform -translate-x-1/2 -translate-y-1/2 transition-all duration-3000 ease-linear animate-move-random-1"
style="left: 39.3933%; top: 56.171%"
>
<div class="relative">
<div
class="w-10 h-10 rounded-full has-background-blue-50 border-2 border-white is-flex is-justify-content-center is-align-items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="white"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-navigation w-5 h-5 text-white"
>
<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>
</svg>
</div>
<div
class="absolute -inset-1 rounded-full has-background-blue-60 animate-ping opacity-40"
></div>
</div>
</div>
<div
class="absolute transform -translate-x-1/2 -translate-y-1/2 transition-all duration-3000 ease-linear animate-move-random-2"
style="left: 22.8854%; top: 33.4304%"
>
<div class="relative">
<div
class="w-10 h-10 rounded-full has-background-blue-50 border-2 border-white is-flex is-justify-content-center is-align-items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="white"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-navigation w-5 h-5 text-white"
>
<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>
</svg>
</div>
<div
class="absolute -inset-1 rounded-full has-background-blue-60 animate-ping opacity-40"
></div>
</div>
</div>
<div
class="absolute transform -translate-x-1/2 -translate-y-1/2 transition-all duration-3000 ease-linear"
style="left: 73.3333%; top: 72%"
>
<div class="relative">
<div
class="w-10 h-10 rounded-full bg-gray-400 border-2 border-white is-flex is-justify-content-center is-align-items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="white"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-navigation w-5 h-5 text-white"
>
<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>
</svg>
</div>
</div>
</div>
<div
class="absolute transform -translate-x-1/2 -translate-y-1/2 transition-all duration-3000 ease-linear animate-move-random-3"
style="left: 37.052%; top: 75.6278%"
>
<div class="relative">
<div
class="w-10 h-10 rounded-full has-background-blue-50 border-2 border-white is-flex is-justify-content-center is-align-items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="white"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-navigation w-5 h-5 text-white"
>
<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>
</svg>
</div>
<div
class="absolute -inset-1 rounded-full has-background-blue-60 animate-ping opacity-40"
></div>
</div>
</div>
<div
class="absolute bottom-3 right-3 backdrop-blur px-3 py-1.5 rounded-lg text-xs font-medium has-text-gray-60"
style="background-color: hsla(0, 100%, 100%, 0.9)"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-map-pin w-4 h-4 inline mr-1"
>
<path
d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
></path>
<circle cx="12" cy="10" r="3"></circle></svg> Nội
</div>
</div>
</div>
</template>
<style scoped>
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.-inset-1 {
inset: calc(var(--spacing) * -1);
}
.right-3 {
right: calc(var(--spacing) * 3);
}
.bottom-3 {
bottom: calc(var(--spacing) * 3);
}
.mr-1 {
margin-right: calc(var(--spacing) * 1);
}
.inline {
display: inline;
}
.h-3 {
height: calc(var(--spacing) * 3);
}
.h-5 {
height: calc(var(--spacing) * 5);
}
.h-6 {
height: calc(var(--spacing) * 6);
}
.h-10 {
height: calc(var(--spacing) * 10);
}
.w-3 {
width: calc(var(--spacing) * 3);
}
.w-5 {
width: calc(var(--spacing) * 5);
}
.w-6 {
width: calc(var(--spacing) * 6);
}
.w-10 {
width: calc(var(--spacing) * 10);
}
.w-full {
width: 100%;
}
.-translate-x-1\/2 {
--tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-1\/2 {
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-full {
--tw-translate-y: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
.animate-ping {
animation: ping 1s cubic-bezier(0,0,0.2,1) infinite;
}
@keyframes ping {
0% {
opacity: 0.4;
transform: scale(0.5);
}
100% {
opacity: 0;
transform: scale(1.5);
}
}
.animate-move-random-1 {
animation: move-random-1 30s ease-in-out infinite;
}
.animate-move-random-2 {
animation: move-random-2 60s ease-in-out infinite;
}
.animate-move-random-3 {
animation: move-random-3 50s ease-in-out infinite;
}
@keyframes move-random-1 {
0% {
transform: translateX(0) translateY(0)
}
33% {
transform: translateX(30px) translateY(24px)
}
66% {
transform: translateX(60px) translateY(12px)
}
100% {
transform: translateX(0) translateY(0)
}
}
@keyframes move-random-2 {
0% {
transform: translateX(0) translateY(0)
}
23% {
transform: translateX(-20px) translateY(-36px)
}
46% {
transform: translateX(0) translateY(22px)
}
75% {
transform: translateX(30px) translateY(-12px)
}
100% {
transform: translateX(0) translateY(0)
}
}
@keyframes move-random-3 {
0% {
transform: translateX(0) translateY(0)
}
10% {
transform: translateX(-30px) translateY(-15px)
}
30% {
transform: translateX(-10px) translateY(26px)
}
50% {
transform: translateX(48px) translateY(-28px)
}
80% {
transform: translateX(17px) translateY(10px)
}
100% {
transform: translateX(0) translateY(0)
}
}
.border-2 {
border-style: var(--tw-border-style);
border-width: 2px;
}
.border-white {
border-color: var(--color-white);
}
.bg-gray-400 {
background-color: var(--color-gray-400);
}
.bg-white\/90 {
background-color: color-mix(in srgb, #fff 90%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 90%, transparent);
}
}
.px-3 {
padding-inline: calc(var(--spacing) * 3);
}
.py-1\.5 {
padding-block: calc(var(--spacing) * 1.5);
}
.text-xs {
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
.font-medium {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
.text-gray-600 {
color: var(--color-gray-600);
}
.text-white {
color: var(--color-white);
}
.opacity-40 {
opacity: 40%;
}
.shadow {
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.backdrop-blur {
--tw-backdrop-blur: blur(8px);
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
}
.transition-all {
transition-property: all;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.duration-3000 {
--tw-duration: 3000ms;
transition-duration: 3000ms;
}
.ease-linear {
--tw-ease: linear;
transition-timing-function: linear;
}
.border-t {
border-top-style: solid;
border-top-width: 1px;
}
.border-l {
border-left-style: solid;
border-left-width: 1px;
}
.border-gray-300 {
border-color: var(--bulma-grey-85);
}
</style>

View File

@@ -117,7 +117,6 @@
weeks.value.map(v=>{ weeks.value.map(v=>{
v.dates = dates.filter(x=>x.week===v.week) v.dates = dates.filter(x=>x.week===v.week)
}) })
console.log('weeks.value', weeks.value)
} }
function nextMonth() { function nextMonth() {
month = month + 1 month = month + 1

View File

@@ -33,6 +33,32 @@ const inventoryHighlights = [
</script> </script>
<template> <template>
<div> <div>
<div class="content buttons is-justify-content-flex-end">
<button class="button fs-14">
<span class="icon">
<Icon :size="18" name="material-symbols:download-rounded" />
</span>
<span>Export</span>
</button>
<button class="button fs-14">
<span class="icon">
<Icon :size="18" name="material-symbols:upload-rounded" />
</span>
<span>Import</span>
</button>
<button class="button fs-14">
<span class="icon">
<Icon :size="18" name="material-symbols:sync" />
</span>
<span>Chuyển kho</span>
</button>
<button class="button fs-14 is-primary">
<span class="icon">
<Icon :size="18" name="material-symbols:add-2-rounded" />
</span>
<span>Điều chỉnh</span>
</button>
</div>
<div class="fixed-grid has-2-cols-mobile has-4-cols"> <div class="fixed-grid has-2-cols-mobile has-4-cols">
<div class="grid"> <div class="grid">
<InventoryHighlightCard <InventoryHighlightCard

View File

@@ -13,7 +13,7 @@ const emit = defineEmits('unselect');
<button <button
@click="emit('unselect')" @click="emit('unselect')"
class="button is-white rounded-full has-text-grey absolute size-8 is-flex is-justify-content-center is-align-items-center" class="button is-white rounded-full has-text-grey absolute size-8 is-flex is-justify-content-center is-align-items-center"
style="right: 0.5rem; top: 0.5rem;" style="z-index: 1; right: 0.5rem; top: 0.5rem;"
> >
<span class="icon"> <span class="icon">
<Icon name="material-symbols:close-rounded" :size="22" /> <Icon name="material-symbols:close-rounded" :size="22" />

View File

@@ -0,0 +1,19 @@
<script setup>
const props = defineProps({
order: Object
});
</script>
<template>
<div class="is-flex is-flex-direction-column is-gap-2">
<div class="p-4 rounded-md has-background-primary-95">
<p class="has-text-grey">Trạng thái</p>
<p class="fs-17 mt-1 font-semibold has-text-primary-50">{{ order.delivery_status__name }}</p>
</div>
<div class="p-4 rounded-md has-background-grey-95">
<p class="has-text-grey">Địa chỉ giao hàng</p>
<p class="mt-1 has-text-grey-10">{{ order.customer__name }}</p>
<p class="has-text-grey-10">{{ order.customer__phone }}</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,83 @@
<script setup>
const props = defineProps({
order: Object
});
const { $dayjs } = useNuxtApp();
const historyItems = [
{
id: 1,
name: 'Tạo đơn hàng',
details: 'Đơn hàng được tạo',
icon: 'material-symbols:info-outline-rounded',
color: 'blue',
time: '2026-02-11T08:51:04.587660+07:00'
},
{
id: 2,
name: 'Xác nhận',
details: 'Đơn hàng được xác nhận',
icon: 'material-symbols:check-circle-outline-rounded',
color: 'green',
time: '2026-02-11T10:30:04.587660+07:00'
},
{
id: 3,
name: 'Xuất kho',
details: 'Hàng đã xuất kho',
icon: 'material-symbols:check-circle-outline-rounded',
color: 'green',
time: '2026-02-11T11:02:04.587660+07:00'
},
{
id: 4,
name: 'Đang giao',
details: 'Tài xế đang giao hàng',
icon: 'material-symbols:info-outline-rounded',
color: 'blue',
time: '2026-02-11T14:20:04.587660+07:00'
},
{
id: 5,
name: 'Giao hàng',
details: 'Đã giao thành công',
icon: 'material-symbols:check-circle-outline-rounded',
color: 'green',
time: '2026-02-11T17:38:04.587660+07:00'
},
]
</script>
<template>
<div>
<div class="is-flex is-flex-direction-column is-gap-2">
<div
v-for="item in historyItems"
:key="item.id"
class="is-flex is-gap-2"
>
<div class="is-flex is-flex-direction-column is-align-items-center is-gap-0.5">
<Icon
:name="item.icon"
:size="22"
:class="`has-text-${item.color}-40`"
/>
<div class="is-flex-grow-1 has-background-grey-lighter rounded-full"
style="width: 3px"
></div>
</div>
<div class="is-flex-grow-1">
<div class="is-flex is-gap-1 is-justify-content-space-between">
<div>
<p class="fs-15">{{ item.name }}</p>
<p class="fs-13 has-text-grey">{{ item.details }}</p>
</div>
<p class="is-family-monospace fs-12 has-text-grey">{{ $dayjs(item.time).format('HH:mm') }}</p>
</div>
<hr class="mt-4 mb-0 has-background-grey-95" style="height: 2px" />
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,66 @@
<script setup>
const props = defineProps({
order: Object
});
const { $dayjs, $shortenCurrency } = useNuxtApp();
const emit = defineEmits(['selectOrder', 'unselect']);
</script>
<template>
<div
:class="[
'card fs-14 is-clickable',
`has-background-${order.status__color}-95`
]"
:style="{ border: `1px solid var(--bulma-${order.status__color}-60)` }"
@click="selected ? emit('unselect') : emit('selectOrder', order.id)"
>
<div class="card-content p-4">
<div class="mb-4 is-flex is-justify-content-space-between is-gap-1">
<p class="fs-15 font-bold">{{ order.code }}</p>
<span
:class="['fs-13', `has-text-${order.payment_status__color}-40`]"
>
{{ order.payment_status__name }}
</span>
</div>
<div class="is-flex is-flex-direction-column is-gap-2">
<!-- customer info -->
<div>
<p class="has-text-grey-10">{{ order.customer__name }}</p>
<div class="has-text-grey fs-13 mt-1 is-flex is-gap-1 is-align-items-center">
<Icon name="material-symbols:call-outline-rounded" />
<p>{{ order.customer__phone }}</p>
</div>
</div>
<!-- product info -->
<div>
<p class="fs-24 has-text-grey-10 font-bold">{{ $shortenCurrency(order.total) }}</p>
<p class="fs-13 has-text-grey">{{ order.order__products.length }} sản phẩm</p>
</div>
<hr class="m-0" />
<div class="is-flex is-flex-direction-column is-gap-0.5 fs-13 has-text-grey">
<p class=" is-flex is-align-items-center is-gap-0.5">
<Icon name="material-symbols:calendar-today-outline-rounded" :size="16" />
<span>{{ $dayjs(order.create_time).format('L') }}</span>
<span></span>
<span>{{ $dayjs(order.create_time).format('HH:mm') }}</span>
</p>
<p>
NV: <span>{{ order.employee__name }}</span>
</p>
</div>
<button
v-if="order.status__name !== 'Hoàn thành'"
:class="[
'button fs-14 has-text-white',
order.status__name === 'Nháp' ? 'is-primary' : order.status__name === 'Đã xác nhận' ? 'is-orange' : 'is-success'
]"
>
{{ order.status__name === 'Nháp' ? 'Xác nhận' : order.status__name === 'Đã xác nhận' ? 'Giao hàng' : 'Hoàn thành' }}
</button>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,25 @@
<script setup>
const props = defineProps({
order: Object
});
const { $numtoString } = useNuxtApp();
</script>
<template>
<div>
<div class="is-flex is-gap-2">
<div class="is-flex-grow-1 p-3 rounded-md has-background-grey-95">
<p class="fs-13 has-text-grey">Tổng tiền</p>
<p class="font-bold">{{ $numtoString(order.total, { hasUnit: true }) }}</p>
</div>
<div class="is-flex-grow-1 p-3 rounded-md has-background-success-95">
<p class="fs-13 has-text-success-40">Đã thanh toán</p>
<p class="font-bold has-text-success-30">0 đ</p>
</div>
</div>
<div class="p-4 mt-4 rounded-md has-background-danger-95">
<p class="fs-13 has-text-danger-50">Còn lại</p>
<p class="fs-20 font-bold has-text-danger-40">{{ $numtoString(order.total, { hasUnit: true }) }}</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,34 @@
<script setup>
const props = defineProps({
order: Object
});
const { $numtoString } = useNuxtApp();
</script>
<template>
<div>
<div class="is-flex is-flex-direction-column is-gap-2">
<div
v-for="product in order.order__products"
:key="product.id"
class="p-3 has-background-grey-95 rounded-md"
>
<div class="fs-15 is-flex is-justify-content-space-between">
<p class="">{{ product.name }}</p>
<p class="font-bold">{{ $numtoString(product.total, { hasUnit: true }) }}</p>
</div>
<div class="is-flex is-gap-8 fs-13 has-text-grey-50 mt-1">
<p>SL: {{ product.quantity }}</p>
<p>Đơn giá: {{ $numtoString(product.unit_price, { hasUnit: true }) }}</p>
<p>Giảm: {{ new Intl.NumberFormat("vi-VN", { style: "percent" }).format(product.discount) }}</p>
</div>
</div>
</div>
<hr />
<div class="font-bold is-flex is-gap-2 is-justify-content-space-between is-align-items-center">
<p class="fs-15">Tổng cộng</p>
<p class="fs-18">{{ $numtoString(order.total, { hasUnit: true }) }}</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,17 @@
<script setup>
const props = defineProps({
order: Object
});
</script>
<template>
<div class="is-flex is-flex-direction-column is-gap-2 is-align-items-center">
<Icon
name="material-symbols:receipt-long-outline-rounded"
:size="50"
class="has-text-grey-70"
/>
<p>Chưa hoá đơn</p>
<button class="button is-primary has-background-purple">Tạo hoá đơn</button>
</div>
</template>

View File

@@ -10,7 +10,7 @@ const emit = defineEmits(['selectOrder', 'unselect'])
<template> <template>
<tr <tr
:class="['is-clickable', selected && 'is-selected']" :class="['is-clickable', selected && 'is-selected']"
@click="selected ? emit('unselect') : emit('selectOrder', order.id) " @click="selected ? emit('unselect') : emit('selectOrder', order.id)"
> >
<td> <td>
<div> <div>
@@ -59,7 +59,15 @@ const emit = defineEmits(['selectOrder', 'unselect'])
</div> </div>
</td> </td>
<td> <td>
<button class="button is-dark fs-13 rounded-full">Xác nhận</button> <button
v-if="order.status__name !== 'Hoàn thành'"
:class="[
'button fs-12 has-text-white rounded-lg',
order.status__name === 'Nháp' ? 'is-primary' : order.status__name === 'Đã xác nhận' ? 'is-orange' : 'is-success'
]"
>
{{ order.status__name === 'Nháp' ? 'Xác nhận' : order.status__name === 'Đã xác nhận' ? 'Giao hàng' : 'Hoàn thành' }}
</button>
</td> </td>
</tr> </tr>
</template> </template>

View File

@@ -35,10 +35,40 @@ const highlights = [
color: 'purple', color: 'purple',
unit: 'VNĐ' unit: 'VNĐ'
}, },
] ];
const viewModes = [
{ name: 'list', icon: 'material-symbols:format-list-bulleted-rounded' },
{ name: 'grid', icon: 'material-symbols:grid-view-outline-rounded' }
];
const viewMode = ref('list');
</script> </script>
<template> <template>
<div> <div>
<div class="content is-flex is-gap-2 is-justify-content-flex-end is-align-items-center">
<div class="tabs is-toggle m-0">
<ul class="is-flex-grow-0 ml-auto">
<li
v-for="mode in viewModes"
:key="mode.name"
:class="[mode.name === viewMode && 'is-active']"
@click="viewMode = mode.name"
>
<a class="px-3 py-1">
<span class="icon m-0">
<Icon :name="mode.icon" :size="18" />
</span>
</a>
</li>
</ul>
</div>
<button class="button fs-14 is-primary">
<span class="icon">
<Icon :size="18" name="material-symbols:add-2-rounded" />
</span>
<span>Tạo đơn hàng</span>
</button>
</div>
<div class="fixed-grid has-2-cols-mobile has-5-cols"> <div class="fixed-grid has-2-cols-mobile has-5-cols">
<div class="grid"> <div class="grid">
<OrderHighlightCard <OrderHighlightCard
@@ -49,6 +79,13 @@ const highlights = [
</div> </div>
</div> </div>
<OrderPipeline /> <OrderPipeline />
<OrdersTable /> <OrdersTable :viewMode="viewMode" />
</div> </div>
</template> </template>
<style scoped>
.tabs {
--bulma-tabs-toggle-link-active-background-color: var(--bulma-link-90);
--bulma-tabs-toggle-link-active-border-color: var(--bulma-link-90);
--bulma-tabs-toggle-link-active-color: var(--bulma-link-40);
}
</style>

View File

@@ -0,0 +1,48 @@
<script setup>
import OrderKanbanCard from '@/components/orders/OrderKanbanCard.vue';
const props = defineProps({
orders: Array,
statuses: Array,
});
const emit = defineEmits(['selectOrder', 'unselect']);
</script>
<template>
<div class="p-5 fixed-grid has-4-cols">
<div class="grid">
<div
v-for="status in statuses"
:key="status.id"
class="card"
style="border: none"
>
<div class="card-content p-0 is-clipped">
<div :class="['p-4 is-flex is-justify-content-space-between is-align-items-center', `has-background-${status.color}-90`]">
<p class="font-semibold has-text-grey-10">{{ status.name }}</p>
<p class="px-2 py-1 font-semibold rounded-lg has-background-white"
:style="{ border: `1px solid var(--bulma-${status.color}-60)`}"
>
{{ orders.filter(o => o.status === status.id).length }}
</p>
</div>
<hr class="m-0 has-background-grey-80" />
<div class="has-background-grey-95 p-4">
<OrderKanbanCard
v-if="orders.filter(o => o.status === status.id).length > 0"
v-for="order in orders.filter(o => o.status === status.id)"
:key="order.id"
:order="order"
@selectOrder="emit('selectOrder', order.id)"
@unselect="emit('unselect')"
/>
<p v-else class="fs-13 has-text-centered has-text-grey">
Không đơn hàng
</p>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -3,6 +3,10 @@ import OrderRow from '@/components/orders/OrderRow.vue';
import SelectedOrder from '@/components/orders/SelectedOrder.vue'; import SelectedOrder from '@/components/orders/SelectedOrder.vue';
import { pull } from 'es-toolkit'; import { pull } from 'es-toolkit';
const props = defineProps({
viewMode: String
});
const { $dayjs } = useNuxtApp(); const { $dayjs } = useNuxtApp();
const orders = [ const orders = [
@@ -480,7 +484,7 @@ function toggleStatus(id) {
</script> </script>
<template> <template>
<div> <div>
<div class="card"> <div class="card is-clipped">
<div class="card-content"> <div class="card-content">
<div class="is-flex is-gap-2 is-align-items-center"> <div class="is-flex is-gap-2 is-align-items-center">
<div class="field is-flex-grow-1 m-0"> <div class="field is-flex-grow-1 m-0">
@@ -556,37 +560,48 @@ function toggleStatus(id) {
<div class="fixed-grid has-3-cols"> <div class="fixed-grid has-3-cols">
<div class="grid"> <div class="grid">
<div :class="['cell', selectedOrder ? 'is-col-span-2' : 'is-col-span-3']"> <div :class="['cell', selectedOrder ? 'is-col-span-2' : 'is-col-span-3']">
<div class="card"> <div class="card is-clipped">
<div class="card-content p-0"> <div class="card-content p-0">
<p class="p-5 fs-17 font-semibold is-flex is-align-items-center is-gap-1"> <template v-if="viewMode === 'list'">
<Icon name="material-symbols:list-alt-outline-rounded" :size="22" /> <p class="p-5 fs-17 font-semibold is-flex is-align-items-center is-gap-1">
<span>Danh sách đơn hàng ({{ filteredOrders.length }})</span> <Icon name="material-symbols:list-alt-outline-rounded" :size="22" />
</p> <span>Danh sách đơn hàng ({{ filteredOrders.length }})</span>
<table class="table is-fullwidth is-hoverable fs-13"> </p>
<thead> <table class="table is-fullwidth is-hoverable fs-13">
<tr> <thead>
<th class="font-semibold">Đơn hàng</th> <tr>
<th class="font-semibold">Khách hàng</th> <th class="font-semibold">Đơn hàng</th>
<th class="font-semibold has-text-right">Tổng tiền</th> <th class="font-semibold">Khách hàng</th>
<th class="font-semibold has-text-centered">Trạng thái</th> <th class="font-semibold has-text-right">Tng tiền</th>
<th class="font-semibold">Thanh toán</th> <th class="font-semibold has-text-centered">Trạng thái</th>
<th class="font-semibold">Giao hàng</th> <th class="font-semibold">Thanh toán</th>
<th class="font-semibold">Ngày tạo</th> <th class="font-semibold">Giao hàng</th>
<th class="font-semibold">Thao tác</th> <th class="font-semibold">Ngày tạo</th>
</tr> <th class="font-semibold">Thao tác</th>
</thead> </tr>
<tbody> </thead>
<OrderRow <tbody>
v-for="order in filteredOrders" <OrderRow
:key="order.id" v-for="order in filteredOrders"
v-bind="{ order, selected: order.id === selectedOrder?.id }" :key="order.id"
@selectOrder="(id) => { v-bind="{ order, selected: order.id === selectedOrder?.id }"
selectedOrder = filteredOrders.find(order => order.id === id); @selectOrder="(id) => {
}" selectedOrder = filteredOrders.find(order => order.id === id);
@unselect="selectedOrder = null" }"
/> @unselect="selectedOrder = null"
</tbody> />
</table> </tbody>
</table>
</template>
<OrdersKanban
v-else
:orders="filteredOrders"
:statuses="statuses"
@selectOrder="(id) => {
selectedOrder = filteredOrders.find(order => order.id === id);
}"
@unselect="selectedOrder = null"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,46 @@
<script setup> <script setup>
import OrderDeliveryTab from '@/components/orders/OrderDeliveryTab.vue';
import OrderHistoryTab from '@/components/orders/OrderHistoryTab.vue';
import OrderPaymentTab from '@/components/orders/OrderPaymentTab.vue';
import OrderProductTab from '@/components/orders/OrderProductTab.vue';
import OrderReceiptTab from '@/components/orders/OrderReceiptTab.vue';
const props = defineProps({ const props = defineProps({
order: Object order: Object
}); });
const { $dayjs, $numtoString } = useNuxtApp(); const { $dayjs, $numtoString } = useNuxtApp();
const emit = defineEmits(['unselect']); const emit = defineEmits(['unselect']);
const tabs = [
{
name: 'Chi tiết đơn',
heading: 'Chi tiết sản phẩm',
icon: 'material-symbols:deployed-code-outline'
},
{
name: 'Giao hàng',
heading: 'Thông tin giao hàng',
icon: 'material-symbols:delivery-truck-speed-outline-rounded'
},
{
name: 'Hoá đơn',
heading: 'Hoá đơn',
icon: 'material-symbols:receipt-long-outline-rounded'
},
{
name: 'Thanh toán',
heading: 'Thanh toán',
icon: 'material-symbols:credit-card-outline'
},
{
name: 'Lịch sử',
heading: 'Lịch sử đơn hàng',
icon: 'material-symbols:history-rounded'
},
];
const activeTab = ref(tabs[0]);
</script> </script>
<template> <template>
@@ -55,35 +91,32 @@ const emit = defineEmits(['unselect']);
<button class="button fs-14 is-flex-grow-1">Ghi thanh toán</button> <button class="button fs-14 is-flex-grow-1">Ghi thanh toán</button>
</div> </div>
<hr class="m-0" /> <hr class="m-0" />
<div class="tabs is-toggle fs-13"> <div class="tabs is-toggle m-0 fs-13">
<ul> <ul>
<li class="is-active"> <li
<a> v-for="tab in tabs"
<span>Chi tiết đơn</span> :key="tab.name"
</a> :class="activeTab.name === tab.name && 'is-active'"
</li> @click="activeTab = tab"
<li> >
<a> <a>{{ tab.name }}</a>
<span>Giao hàng</span>
</a>
</li>
<li>
<a>
<span>Hoá đơn</span>
</a>
</li>
<li>
<a>
<span>Thanh toán</span>
</a>
</li>
<li>
<a>
<span>Lịch sử</span>
</a>
</li> </li>
</ul> </ul>
</div> </div>
<hr class="m-0" />
<div id="tab-content">
<div class="is-flex is-gap-1 mb-4">
<Icon :name="activeTab.icon" :size="21" />
<span class="fs-15 font-semibold">{{ activeTab.heading }}</span>
</div>
<div>
<OrderProductTab v-if="activeTab.name === 'Chi tiết đơn'" :order="order" />
<OrderDeliveryTab v-else-if="activeTab.name === 'Giao hàng'" :order="order" />
<OrderReceiptTab v-else-if="activeTab.name === 'Hoá đơn'" :order="order" />
<OrderPaymentTab v-else-if="activeTab.name === 'Thanh toán'" :order="order" />
<OrderHistoryTab v-else-if="activeTab.name === 'Lịch sử'" :order="order" />
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,19 +1,22 @@
<template> <template>
<ClientOnly> <ClientOnly>
<TopMenu @changetab="changeTab" /> <TopMenu @changetab="changeTab" />
</ClientOnly>
<ClientOnly>
<div class="container blockdiv has-text-text-20"> <div class="container blockdiv has-text-text-20">
<div class="fs-17 font-semibold mb-2 is-flex is-align-items-center is-gap-1" v-if="tab"> <div class="fs-17 font-semibold mb-2 is-flex is-align-items-center is-gap-1" v-if="tab">
<template v-if="subtab"> <template v-if="subtab">
<span>{{ tab[$store.lang] }}</span> <span>{{ tab[$store.lang] }}</span>
<SvgIcon class="mx-2" v-bind="{ name: 'right.svg', size: 17, type: 'has-text-black' }"></SvgIcon> <SvgIcon class="mx-2" v-bind="{ name: 'right.svg', size: 17, type: 'has-text-black' }"></SvgIcon>
<span>{{ subtab[$store.lang] }}</span> <span>{{ subtab[$store.lang] }}</span>
</template> </template>
<span v-else>{{ tab[$store.lang] }}</span> <div v-else>
<button class="button is-primary is-light rounded-full p-1" @click="refresh()"> <div class="is-flex is-gap-1 is-align-items-center mb-1">
<Icon name="material-symbols:refresh-rounded" :size="24" /> <p>{{ tab[$store.lang] }}</p>
</button> <button class="button is-primary is-light rounded-full p-1" @click="refresh()">
<Icon name="material-symbols:refresh-rounded" :size="20" />
</button>
</div>
<p class="has-text-grey fs-13 font-normal">Cập nhật: 16:25:54</p>
</div>
</div> </div>
<KeepAlive> <KeepAlive>
<component :is="componentMap[vbind.component]" v-bind="vbind" :key="componentKey" v-if="componentKey" /> <component :is="componentMap[vbind.component]" v-bind="vbind" :key="componentKey" v-if="componentKey" />

View File

@@ -11635,7 +11635,7 @@ a.navbar-item.is-active, a.navbar-item.is-selected,
margin: 0.5rem 0; margin: 0.5rem 0;
} }
@media screen and (max-width: 1023px) { @media screen and (max-width: 767px) {
.navbar > .container { .navbar > .container {
display: block; display: block;
} }
@@ -11684,7 +11684,7 @@ a.navbar-item.is-active, a.navbar-item.is-selected,
padding-bottom: var(--bulma-navbar-height); padding-bottom: var(--bulma-navbar-height);
} }
} }
@media screen and (min-width: 1024px) { @media screen and (min-width: 768px) {
.navbar, .navbar,
.navbar-menu, .navbar-menu,
.navbar-start, .navbar-start,

File diff suppressed because one or more lines are too long

View File

@@ -13,6 +13,7 @@ $grey: #767676;
@use "bulma/sass" with ( @use "bulma/sass" with (
$family-primary: string.unquote("'Inter', 'SF Pro', 'Helvetica', 'Arial', sans-serif"), $family-primary: string.unquote("'Inter', 'SF Pro', 'Helvetica', 'Arial', sans-serif"),
$family-monospace: string.unquote("'Roboto Mono', monospace"), $family-monospace: string.unquote("'Roboto Mono', monospace"),
$navbar-breakpoint: 768px,
$primary: $blue, $primary: $blue,
$link: $blue, $link: $blue,
$info: $cyan, $info: $cyan,