change layout and add component
This commit is contained in:
parent
46b84b1535
commit
9662587eda
4140
assets/img/logo/logo-imigresen.svg
Normal file
4140
assets/img/logo/logo-imigresen.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 353 KiB |
9
assets/img/logo/niise-logo.svg
Normal file
9
assets/img/logo/niise-logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
9
assets/img/logo/niise-text.svg
Normal file
9
assets/img/logo/niise-text.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
7126
assets/json/iconamoon.json
Normal file
7126
assets/json/iconamoon.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
html[data-theme="default"] {
|
||||
--color-primary: 13, 27, 42;
|
||||
--color-primary: 0, 165, 154;
|
||||
--color-secondary: 97, 176, 183;
|
||||
--color-accent: 243, 244, 246;
|
||||
--color-success: 79, 192, 103;
|
||||
@ -10,6 +10,11 @@ html[data-theme="default"] {
|
||||
--border-color: 228, 228, 231;
|
||||
--bg-1: 243, 244, 246;
|
||||
--bg-2: 255, 255, 255;
|
||||
--sidebar: 38, 50, 55;
|
||||
--sidebar-menu: 26, 35, 38;
|
||||
--sidebar-text: 255, 255, 255;
|
||||
--header: 49, 65, 71;
|
||||
--header-text: 255, 255, 255;
|
||||
--scroll-color: 170, 170, 170;
|
||||
--scroll-hover-color: 155, 155, 155;
|
||||
--fk-border-color: 228, 228, 231;
|
||||
@ -123,4 +128,4 @@ html[data-theme="custom1"] {
|
||||
--tab-border: 1px;
|
||||
--tab-radius: 0.5rem;
|
||||
--tw-shadow: #e5eaf2;
|
||||
}
|
||||
}
|
||||
|
@ -26,3 +26,7 @@
|
||||
.badge.badge-danger {
|
||||
@apply bg-danger text-white;
|
||||
}
|
||||
|
||||
.badge.badge-disabled {
|
||||
@apply bg-gray-300 text-gray-600;
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ body {
|
||||
|
||||
.w-header {
|
||||
@apply z-20 fixed top-0 right-0 px-5 py-3 duration-300 shadow-md;
|
||||
background-color: rgb(var(--bg-2));
|
||||
background-color: rgb(var(--header));
|
||||
color: rgb(var(--header-text));
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
@ -16,7 +17,8 @@ body {
|
||||
|
||||
.vertical-menu {
|
||||
@apply text-base h-screen fixed w-64 top-0 z-50 duration-300 border-l-0 shadow-md;
|
||||
background-color: rgb(var(--bg-2));
|
||||
background-color: rgb(var(--sidebar));
|
||||
color: rgb(var(--sidebar-text));
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
@ -26,17 +28,13 @@ body {
|
||||
justify-center
|
||||
transition-colors
|
||||
duration-300;
|
||||
color: rgb(var(--text-color));
|
||||
color: rgb(var(--header-text));
|
||||
}
|
||||
|
||||
.icon-btn.profile {
|
||||
@apply border;
|
||||
border-radius: var(--rounded-box);
|
||||
border: 1px solid rgb(var(--border-color));
|
||||
color: rgb(var(--text-color));
|
||||
color: rgb(var(--header-text));
|
||||
}
|
||||
|
||||
.icon-btn:hover {
|
||||
color: rgb(var(--text-color));
|
||||
background-color: rgb(var(--bg-1));
|
||||
background-color: rgb(var(--sidebar));
|
||||
}
|
||||
|
@ -2,17 +2,18 @@
|
||||
Notes: This file is the main entry point for the SCSS stylesheet.
|
||||
================================================================================*/
|
||||
|
||||
@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300..700&display=swap");
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#__nuxt {
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
font-family: "Roboto", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,9 @@ const refreshPage = () => {
|
||||
<div class="rs-loading bg-white absolute z-50 top-0 left-0 w-full h-full">
|
||||
<div class="flex justify-center text-center items-center h-screen">
|
||||
<div>
|
||||
<div class="flex justify-center items-center">
|
||||
<img
|
||||
src="@/assets/img/logo/logo-nobg.svg"
|
||||
class="w-[50px] mb-5"
|
||||
alt=""
|
||||
/>
|
||||
<div class="img-container flex justify-center items-center mb-5">
|
||||
<img src="@/assets/img/logo/niise-logo.svg" class="max-w-[60px]" />
|
||||
<img src="@/assets/img/logo/niise-text.svg" class="max-w-[120px]" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -25,6 +25,7 @@ const props = defineProps({
|
||||
'badge-success': variant === 'success',
|
||||
'badge-warning': variant === 'warning',
|
||||
'badge-danger': variant === 'danger',
|
||||
'badge-disabled': variant === 'disabled',
|
||||
}"
|
||||
>
|
||||
<Icon v-if="icon" :name="icon" :size="iconSize"></Icon>
|
||||
|
@ -12,12 +12,17 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: "md",
|
||||
},
|
||||
btnType: {
|
||||
type: String,
|
||||
default: "button",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="button"
|
||||
:type="btnType"
|
||||
:class="{
|
||||
'button-sm': size === 'sm',
|
||||
'button-md': size === 'md',
|
||||
|
@ -1,17 +1,39 @@
|
||||
<script setup>
|
||||
const route = useRoute();
|
||||
|
||||
// Get breadcrumb from page meta
|
||||
const breadcrumb = computed(() => {
|
||||
let breadcrumb = null;
|
||||
const matched = route.matched;
|
||||
const breadcrumb = matched.map((item) => {
|
||||
return {
|
||||
name: item.meta.title,
|
||||
path: item.path,
|
||||
};
|
||||
});
|
||||
|
||||
if (matched[matched.length - 1].meta?.breadcrumb) {
|
||||
breadcrumb = matched[matched.length - 1].meta.breadcrumb;
|
||||
} else {
|
||||
// if no breadcrumb in page meta, get breadcrumb from route matched
|
||||
breadcrumb = matched.map((item) => {
|
||||
return {
|
||||
name: item.meta.title,
|
||||
path: item.path,
|
||||
};
|
||||
});
|
||||
|
||||
return breadcrumb;
|
||||
}
|
||||
|
||||
// if type current overwrite path to its own path
|
||||
if (breadcrumb) {
|
||||
breadcrumb.forEach((item) => {
|
||||
if (item.type == "current") {
|
||||
item.path = route.path;
|
||||
} else if (item.type == "parent") {
|
||||
item.path = route.path.split("/").slice(0, -item.parentNo).join("/");
|
||||
}
|
||||
});
|
||||
}
|
||||
return breadcrumb;
|
||||
});
|
||||
|
||||
// Get title from page meta
|
||||
const title = computed(() => {
|
||||
const matched = route.matched;
|
||||
const title = matched[matched.length - 1].meta.title;
|
||||
@ -28,32 +50,43 @@ async function navigateMenu(path) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="breadcrumb && title"
|
||||
class="flex flex-col md:flex-row items-stretch justify-between pb-5"
|
||||
>
|
||||
<span class="text-xl font-semibold">{{ title }}</span>
|
||||
<div
|
||||
class="flex items-center text-sm"
|
||||
v-if="breadcrumb && breadcrumb.length != 0"
|
||||
>
|
||||
<span
|
||||
v-for="(item, index) in breadcrumb"
|
||||
:key="index"
|
||||
class="flex items-center text-primary"
|
||||
>
|
||||
<Icon
|
||||
v-if="index != 0"
|
||||
name="ic:round-chevron-right"
|
||||
size="14"
|
||||
class="pr-1"
|
||||
></Icon>
|
||||
<a
|
||||
@click="navigateMenu(item.path)"
|
||||
class="underline cursor-pointer hover:text-primary/90 pr-1"
|
||||
>{{ item.name }}</a
|
||||
<div v-if="breadcrumb" class="mb-6">
|
||||
<nav aria-label="Breadcrumb" class="mb-4">
|
||||
<ol class="flex items-center text-sm">
|
||||
<li class="flex items-center">
|
||||
<NuxtLink to="/" class="text-gray-500 hover:text-gray-700">
|
||||
<Icon name="mdi:home" size="16" />
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li
|
||||
v-for="(item, index) in breadcrumb"
|
||||
:key="index"
|
||||
class="flex items-center"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<Icon
|
||||
name="mdi:chevron-right"
|
||||
size="16"
|
||||
class="mx-2 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<a
|
||||
@click="navigateMenu(item.path)"
|
||||
:class="{
|
||||
'text-gray-500 hover:text-gray-700':
|
||||
index !== breadcrumb.length - 1,
|
||||
'text-primary font-medium': index === breadcrumb.length - 1,
|
||||
}"
|
||||
:aria-current="index === breadcrumb.length - 1 ? 'page' : undefined"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- <div class="flex justify-between items-center">
|
||||
<h1 class="text-2xl font-semibold text-gray-800">{{ title }}</h1>
|
||||
<slot name="right"></slot>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
@ -85,60 +85,6 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 item-center justify-items-end">
|
||||
<VDropdown placement="bottom-end" distance="13" name="language">
|
||||
<button class="icon-btn h-10 w-10 rounded-full">
|
||||
<country-flag :country="languageNow.flagCode" />
|
||||
</button>
|
||||
<template #popper>
|
||||
<ul class="header-dropdown w-full md:w-32">
|
||||
<li
|
||||
v-for="lang in langList"
|
||||
class="flex items-center justify-center hover:bg-[rgb(var(--bg-1))]"
|
||||
>
|
||||
<button
|
||||
@click="changeLanguage(lang.value)"
|
||||
class="w-full py-2 px-2 flex justify-center items-center h-10"
|
||||
>
|
||||
<div class="ml-3 flex justify-center items-center">
|
||||
<country-flag :country="lang.flagCode" />
|
||||
</div>
|
||||
<span class="grow">{{ lang.name }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<VDropdown placement="bottom-end" distance="13" name="theme">
|
||||
<button class="icon-btn h-10 w-10 rounded-full">
|
||||
<Icon size="22px" name="material-symbols:format-paint-rounded" />
|
||||
</button>
|
||||
<template #popper>
|
||||
<ul class="header-dropdown w-full md:w-52">
|
||||
<li v-for="(val, index) in themes">
|
||||
<a
|
||||
@click="setTheme(val.theme)"
|
||||
class="flex justify-between items-center cursor-pointer py-2 px-4 hover:bg-[rgb(var(--bg-1))]"
|
||||
>
|
||||
<span class="capitalize"> {{ val.theme }} </span>
|
||||
<div class="flex items-center gap-x-1">
|
||||
<div
|
||||
v-for="(color, index) in val.colors"
|
||||
class="h-[25px] w-[10px] rounded-lg"
|
||||
:style="{
|
||||
backgroundColor: rgbToHex(color.value),
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</VDropdown>
|
||||
|
||||
<button @click="toggleSearch" class="icon-btn h-10 w-10 rounded-full">
|
||||
<Icon name="ic:round-search" class="" />
|
||||
</button>
|
||||
|
||||
<VDropdown placement="bottom-end" distance="13" name="notification">
|
||||
<button class="relative icon-btn h-10 w-10 rounded-full">
|
||||
<span
|
||||
@ -202,7 +148,12 @@ onMounted(() => {
|
||||
</template>
|
||||
</VDropdown>
|
||||
|
||||
<VDropdown placement="bottom-end" distance="13" name="profile">
|
||||
<VDropdown
|
||||
placement="bottom-end"
|
||||
distance="13"
|
||||
name="profile"
|
||||
class="flex justify-center item-center"
|
||||
>
|
||||
<button class="icon-btn profile px-2">
|
||||
<img
|
||||
class="w-8 h-8 object-cover rounded-full"
|
||||
@ -212,10 +163,7 @@ onMounted(() => {
|
||||
v-if="isDesktop"
|
||||
class="grid grid-cols-1 text-left ml-3 flex-none"
|
||||
>
|
||||
<p class="font-semibold text-sm truncate w-24 mb-0">John Doe</p>
|
||||
<span class="font-medium text-xs truncate w-24"
|
||||
>RM 10,000.00</span
|
||||
>
|
||||
<p class="font-semibold text-sm truncate w-24 mb-0">Johan</p>
|
||||
</div>
|
||||
<Icon name="ic:outline-keyboard-arrow-down" class="ml-3" />
|
||||
</button>
|
||||
|
@ -62,15 +62,8 @@ function openMenu(event) {
|
||||
// Active menu
|
||||
function activeMenu(routePath) {
|
||||
return route.path == routePath
|
||||
? ` shadow-lg
|
||||
shadow-primary/50
|
||||
dark:shadow-primary/10
|
||||
text-white
|
||||
bg-gradient-to-r
|
||||
from-primary
|
||||
to-primary/90
|
||||
active-menu`
|
||||
: `transition-all duration-300 hover:ml-4`;
|
||||
? `bg-[rgb(var(--color-primary))] font-normal text-white active-menu`
|
||||
: `font-light text-white/90 md:transition-all md:duration-200 hover:md:ml-2`;
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
@ -98,10 +91,10 @@ function navigationPage(path, external) {
|
||||
v-if="item.header"
|
||||
class="text-left font-normal text-xs mx-6 mt-5 mb-2"
|
||||
>
|
||||
<span class="uppercase text-primary dark:text-primary">
|
||||
<span class="uppercase text-gray-400">
|
||||
{{ item.header ? item.header : "" }}
|
||||
</span>
|
||||
<p class="text-gray-500 dark:text-gray-500">
|
||||
<p class="text-gray-400">
|
||||
{{ item.description ? item.description : "" }}
|
||||
</p>
|
||||
</div>
|
||||
@ -130,7 +123,7 @@ function navigationPage(path, external) {
|
||||
item2.child === undefined ||
|
||||
(item2.child && item2.child.length === 0)
|
||||
"
|
||||
class="flex items-center px-4 py-3 mx-3 rounded-lg cursor-pointer"
|
||||
class="flex items-center px-6 py-3 cursor-pointer"
|
||||
@click="navigationPage(item2.path, item2.external)"
|
||||
:class="activeMenu(item2.path)"
|
||||
>
|
||||
@ -146,7 +139,7 @@ function navigationPage(path, external) {
|
||||
</NuxtLink>
|
||||
<a
|
||||
v-else
|
||||
class="flex items-center px-4 py-3 mx-3 rounded-lg cursor-pointer"
|
||||
class="flex items-center px-6 py-3 rounded-lg cursor-pointer"
|
||||
:class="activeMenu(item2.path)"
|
||||
>
|
||||
<Icon v-if="item2.icon" :name="item2.icon" size="18"></Icon>
|
||||
|
@ -17,7 +17,7 @@ const props = defineProps({
|
||||
},
|
||||
indent: {
|
||||
type: Number,
|
||||
default: 0.2,
|
||||
default: 0.5,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["openMenu"]);
|
||||
@ -61,15 +61,8 @@ function openMenu(event) {
|
||||
// Active menu
|
||||
function activeMenu(routePath) {
|
||||
return route.path == routePath
|
||||
? ` shadow-lg
|
||||
shadow-primary/50
|
||||
dark:shadow-primary/10
|
||||
text-white
|
||||
bg-gradient-to-r
|
||||
from-primary
|
||||
to-primary/90
|
||||
active-menu`
|
||||
: `transition-all duration-300 hover:ml-4`;
|
||||
? `bg-[rgb(var(--color-primary))] font-normal text-white active-menu`
|
||||
: `font-light text-white/90 md:transition-all md:duration-200 hover:md:ml-2`;
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
@ -85,7 +78,7 @@ function navigationPage(path, external) {
|
||||
}
|
||||
|
||||
const indentStyle = computed(() => {
|
||||
return { "background-color": `rgba(var(--bg-1), ${indent.value})` };
|
||||
return { "background-color": `rgba(var(--sidebar-menu), ${indent.value})` };
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -113,7 +106,7 @@ const indentStyle = computed(() => {
|
||||
v-if="
|
||||
item.child === undefined || (item.child && item.child.length === 0)
|
||||
"
|
||||
class="flex items-center px-4 py-3 mx-3 rounded-lg cursor-pointer"
|
||||
class="flex items-center px-6 py-3 cursor-pointer"
|
||||
@click="navigationPage(item.path, item.external)"
|
||||
:class="activeMenu(item.path)"
|
||||
>
|
||||
@ -128,7 +121,7 @@ const indentStyle = computed(() => {
|
||||
</NuxtLink>
|
||||
<a
|
||||
v-else
|
||||
class="flex items-center px-4 py-3 mx-3 rounded-lg cursor-pointer"
|
||||
class="flex items-center px-6 py-3 rounded-lg cursor-pointer"
|
||||
:class="activeMenu(item.path)"
|
||||
>
|
||||
<span class="mx-3 font-normal">{{ item.title }}</span>
|
||||
|
@ -31,14 +31,15 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="vertical-menu">
|
||||
<div class="py-2 px-4">
|
||||
<div class="py-2 px-4 bg-[rgb(var(--header))]">
|
||||
<nuxt-link to="/">
|
||||
<div class="flex flex-auto gap-3 justify-center items-center h-[48px]">
|
||||
<img
|
||||
class="h-10 block"
|
||||
src="@/assets/img/logo/logo-word-black.svg"
|
||||
class="h-8 block"
|
||||
src="@/assets/img/logo/logo-imigresen.svg"
|
||||
alt=""
|
||||
/>
|
||||
<span class="text-xs">Jabatan Imigresen Malaysia</span>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
export default [
|
||||
{
|
||||
header: "",
|
||||
header: "Utama",
|
||||
description: "",
|
||||
child: [
|
||||
{
|
||||
@ -10,11 +10,64 @@ export default [
|
||||
child: [],
|
||||
meta: {},
|
||||
},
|
||||
{
|
||||
title: "Carian",
|
||||
path: "/carian",
|
||||
icon: "ph:magnifying-glass",
|
||||
child: [],
|
||||
meta: {},
|
||||
},
|
||||
{
|
||||
title: "Laporan",
|
||||
path: "/laporan",
|
||||
icon: "ph:chart-bar-fill",
|
||||
child: [],
|
||||
meta: {},
|
||||
},
|
||||
],
|
||||
meta: {},
|
||||
},
|
||||
{
|
||||
header: "Fungsi",
|
||||
description: "",
|
||||
child: [
|
||||
{
|
||||
title: "Komponen",
|
||||
icon: "ph:gear-fine",
|
||||
child: [
|
||||
{
|
||||
title: "Butang",
|
||||
path: "/komponen/butang",
|
||||
child: [],
|
||||
meta: {},
|
||||
},
|
||||
{
|
||||
title: "Modal",
|
||||
path: "/komponen/modal",
|
||||
child: [],
|
||||
meta: {},
|
||||
},
|
||||
{
|
||||
title: "Data Table",
|
||||
path: "/komponen/datatable",
|
||||
child: [],
|
||||
meta: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Ikon",
|
||||
path: "/ikon",
|
||||
icon: "iconamoon:slightly-smiling-face",
|
||||
child: [],
|
||||
meta: {},
|
||||
},
|
||||
],
|
||||
meta: {},
|
||||
},
|
||||
{
|
||||
header: "Administration",
|
||||
description: "Manage your application",
|
||||
description: "",
|
||||
child: [
|
||||
{
|
||||
title: "Configuration",
|
||||
@ -78,27 +131,4 @@ export default [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Help",
|
||||
description: "Help and documentation",
|
||||
child: [
|
||||
{
|
||||
title: "Documentation",
|
||||
icon: "solar:book-bookmark-minimalistic-bold",
|
||||
path: "https://mawar-cms-docs.vercel.app",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
title: "UI Components",
|
||||
icon: "material-symbols:settings-input-component-outline-rounded",
|
||||
path: "https://corradui.datasc.dev",
|
||||
external: true,
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
auth: {
|
||||
role: ["Developer"],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -77,6 +77,7 @@
|
||||
"vue3-apexcharts": "^1.4.1",
|
||||
"vue3-click-away": "^1.2.4",
|
||||
"vue3-dropzone": "^2.0.1",
|
||||
"vue3-recaptcha-v2": "^2.0.2",
|
||||
"vuedraggable": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
122
pages/carian/index.vue
Normal file
122
pages/carian/index.vue
Normal file
@ -0,0 +1,122 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Carian",
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Carian",
|
||||
type: "current",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const categories = ref([
|
||||
"Semua Kategori",
|
||||
"Kategori 1",
|
||||
"Kategori 2",
|
||||
"Kategori 3",
|
||||
]);
|
||||
const selectedCategory = ref("Semua Kategori");
|
||||
|
||||
const criteria = ref([
|
||||
{ label: "Kriteria 1", options: ["Pilihan 1", "Pilihan 2", "Pilihan 3"] },
|
||||
{ label: "Kriteria 2", options: ["Pilihan A", "Pilihan B", "Pilihan C"] },
|
||||
{ label: "Kriteria 3", options: ["Pilihan X", "Pilihan Y", "Pilihan Z"] },
|
||||
{ label: "Kriteria 4", options: ["Pilihan 1", "Pilihan 2", "Pilihan 3"] },
|
||||
{
|
||||
label: "Kriteria 5",
|
||||
options: ["Pilihan A", "Pilihan B", "Pilihan C"],
|
||||
},
|
||||
]);
|
||||
|
||||
const searchQuery = ref("");
|
||||
|
||||
const performSearch = () => {
|
||||
// Implement search logic here
|
||||
console.log("Performing search with:", searchQuery.value);
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<rs-card>
|
||||
<template #header> Carian laman </template>
|
||||
<template #body>
|
||||
<FormKit type="form" @submit="performSearch" submit-label="Search">
|
||||
<div class="flex items-center space-x-2 mb-4">
|
||||
<FormKit
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
name="search"
|
||||
placeholder="Carian..."
|
||||
:classes="{
|
||||
outer: 'flex-grow',
|
||||
input: 'w-full',
|
||||
}"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:magnifying-glass"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
type="select"
|
||||
v-model="selectedCategory"
|
||||
:options="categories"
|
||||
placeholder="Semua Kategori"
|
||||
:classes="{
|
||||
outer: 'w-48',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-lg font-medium text-gray-700 mb-4">
|
||||
Carian Terperinci
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<FormKit
|
||||
v-for="(item, index) in criteria.slice(0, 2)"
|
||||
:key="index"
|
||||
type="select"
|
||||
:name="item.label.toLowerCase().replace(' ', '_')"
|
||||
:label="item.label"
|
||||
:options="item.options"
|
||||
placeholder="Pilih pilihan"
|
||||
>
|
||||
<template #label>
|
||||
<label
|
||||
class="formkit-label text-gray-700 dark:text-gray-200 block mb-2 font-semibold text-sm formkit-invalid:text-red-500"
|
||||
>
|
||||
{{ item.label }}
|
||||
</label>
|
||||
</template>
|
||||
</FormKit>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<FormKit
|
||||
v-for="(item, index) in criteria.slice(2)"
|
||||
:key="index"
|
||||
type="select"
|
||||
:name="item.label.toLowerCase().replace(' ', '_')"
|
||||
:label="item.label"
|
||||
:options="item.options"
|
||||
placeholder="Pilih pilihan"
|
||||
>
|
||||
<template #label>
|
||||
<label
|
||||
class="formkit-label text-gray-700 dark:text-gray-200 block mb-2 font-semibold text-sm formkit-invalid:text-red-500"
|
||||
>
|
||||
{{ item.label }}
|
||||
</label>
|
||||
</template>
|
||||
</FormKit>
|
||||
</div>
|
||||
</FormKit>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</template>
|
@ -3,6 +3,12 @@ definePageMeta({
|
||||
title: "Dashboard",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: "/",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const data1 = ref([]);
|
||||
|
@ -1,9 +1,17 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Forgot Password",
|
||||
title: "Reset Password",
|
||||
layout: "empty",
|
||||
middleware: ["dashboard"],
|
||||
});
|
||||
|
||||
const email = ref("");
|
||||
|
||||
const changePassword = () => {
|
||||
// Simulate password change request without API call
|
||||
console.log("Password change requested for email:", email.value);
|
||||
// Add your password change logic here
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -12,31 +20,55 @@ definePageMeta({
|
||||
>
|
||||
<div class="w-full md:w-3/4 lg:w-1/2 xl:w-2/6 relative">
|
||||
<rs-card class="h-screen md:h-auto px-10 md:px-16 py-12 md:py-20 mb-0">
|
||||
<div
|
||||
class="absolute -bottom-3 left-3 img-container flex justify-start items-center mb-5"
|
||||
>
|
||||
<img
|
||||
src="@/assets/img/logo/logo-word-black.svg"
|
||||
class="max-w-[90px]"
|
||||
/>
|
||||
<div class="text-center mb-8">
|
||||
<div class="img-container flex justify-center items-center mb-5">
|
||||
<img src="@/assets/img/logo/niise-logo.svg" class="max-w-[60px]" />
|
||||
<img src="@/assets/img/logo/niise-text.svg" class="max-w-[120px]" />
|
||||
</div>
|
||||
<h2 class="mt-4 text-2xl font-bold text-gray-700">
|
||||
Tukar kata laluan
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500">
|
||||
Kata laluan sementara akan dihantar ke emel anda.
|
||||
</p>
|
||||
</div>
|
||||
<h3 class="mb-4">Forgot Password</h3>
|
||||
<p class="text-slate-500 mb-6">
|
||||
Please input the correct email to reset the password.
|
||||
</p>
|
||||
<div class="grid grid-cols-1">
|
||||
<FormKit label="Email" type="email" outer-class="text-left" />
|
||||
<NuxtLink to="/reset-password">
|
||||
<FormKit type="button" input-class="w-full">Validate Email</FormKit>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<p class="mt-3 text-center text-slate-500">
|
||||
Already have an account?
|
||||
<NuxtLink to="/login" class="text-primary hover:underline"
|
||||
>Login</NuxtLink
|
||||
|
||||
<FormKit type="form" :actions="false" @submit="changePassword">
|
||||
<FormKit
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Sila masukkan emel anda"
|
||||
validation="required|email"
|
||||
:validation-messages="{
|
||||
required: 'Emel wajib diisi',
|
||||
email: 'Format emel tidak sah',
|
||||
}"
|
||||
>
|
||||
</p>
|
||||
<template #prefix>
|
||||
<Icon
|
||||
name="ph:envelope"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
</FormKit>
|
||||
|
||||
<rs-button @click="navigateTo('reset-password')" class="w-full mt-6">
|
||||
<Icon name="ph:lock-fill" class="mr-2" />
|
||||
Tukar kata laluan
|
||||
</rs-button>
|
||||
|
||||
<div class="mt-4">
|
||||
Kembali ke
|
||||
<nuxt-link to="/login" class="text-sm text-blue-500">
|
||||
log masuk
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Add any additional component-specific styles here */
|
||||
</style>
|
||||
|
117
pages/ikon/index.vue
Normal file
117
pages/ikon/index.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<script setup>
|
||||
import iconJson from "@/assets/json/iconamoon.json";
|
||||
|
||||
definePageMeta({
|
||||
title: "Set Ikons",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Set Ikons",
|
||||
type: "current",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const copyToClipboard = (text) => {
|
||||
let tagHtml = '<i class="icon-' + text + '"></i>';
|
||||
navigator.clipboard.writeText(tagHtml);
|
||||
};
|
||||
|
||||
const searchQuery = ref("");
|
||||
const displayedIcons = ref([]);
|
||||
const containerRef = ref(null);
|
||||
const batchSize = 50;
|
||||
let currentIndex = 0;
|
||||
|
||||
const filteredIcons = computed(() => {
|
||||
if (!searchQuery.value) return iconJson;
|
||||
return iconJson.filter((icon) =>
|
||||
icon.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
);
|
||||
});
|
||||
|
||||
const loadMoreIcons = () => {
|
||||
const newIcons = filteredIcons.value.slice(
|
||||
currentIndex,
|
||||
currentIndex + batchSize
|
||||
);
|
||||
displayedIcons.value.push(...newIcons);
|
||||
currentIndex += batchSize;
|
||||
};
|
||||
|
||||
const resetIconDisplay = () => {
|
||||
displayedIcons.value = [];
|
||||
currentIndex = 0;
|
||||
loadMoreIcons();
|
||||
};
|
||||
|
||||
watch(searchQuery, () => {
|
||||
resetIconDisplay();
|
||||
nextTick(() => {
|
||||
if (containerRef.value) {
|
||||
containerRef.value.scrollTop = 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
resetIconDisplay();
|
||||
});
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!containerRef.value) return;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = containerRef.value;
|
||||
if (scrollHeight - scrollTop <= clientHeight + 1) {
|
||||
loadMoreIcons();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<rs-card>
|
||||
<template #header>
|
||||
Set Ikon Icomoon (Jumlah: {{ filteredIcons.length }})
|
||||
</template>
|
||||
<template #body>
|
||||
<FormKit
|
||||
type="text"
|
||||
v-model="searchQuery"
|
||||
placeholder="Search icons..."
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-8 max-h-[70vh] overflow-y-auto"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div
|
||||
v-for="icon in displayedIcons"
|
||||
:key="icon.name"
|
||||
class="flex items-center"
|
||||
>
|
||||
<Icon :name="icon.name" class="!w-10 !h-10 mr-4"></Icon>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-sm">{{ icon.name }}</div>
|
||||
<rs-button
|
||||
@click="copyToClipboard(icon.name)"
|
||||
class="!text-xs !py-1 !px-2 !mt-1"
|
||||
>
|
||||
<Icon name="ph:copy" class="!w-4 !h-4 mr-1"></Icon>
|
||||
Copy
|
||||
</rs-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="displayedIcons.length === 0" class="text-center mt-4">
|
||||
No icons found matching your search.
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</template>
|
289
pages/komponen/butang/index.vue
Normal file
289
pages/komponen/butang/index.vue
Normal file
@ -0,0 +1,289 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Butang",
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Komponen",
|
||||
type: "current",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const showCode1 = ref(false);
|
||||
const showCode2 = ref(false);
|
||||
const showCode3 = ref(false);
|
||||
const showCode4 = ref(false);
|
||||
|
||||
const copyCode = (codeId) => {
|
||||
const codeElement = document.getElementById(codeId);
|
||||
if (codeElement) {
|
||||
navigator.clipboard
|
||||
.writeText(codeElement.textContent)
|
||||
.then(() => {
|
||||
// You can add a notification here if you want to inform the user that the code was copied
|
||||
console.log("Code copied to clipboard");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to copy code: ", err);
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
<rs-card>
|
||||
<template #header> Default </template>
|
||||
<template #body>
|
||||
<p class="mb-4">Use the <code>rs-button</code> to show badges.</p>
|
||||
<div class="flex flex-wrap items-center justify-start gap-x-6">
|
||||
<rs-button> Button </rs-button>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
class="text-sm border border-slate-200 py-1 px-3 rounded-lg my-2"
|
||||
@click="showCode1 ? (showCode1 = false) : (showCode1 = true)"
|
||||
>
|
||||
Show Code
|
||||
</button>
|
||||
</div>
|
||||
<ClientOnly>
|
||||
<transition name="fade">
|
||||
<div v-show="showCode1" v-highlight>
|
||||
<div class="relative">
|
||||
<button
|
||||
@click="copyCode('code1')"
|
||||
class="absolute top-4 right-2 text-sm bg-gray-300 hover:bg-gray-300 py-1 px-3 rounded z-10"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
|
||||
<NuxtScrollbar style="max-height: 300px">
|
||||
<pre class="language-html shadow-none">
|
||||
<code id="code1">
|
||||
<template>
|
||||
<rs-button>Button</rs-button>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
</code>
|
||||
</pre>
|
||||
</NuxtScrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card>
|
||||
<template #header> Variant </template>
|
||||
<template #body>
|
||||
<p class="mb-4">
|
||||
Button variant uses props <code>variant</code> to change the color of
|
||||
the button. There are 6 variants: <code>primary</code>,
|
||||
<code>info</code>, <code>success</code>, <code>warning</code> and
|
||||
<code>danger</code>.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-start gap-x-6">
|
||||
<rs-button variant="primary"> Primary </rs-button>
|
||||
<rs-button variant="secondary"> Secondary </rs-button>
|
||||
<rs-button variant="info"> Info </rs-button>
|
||||
<rs-button variant="success"> Success </rs-button>
|
||||
<rs-button variant="warning"> Warning </rs-button>
|
||||
<rs-button variant="danger"> Danger </rs-button>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
class="text-sm border border-slate-200 py-1 px-3 rounded-lg my-2"
|
||||
@click="showCode2 ? (showCode2 = false) : (showCode2 = true)"
|
||||
>
|
||||
Show Code
|
||||
</button>
|
||||
</div>
|
||||
<ClientOnly>
|
||||
<transition name="fade">
|
||||
<div v-show="showCode2" v-highlight>
|
||||
<NuxtScrollbar style="max-height: 300px">
|
||||
<pre class="language-html shadow-none">
|
||||
<code>
|
||||
<template>
|
||||
<rs-button variant="primary">Primary</rs-button>
|
||||
<rs-button variant="secondary">Secondary</rs-button>
|
||||
<rs-button variant="info">Info</rs-button>
|
||||
<rs-button variant="success">Success</rs-button>
|
||||
<rs-button variant="warning">Warning</rs-button>
|
||||
<rs-button variant="danger">Danger</rs-button>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
</code>
|
||||
</pre>
|
||||
</NuxtScrollbar>
|
||||
</div>
|
||||
</transition>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card>
|
||||
<template #header> Variant Type </template>
|
||||
<template #body>
|
||||
<p class="mb-4">
|
||||
Button variant type uses props <code>variant</code> to change the
|
||||
shape of the button. There are 3 variant types: <code>fill</code>,
|
||||
<code>outline</code> and <code>text</code>. <code>fill</code> is the
|
||||
default. <code>outline</code> is used to create a button with a
|
||||
border. <code>text</code> is used to create a button with no border
|
||||
and no background.
|
||||
</p>
|
||||
|
||||
<!-- Fill Button -->
|
||||
<div class="my-6">
|
||||
<div
|
||||
class="font-semibold text-lg bg-[rgb(var(--bg-1))] w-full mb-4 pl-2"
|
||||
>
|
||||
Fill
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-start gap-x-6">
|
||||
<rs-button variant="primary"> Primary </rs-button>
|
||||
<rs-button variant="secondary"> Secondary </rs-button>
|
||||
<rs-button variant="info"> Info </rs-button>
|
||||
<rs-button variant="success"> Success </rs-button>
|
||||
<rs-button variant="warning"> Warning </rs-button>
|
||||
<rs-button variant="danger"> Danger </rs-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Outline Button -->
|
||||
<div class="my-6">
|
||||
<div
|
||||
class="font-semibold text-lg bg-[rgb(var(--bg-1))] w-full mb-4 pl-2"
|
||||
>
|
||||
Outline
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-start gap-x-6">
|
||||
<rs-button variant="primary-outline"> Primary </rs-button>
|
||||
<rs-button variant="secondary-outline"> Secondary </rs-button>
|
||||
<rs-button variant="info-outline"> Info </rs-button>
|
||||
<rs-button variant="success-outline"> Success </rs-button>
|
||||
<rs-button variant="warning-outline"> Warning </rs-button>
|
||||
<rs-button variant="danger-outline"> Danger </rs-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Text Button -->
|
||||
<div class="my-6">
|
||||
<div
|
||||
class="font-semibold text-lg bg-[rgb(var(--bg-1))] w-full mb-4 pl-2"
|
||||
>
|
||||
Text
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-start gap-x-6">
|
||||
<rs-button variant="primary-text"> Primary </rs-button>
|
||||
<rs-button variant="secondary-text"> Secondary </rs-button>
|
||||
<rs-button variant="info-text"> Info </rs-button>
|
||||
<rs-button variant="success-text"> Success </rs-button>
|
||||
<rs-button variant="warning-text"> Warning </rs-button>
|
||||
<rs-button variant="danger-text"> Danger </rs-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
class="text-sm border border-slate-200 py-1 px-3 rounded-lg my-2"
|
||||
@click="showCode3 ? (showCode3 = false) : (showCode3 = true)"
|
||||
>
|
||||
Show Code
|
||||
</button>
|
||||
</div>
|
||||
<ClientOnly>
|
||||
<transition name="fade">
|
||||
<div v-show="showCode3" v-highlight>
|
||||
<NuxtScrollbar style="max-height: 300px">
|
||||
<pre class="language-html shadow-none">
|
||||
<code>
|
||||
<template>
|
||||
<!-- Fill Button -->
|
||||
<rs-button variant="primary">Primary</rs-button>
|
||||
<rs-button variant="secondary">Secondary</rs-button>
|
||||
<rs-button variant="info">Info</rs-button>
|
||||
<rs-button variant="success">Success</rs-button>
|
||||
<rs-button variant="warning">Warning</rs-button>
|
||||
<rs-button variant="danger">Danger</rs-button>
|
||||
|
||||
<!-- Outline Button -->
|
||||
<rs-button variant="primary-outline">Primary</rs-button>
|
||||
<rs-button variant="secondary-outline">Secondary</rs-button>
|
||||
<rs-button variant="info-outline">Info</rs-button>
|
||||
<rs-button variant="success-outline">Success</rs-button>
|
||||
<rs-button variant="warning-outline">Warning</rs-button>
|
||||
<rs-button variant="danger-outline">Danger</rs-button>
|
||||
|
||||
<!-- Text Button -->
|
||||
<rs-button variant="primary-text">Primary</rs-button>
|
||||
<rs-button variant="secondary-text">Secondary</rs-button>
|
||||
<rs-button variant="info-text">Info</rs-button>
|
||||
<rs-button variant="success-text">Success</rs-button>
|
||||
<rs-button variant="warning-text">Warning</rs-button>
|
||||
<rs-button variant="danger-text">Danger</rs-button>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
</code>
|
||||
</pre>
|
||||
</NuxtScrollbar>
|
||||
</div>
|
||||
</transition>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card>
|
||||
<template #header> Size </template>
|
||||
<template #body>
|
||||
<p class="mb-4">
|
||||
Button size uses props <code>size</code> to change the size of button.
|
||||
There are 3 sizes: <code>small</code>, <code>medium</code> and
|
||||
<code>large</code>.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-start gap-x-6">
|
||||
<rs-button size="sm"> Small </rs-button>
|
||||
<rs-button size="md"> Medium </rs-button>
|
||||
<rs-button size="lg"> Large </rs-button>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
class="text-sm border border-slate-200 py-1 px-3 rounded-lg my-2"
|
||||
@click="showCode4 ? (showCode4 = false) : (showCode4 = true)"
|
||||
>
|
||||
Show Code
|
||||
</button>
|
||||
</div>
|
||||
<ClientOnly>
|
||||
<transition name="fade">
|
||||
<div v-show="showCode4" v-highlight>
|
||||
<NuxtScrollbar style="max-height: 300px">
|
||||
<pre class="language-html shadow-none">
|
||||
<code>
|
||||
<template>
|
||||
<rs-button size="sm">Small</rs-button>
|
||||
<rs-button size="md">Medium</rs-button>
|
||||
<rs-button size="lg">Large</rs-button>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
</code>
|
||||
</pre>
|
||||
</NuxtScrollbar>
|
||||
</div>
|
||||
</transition>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</template>
|
17
pages/komponen/datatable/index.vue
Normal file
17
pages/komponen/datatable/index.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Data Table",
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Data Table",
|
||||
type: "current",
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
</div>
|
||||
</template>
|
17
pages/komponen/modal/index.vue
Normal file
17
pages/komponen/modal/index.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Modal",
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Modal",
|
||||
type: "current",
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
</div>
|
||||
</template>
|
335
pages/laporan/index.vue
Normal file
335
pages/laporan/index.vue
Normal file
@ -0,0 +1,335 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Laporan",
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Laporan",
|
||||
type: "current",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const data = ref([
|
||||
{
|
||||
kriteriaPertama: "Aura",
|
||||
kedua: "Hard",
|
||||
ketiga: "Business Services Sales Representative",
|
||||
keempat: "1969-04-19",
|
||||
status: "Digantung",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Chantal",
|
||||
kedua: "Nailor",
|
||||
ketiga: "Technical Services Librarian",
|
||||
keempat: "1980-01-10",
|
||||
status: "Tidak Aktif",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Cicely",
|
||||
kedua: "Sigler",
|
||||
ketiga: "Senior Research Officer",
|
||||
keempat: "1960-03-15",
|
||||
status: "Menunggu",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Coy",
|
||||
kedua: "Wollard",
|
||||
ketiga: "Customer Service Operator",
|
||||
keempat: "1982-10-12",
|
||||
status: "Aktif",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Delma",
|
||||
kedua: "Bonds",
|
||||
ketiga: "Lead Brand Manager",
|
||||
keempat: "1968-12-21",
|
||||
status: "Menunggu",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Lorin",
|
||||
kedua: "Forbes",
|
||||
ketiga: "Product Marketing Manager",
|
||||
keempat: "1974-05-08",
|
||||
status: "Aktif",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Eldon",
|
||||
kedua: "Marcellus",
|
||||
ketiga: "Software Developer",
|
||||
keempat: "1987-11-30",
|
||||
status: "Menunggu",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Allie",
|
||||
kedua: "Cordell",
|
||||
ketiga: "UX Designer",
|
||||
keempat: "1992-07-19",
|
||||
status: "Digantung",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Sheridan",
|
||||
kedua: "Caldwell",
|
||||
ketiga: "HR Manager",
|
||||
keempat: "1970-03-22",
|
||||
status: "Tidak Aktif",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Rowan",
|
||||
kedua: "Douglas",
|
||||
ketiga: "Financial Analyst",
|
||||
keempat: "1983-08-13",
|
||||
status: "Aktif",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Elvin",
|
||||
kedua: "Temple",
|
||||
ketiga: "Operations Manager",
|
||||
keempat: "1976-01-11",
|
||||
status: "Menunggu",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Gisele",
|
||||
kedua: "Erickson",
|
||||
ketiga: "Public Relations Specialist",
|
||||
keempat: "1989-09-30",
|
||||
status: "Tidak Aktif",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Taryn",
|
||||
kedua: "Ridgeway",
|
||||
ketiga: "Content Strategist",
|
||||
keempat: "1978-12-17",
|
||||
status: "Digantung",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Armand",
|
||||
kedua: "Copeland",
|
||||
ketiga: "Legal Advisor",
|
||||
keempat: "1965-04-02",
|
||||
status: "Menunggu",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Tisha",
|
||||
kedua: "Gillespie",
|
||||
ketiga: "IT Support Specialist",
|
||||
keempat: "1985-06-25",
|
||||
status: "Aktif",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Brenton",
|
||||
kedua: "Thornton",
|
||||
ketiga: "Digital Marketing Specialist",
|
||||
keempat: "1991-02-14",
|
||||
status: "Digantung",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Annis",
|
||||
kedua: "Hobson",
|
||||
ketiga: "Project Coordinator",
|
||||
keempat: "1981-05-07",
|
||||
status: "Menunggu",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Dylan",
|
||||
kedua: "Hudson",
|
||||
ketiga: "Account Manager",
|
||||
keempat: "1990-09-15",
|
||||
status: "Tidak Aktif",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Kendall",
|
||||
kedua: "Browning",
|
||||
ketiga: "Supply Chain Manager",
|
||||
keempat: "1973-07-03",
|
||||
status: "Aktif",
|
||||
tindakan: "...",
|
||||
},
|
||||
{
|
||||
kriteriaPertama: "Maris",
|
||||
kedua: "Woodard",
|
||||
ketiga: "Customer Relations Specialist",
|
||||
keempat: "1962-11-20",
|
||||
status: "Menunggu",
|
||||
tindakan: "...",
|
||||
},
|
||||
]);
|
||||
|
||||
const criteria = ref([
|
||||
{
|
||||
label: "Kriteria 1",
|
||||
options: ["Aura", "Chantal", "Cicely", "Coy", "Delma"],
|
||||
},
|
||||
{
|
||||
label: "Kriteria 2",
|
||||
options: ["Hard", "Nailor", "Sigler", "Wollard", "Bonds"],
|
||||
},
|
||||
{
|
||||
label: "Kriteria 3",
|
||||
options: [
|
||||
"Business Services Sales Representative",
|
||||
"Technical Services Librarian",
|
||||
"Senior Research Officer",
|
||||
"Customer Service Operator",
|
||||
"Lead Brand Manager",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Kriteria 4",
|
||||
options: [
|
||||
"1969-04-19",
|
||||
"1980-01-10",
|
||||
"1960-03-15",
|
||||
"1982-10-12",
|
||||
"1968-12-21",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Kriteria 5",
|
||||
options: ["Digantung", "Tidak Aktif", "Menunggu", "Aktif"],
|
||||
},
|
||||
]);
|
||||
|
||||
const selectedCriteria = ref({
|
||||
kriteria1: "",
|
||||
kriteria2: "",
|
||||
kriteria3: "",
|
||||
kriteria4: "",
|
||||
kriteria5: "",
|
||||
});
|
||||
|
||||
const filteredData = ref([...data.value]);
|
||||
|
||||
const performSearch = () => {
|
||||
filteredData.value = data.value.filter((item) => {
|
||||
return (
|
||||
(!selectedCriteria.value.kriteria1 ||
|
||||
item.kriteriaPertama.includes(selectedCriteria.value.kriteria1)) &&
|
||||
(!selectedCriteria.value.kriteria2 ||
|
||||
item.kedua.includes(selectedCriteria.value.kriteria2)) &&
|
||||
(!selectedCriteria.value.kriteria3 ||
|
||||
item.ketiga.includes(selectedCriteria.value.kriteria3)) &&
|
||||
(!selectedCriteria.value.kriteria4 ||
|
||||
item.keempat.includes(selectedCriteria.value.kriteria4)) &&
|
||||
(!selectedCriteria.value.kriteria5 ||
|
||||
item.status.includes(selectedCriteria.value.kriteria5))
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const resetFilter = () => {
|
||||
selectedCriteria.value = {
|
||||
kriteria1: "",
|
||||
kriteria2: "",
|
||||
kriteria3: "",
|
||||
kriteria4: "",
|
||||
kriteria5: "",
|
||||
};
|
||||
filteredData.value = [...data.value];
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<rs-card>
|
||||
<template #header> Laporan</template>
|
||||
<template #body>
|
||||
<FormKit type="form" :actions="false">
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<FormKit
|
||||
v-for="(item, index) in criteria.slice(0, 2)"
|
||||
:key="index"
|
||||
type="select"
|
||||
:name="item.label.toLowerCase().replace(' ', '_')"
|
||||
:label="item.label"
|
||||
:options="item.options"
|
||||
v-model="selectedCriteria[`kriteria${index + 1}`]"
|
||||
placeholder="Pilih pilihan"
|
||||
>
|
||||
<template #label>
|
||||
<label
|
||||
class="formkit-label text-gray-700 dark:text-gray-200 block mb-2 font-semibold text-sm formkit-invalid:text-red-500"
|
||||
>
|
||||
{{ item.label }}
|
||||
</label>
|
||||
</template>
|
||||
</FormKit>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<FormKit
|
||||
v-for="(item, index) in criteria.slice(2)"
|
||||
:key="index"
|
||||
type="select"
|
||||
:name="item.label.toLowerCase().replace(' ', '_')"
|
||||
:label="item.label"
|
||||
:options="item.options"
|
||||
v-model="selectedCriteria[`kriteria${index + 3}`]"
|
||||
placeholder="Pilih pilihan"
|
||||
>
|
||||
<template #label>
|
||||
<label
|
||||
class="formkit-label text-gray-700 dark:text-gray-200 block mb-2 font-semibold text-sm formkit-invalid:text-red-500"
|
||||
>
|
||||
{{ item.label }}
|
||||
</label>
|
||||
</template>
|
||||
</FormKit>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end items-center gap-2">
|
||||
<rs-button @click="resetFilter" variant="primary-outline"
|
||||
>Reset</rs-button
|
||||
>
|
||||
<rs-button type="submit" @click="performSearch">
|
||||
Hantar Laporan
|
||||
</rs-button>
|
||||
</div>
|
||||
</FormKit>
|
||||
|
||||
<rs-table
|
||||
class="mt-8"
|
||||
:key="filteredData"
|
||||
:data="filteredData"
|
||||
:pageSize="100"
|
||||
>
|
||||
<template v-slot:status="data">
|
||||
<rs-badge
|
||||
:variant="
|
||||
data.text === 'Aktif'
|
||||
? 'success'
|
||||
: data.text === 'Menunggu'
|
||||
? 'warning'
|
||||
: data.text === 'Tidak Aktif'
|
||||
? 'disabled'
|
||||
: data.text === 'Digantung'
|
||||
? 'danger'
|
||||
: 'default'
|
||||
"
|
||||
>
|
||||
{{ data.text }}
|
||||
</rs-badge>
|
||||
</template>
|
||||
</rs-table>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</template>
|
@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { useUserStore } from "~/stores/user";
|
||||
import { RecaptchaV2 } from "vue3-recaptcha-v2";
|
||||
|
||||
definePageMeta({
|
||||
title: "Login",
|
||||
@ -54,6 +55,19 @@ const login = async () => {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleWidgetId = (widgetId) => {
|
||||
console.log("Widget ID: ", widgetId);
|
||||
};
|
||||
const handleErrorCalback = () => {
|
||||
console.log("Error callback");
|
||||
};
|
||||
const handleExpiredCallback = () => {
|
||||
console.log("Expired callback");
|
||||
};
|
||||
const handleLoadCallback = (response) => {
|
||||
console.log("Load callback", response);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -62,41 +76,53 @@ const login = async () => {
|
||||
>
|
||||
<div class="w-full md:w-3/4 lg:w-1/2 xl:w-2/6 relative">
|
||||
<rs-card class="h-screen md:h-auto px-10 md:px-16 py-12 md:py-20 mb-0">
|
||||
<div
|
||||
class="absolute -bottom-3 left-3 img-container flex justify-start items-center mb-5"
|
||||
>
|
||||
<img
|
||||
src="@/assets/img/logo/logo-word-black.svg"
|
||||
class="max-w-[90px]"
|
||||
/>
|
||||
<div class="img-container flex justify-center items-center mb-5">
|
||||
<img src="@/assets/img/logo/niise-logo.svg" class="max-w-[60px]" />
|
||||
<img src="@/assets/img/logo/niise-text.svg" class="max-w-[120px]" />
|
||||
</div>
|
||||
<h3 class="mb-4">Login</h3>
|
||||
<p class="text-slate-500 mb-6">
|
||||
Welcome to Corrad. Please login to continue.
|
||||
</p>
|
||||
<p class="text-slate-500 text-lg mb-6">Log masuk ke akaun anda</p>
|
||||
<div class="grid grid-cols-2">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Username"
|
||||
v-model="username"
|
||||
validation="required"
|
||||
placeholder="Masukkan ID Pengguna"
|
||||
:classes="{
|
||||
outer: 'col-span-2',
|
||||
label: 'text-left',
|
||||
messages: 'text-left',
|
||||
}"
|
||||
/>
|
||||
<FormKit
|
||||
:type="togglePasswordVisibility ? 'text' : 'password'"
|
||||
label="Password"
|
||||
v-model="password"
|
||||
validation="required"
|
||||
:classes="{
|
||||
outer: 'col-span-2',
|
||||
label: 'text-left',
|
||||
messages: 'text-left',
|
||||
:validation-messages="{
|
||||
required: 'ID Pengguna wajib diisi.',
|
||||
}"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:user-fill"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
:type="togglePasswordVisibility ? 'text' : 'password'"
|
||||
v-model="password"
|
||||
validation="required"
|
||||
placeholder="Masukkan Kata Laluan"
|
||||
:classes="{
|
||||
outer: 'col-span-2',
|
||||
label: 'text-left',
|
||||
messages: 'text-left',
|
||||
}"
|
||||
:validation-messages="{
|
||||
required: 'Kata Laluan wajib diisi.',
|
||||
}"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:lock-key-fill"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<div
|
||||
class="bg-gray-100 hover:bg-slate-200 dark:bg-slate-700 hover:dark:bg-slate-900 h-full rounded-r-md p-3 flex justify-center items-center cursor-pointer"
|
||||
@ -111,29 +137,43 @@ const login = async () => {
|
||||
</div>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit type="checkbox" label="Remember Me" />
|
||||
<div class="col-span-2 mb-4">
|
||||
<RecaptchaV2
|
||||
@widget-id="handleWidgetId"
|
||||
@error-callback="handleErrorCalback"
|
||||
@expired-callback="handleExpiredCallback"
|
||||
@load-callback="handleLoadCallback"
|
||||
/>
|
||||
</div>
|
||||
<NuxtLink
|
||||
class="flex items-center justify-end h-5 mt-1 text-primary hover:underline"
|
||||
class="col-span-2 flex items-center justify-end h-5 mt-1 text-primary hover:underline mb-5"
|
||||
to="forgot-password"
|
||||
>Forgot Password?</NuxtLink
|
||||
>
|
||||
<!-- <NuxtLink to="/" class="col-span-2">
|
||||
</NuxtLink> -->
|
||||
Lupa Kata Laluan?
|
||||
</NuxtLink>
|
||||
<FormKit
|
||||
type="button"
|
||||
input-class="w-full"
|
||||
outer-class="col-span-2"
|
||||
@click="login"
|
||||
>
|
||||
Sign In
|
||||
Log Masuk
|
||||
|
||||
<Icon name="ph:caret-circle-right" class="!w-5 !h-5 ml-1"></Icon>
|
||||
</FormKit>
|
||||
</div>
|
||||
<p class="mt-3 text-center text-slate-500">
|
||||
Don't have an account?
|
||||
<NuxtLink to="/register" class="text-primary hover:underline"
|
||||
>Sign Up</NuxtLink
|
||||
>
|
||||
</p>
|
||||
<div class="flex justify-center items-center">
|
||||
<hr class="w-full" />
|
||||
<p class="w-full !text-gray-400">Tiada akaun?</p>
|
||||
<hr class="w-full" />
|
||||
</div>
|
||||
|
||||
<rs-button
|
||||
@click="navigateTo('/register')"
|
||||
class="w-full !bg-gray-100 !text-gray-600 border mt-5"
|
||||
>
|
||||
Daftar / Log masuk kali pertama
|
||||
</rs-button>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,12 +1,34 @@
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { RecaptchaV2 } from "vue3-recaptcha-v2";
|
||||
|
||||
definePageMeta({
|
||||
title: "Register",
|
||||
layout: "empty",
|
||||
middleware: ["dashboard"],
|
||||
});
|
||||
|
||||
const togglePasswordVisibility = ref(false);
|
||||
const togglePasswordVisibility2 = ref(false);
|
||||
const formData = ref({
|
||||
fullName: "",
|
||||
idNumber: "",
|
||||
phoneNumber: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
email: "",
|
||||
confirmEmail: "",
|
||||
subscribeNewsletter: false,
|
||||
agreeTerms: false,
|
||||
});
|
||||
|
||||
const register = () => {
|
||||
// Simulate registration without API call
|
||||
console.log("Registration attempted with:", formData.value);
|
||||
// Add your registration logic here
|
||||
};
|
||||
|
||||
const handleRecaptcha = (response) => {
|
||||
console.log("reCAPTCHA response:", response);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -15,81 +37,181 @@ const togglePasswordVisibility2 = ref(false);
|
||||
>
|
||||
<div class="w-full md:w-3/4 lg:w-1/2 xl:w-2/6 relative">
|
||||
<rs-card class="h-screen md:h-auto px-10 md:px-16 py-12 md:py-20 mb-0">
|
||||
<div
|
||||
class="absolute -bottom-3 left-3 img-container flex justify-start items-center mb-5"
|
||||
>
|
||||
<img
|
||||
src="@/assets/img/logo/logo-word-black.svg"
|
||||
class="max-w-[90px]"
|
||||
/>
|
||||
<div class="text-center mb-8">
|
||||
<div class="img-container flex justify-center items-center mb-5">
|
||||
<img src="@/assets/img/logo/niise-logo.svg" class="max-w-[60px]" />
|
||||
<img src="@/assets/img/logo/niise-text.svg" class="max-w-[120px]" />
|
||||
</div>
|
||||
<h2 class="mt-4 text-2xl font-bold text-gray-700">Daftar Akaun</h2>
|
||||
<p class="text-sm text-gray-500">Semua medan adalah wajib</p>
|
||||
</div>
|
||||
<h3 class="mb-4">Sign Up</h3>
|
||||
<p class="text-slate-500 mb-6 col-sp">
|
||||
Please fill in the form to create an account.
|
||||
</p>
|
||||
<FormKit label="Username" type="text" label-class="text-left" />
|
||||
<FormKit label="Email" type="email" label-class="text-left" />
|
||||
<FormKit
|
||||
:type="togglePasswordVisibility ? 'text' : 'password'"
|
||||
label="Password"
|
||||
type="password"
|
||||
label-class="text-left"
|
||||
>
|
||||
<template #suffix>
|
||||
<div
|
||||
class="bg-gray-100 hover:bg-slate-200 dark:bg-slate-700 hover:dark:bg-slate-900 h-full rounded-r-md p-3 flex justify-center items-center cursor-pointer"
|
||||
@click="togglePasswordVisibility = !togglePasswordVisibility"
|
||||
>
|
||||
<Icon
|
||||
v-if="!togglePasswordVisibility"
|
||||
name="ion:eye-outline"
|
||||
size="19"
|
||||
></Icon>
|
||||
<Icon v-else name="ion:eye-off-outline" size="19"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
:type="togglePasswordVisibility2 ? 'text' : 'password'"
|
||||
label="Re-enter Password"
|
||||
type="password"
|
||||
label-class="text-left"
|
||||
>
|
||||
<template #suffix>
|
||||
<div
|
||||
class="bg-gray-100 hover:bg-slate-200 dark:bg-slate-700 hover:dark:bg-slate-900 h-full rounded-r-md p-3 flex justify-center items-center cursor-pointer"
|
||||
@click="togglePasswordVisibility2 = !togglePasswordVisibility2"
|
||||
>
|
||||
<Icon
|
||||
v-if="!togglePasswordVisibility2"
|
||||
name="ion:eye-outline"
|
||||
size="19"
|
||||
></Icon>
|
||||
<Icon v-else name="ion:eye-off-outline" size="19"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="agreement"
|
||||
outer-class="col-span-1 md:col-span-2"
|
||||
>
|
||||
<template #label
|
||||
>I agree to the
|
||||
<a class="text-primary hover:underline ml-1">Term and Services</a>
|
||||
</template>
|
||||
</FormKit>
|
||||
|
||||
<NuxtLink to="/" class="col-span-1 md:col-span-2">
|
||||
<FormKit type="button" input-class="w-full">Sign up</FormKit>
|
||||
</NuxtLink>
|
||||
|
||||
<p class="mt-3 text-center text-slate-500">
|
||||
Already have an account?
|
||||
<NuxtLink to="/login" class="text-primary hover:underline"
|
||||
>Login</NuxtLink
|
||||
<FormKit type="form" :actions="false" @submit="register">
|
||||
<FormKit
|
||||
type="text"
|
||||
name="fullName"
|
||||
placeholder="Nama Penuh"
|
||||
validation="required"
|
||||
:validation-messages="{
|
||||
required: 'Nama Penuh wajib diisi',
|
||||
}"
|
||||
>
|
||||
</p>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:user-fill"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-0 md:gap-4">
|
||||
<FormKit
|
||||
type="text"
|
||||
name="idNumber"
|
||||
placeholder="Nombor Mykad / Nombor Pasport"
|
||||
validation="required"
|
||||
:validation-messages="{
|
||||
required: 'Nombor Mykad / Nombor Pasport wajib diisi',
|
||||
}"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:identification-card-fill"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
type="tel"
|
||||
name="phoneNumber"
|
||||
placeholder="Nombor Telefon"
|
||||
validation="required"
|
||||
:validation-messages="{
|
||||
required: 'Nombor Telefon wajib diisi',
|
||||
}"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:device-mobile-camera-fill"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-0 md:gap-4">
|
||||
<FormKit
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Kata Laluan"
|
||||
validation="required"
|
||||
:validation-messages="{
|
||||
required: 'Kata Laluan wajib diisi',
|
||||
}"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:lock-key-fill"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
type="password"
|
||||
name="confirmPassword"
|
||||
placeholder="Pengesahan Kata Laluan"
|
||||
validation="required|confirm"
|
||||
:validation-messages="{
|
||||
required: 'Pengesahan Kata Laluan wajib diisi',
|
||||
confirm: 'Kata Laluan tidak sepadan',
|
||||
}"
|
||||
:validation-rules="{
|
||||
confirm: (value) => value === value.password,
|
||||
}"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:lock-key-fill"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-0 md:gap-4">
|
||||
<FormKit
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Emel"
|
||||
validation="required|email"
|
||||
:validation-messages="{
|
||||
required: 'Emel wajib diisi',
|
||||
email: 'Format emel tidak sah',
|
||||
}"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:envelope-fill"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
type="email"
|
||||
name="confirmEmail"
|
||||
placeholder="Pengesahan Emel"
|
||||
validation="required|confirm"
|
||||
:validation-messages="{
|
||||
required: 'Pengesahan Emel wajib diisi',
|
||||
confirm: 'Emel tidak sepadan',
|
||||
}"
|
||||
:validation-rules="{
|
||||
confirm: (value) => value === value.email,
|
||||
}"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<Icon
|
||||
name="ph:envelope-fill"
|
||||
class="!w-5 !h-5 ml-3 text-gray-500"
|
||||
></Icon>
|
||||
</template>
|
||||
</FormKit>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-start mb-4 mt-2">
|
||||
<RecaptchaV2 @verify="handleRecaptcha" />
|
||||
</div>
|
||||
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
name="subscribeNewsletter"
|
||||
label="Melanggan ke newsletter bulanan"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
name="agreeTerms"
|
||||
label="Setuju dengan terma perkhidmatan"
|
||||
validation="accepted"
|
||||
:validation-messages="{
|
||||
accepted: 'Anda mesti bersetuju dengan terma perkhidmatan',
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
Setuju dengan
|
||||
<a href="#" class="text-blue-600 ml-1">terma perkhidmatan</a>
|
||||
</template>
|
||||
</FormKit>
|
||||
|
||||
<rs-button btn-type="submit" class="w-full"> Daftar Akaun </rs-button>
|
||||
</FormKit>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<p class="text-sm text-gray-500">
|
||||
Sudah mempunyai akaun?
|
||||
<nuxt-link to="/login" class="text-blue-600">Log Masuk</nuxt-link>
|
||||
</p>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,42 +0,0 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Reset Password",
|
||||
layout: "empty",
|
||||
middleware: ["dashboard"],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex-none md:flex justify-center text-center items-center h-screen"
|
||||
>
|
||||
<div class="w-full md:w-3/4 lg:w-1/2 xl:w-2/6 relative">
|
||||
<rs-card class="h-screen md:h-auto px-10 md:px-16 py-12 md:py-20 mb-0">
|
||||
<div
|
||||
class="absolute -bottom-3 left-3 img-container flex justify-start items-center mb-5"
|
||||
>
|
||||
<img
|
||||
src="@/assets/img/logo/logo-word-black.svg"
|
||||
class="max-w-[90px]"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="mb-4">Reset Password</h3>
|
||||
<p class="text-slate-500 mb-6">
|
||||
Please fill in the form to reset your password.
|
||||
</p>
|
||||
<div class="grid grid-cols-1">
|
||||
<FormKit label="Email" type="email" outer-class="text-left" />
|
||||
<FormKit label="Password" type="password" outer-class="text-left" />
|
||||
<FormKit
|
||||
label="Re-enter Password"
|
||||
type="password"
|
||||
outer-class="text-left"
|
||||
/>
|
||||
<NuxtLink to="/login">
|
||||
<FormKit type="button" input-class="w-full">Reset Password</FormKit>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
8
plugins/recaptcha.client.js
Normal file
8
plugins/recaptcha.client.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { install } from "vue3-recaptcha-v2";
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.use(install, {
|
||||
sitekey: "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
|
||||
cnDomains: false,
|
||||
});
|
||||
});
|
@ -46,17 +46,19 @@ model role {
|
||||
}
|
||||
|
||||
model lookup {
|
||||
lookupID Int @id @default(autoincrement())
|
||||
lookupOrder Int?
|
||||
lookupTitle String? @db.VarChar(255)
|
||||
lookupRefCode String? @db.VarChar(255)
|
||||
lookupValue String? @db.VarChar(255)
|
||||
lookupType String? @db.VarChar(255)
|
||||
lookupStatus String? @db.VarChar(255)
|
||||
lookupCreatedDate DateTime? @db.DateTime(0)
|
||||
lookupModifiedDate DateTime? @db.DateTime(0)
|
||||
assistant assistant[]
|
||||
project project[]
|
||||
lookupID Int @id @default(autoincrement())
|
||||
lookupOrder Int?
|
||||
lookupTitle String? @db.VarChar(255)
|
||||
lookupRefCode String? @db.VarChar(255)
|
||||
lookupValue String? @db.VarChar(255)
|
||||
lookupType String? @db.VarChar(255)
|
||||
lookupStatus String? @db.VarChar(255)
|
||||
lookupCreatedDate DateTime? @db.DateTime(0)
|
||||
lookupModifiedDate DateTime? @db.DateTime(0)
|
||||
assistant assistant[]
|
||||
project project[]
|
||||
project_project_projectViewTypeTolookup project[] @relation("project_projectViewTypeTolookup")
|
||||
thread thread[]
|
||||
}
|
||||
|
||||
model userrole {
|
||||
@ -152,17 +154,21 @@ model project {
|
||||
projectDescription String? @db.VarChar(255)
|
||||
projectDefault Boolean? @default(dbgenerated("b'0'")) @db.Bit(1)
|
||||
projectType Int?
|
||||
projectPublic Boolean? @db.Bit(1)
|
||||
projectViewType Int?
|
||||
projectCreatedDate DateTime? @db.DateTime(0)
|
||||
projectModifiedDate DateTime? @db.DateTime(0)
|
||||
chat chat[]
|
||||
user user? @relation(fields: [userID], references: [userID], onDelete: NoAction, onUpdate: NoAction, map: "project_ibfk_2")
|
||||
lookup lookup? @relation(fields: [projectType], references: [lookupID], onDelete: NoAction, onUpdate: NoAction, map: "project_ibfk_3")
|
||||
lookup_project_projectViewTypeTolookup lookup? @relation("project_projectViewTypeTolookup", fields: [projectViewType], references: [lookupID], onDelete: NoAction, onUpdate: NoAction, map: "project_ibfk_4")
|
||||
project_permission_project_permission_projectPermissionIDToproject project_permission? @relation("project_permission_projectPermissionIDToproject")
|
||||
project_permission_project_permission_projectIDToproject project_permission[] @relation("project_permission_projectIDToproject")
|
||||
repository repository[]
|
||||
|
||||
@@index([projectType], map: "projectType")
|
||||
@@index([userID], map: "userID")
|
||||
@@index([projectViewType], map: "projectViewType")
|
||||
}
|
||||
|
||||
model project_permission {
|
||||
@ -205,12 +211,15 @@ model thread {
|
||||
assistantID Int?
|
||||
threadTitle String? @db.VarChar(255)
|
||||
threadOAIID String? @unique(map: "threadOAIID") @db.VarChar(255)
|
||||
threadStatus Int?
|
||||
threadCreatedDate DateTime? @db.DateTime(0)
|
||||
threadModifiedDate DateTime? @db.DateTime(0)
|
||||
chat chat[]
|
||||
assistant assistant? @relation(fields: [assistantID], references: [assistantID], onDelete: NoAction, onUpdate: NoAction, map: "thread_ibfk_1")
|
||||
user user? @relation(fields: [userID], references: [userID], onDelete: NoAction, onUpdate: NoAction, map: "thread_ibfk_2")
|
||||
lookup lookup? @relation(fields: [threadStatus], references: [lookupID], onDelete: NoAction, onUpdate: NoAction, map: "thread_ibfk_3")
|
||||
|
||||
@@index([assistantID], map: "assistantID")
|
||||
@@index([userID], map: "userID")
|
||||
@@index([threadStatus], map: "threadStatus")
|
||||
}
|
||||
|
@ -9227,6 +9227,11 @@ vue3-perfect-scrollbar@^1.6.1:
|
||||
perfect-scrollbar "^1.5.5"
|
||||
postcss-import "^12.0.0"
|
||||
|
||||
vue3-recaptcha-v2@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/vue3-recaptcha-v2/-/vue3-recaptcha-v2-2.0.2.tgz#127d568d90cd3de3588429eb500d16f408380f13"
|
||||
integrity sha512-uIxJG4+BVquSVJxCLdkJV3Y0IgXPG+sWqAG34qwHt9MungDFshuKprtrN48oJHcLXePLey/FEo2B5pbfMB5cjQ==
|
||||
|
||||
vue@^2.5.16:
|
||||
version "2.7.14"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.14.tgz#3743dcd248fd3a34d421ae456b864a0246bafb17"
|
||||
|
Loading…
x
Reference in New Issue
Block a user