Template
first commit
This commit is contained in:
Executable
+15
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import UiParentCard from '@/components/shared/UiParentCard.vue';
|
||||
</script>
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12" md="12">
|
||||
<UiParentCard title="Icons">
|
||||
<div class="pa-7 pt-0">
|
||||
<iframe src="https://tabler-icons.io/" title="Inline Frame Example" frameborder="0" width="100%" height="650"></iframe>
|
||||
</div>
|
||||
</UiParentCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import UiParentCard from "@/components/shared/UiParentCard.vue";
|
||||
</script>
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12" md="12">
|
||||
<v-card elevation="10">
|
||||
<v-card-item>
|
||||
<h5 class="text-h5 mb-3">Sample Page</h5>
|
||||
<p class="text-body-1">This is a sample page</p>
|
||||
</v-card-item>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<!-- Breadcrumb -->
|
||||
<v-breadcrumbs :items="breadcrumbs" divider=">" class="pa-0 mb-4">
|
||||
<template v-slot:item="{ item }">
|
||||
<v-breadcrumbs-item :to="item.to" :disabled="item.disabled">
|
||||
{{ item.title }}
|
||||
</v-breadcrumbs-item>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
|
||||
<!-- Page Title -->
|
||||
<h1 class="text-h5 mb-6">Buat Registrasi</h1>
|
||||
|
||||
<!-- Registration Form -->
|
||||
<v-form ref="form" v-model="valid" lazy-validation>
|
||||
<!-- Registration Type Section -->
|
||||
<RegistrationType
|
||||
v-model:registrationData="formData.registrationType"
|
||||
@update="updateSection('registrationType')"
|
||||
/>
|
||||
|
||||
<!-- Patient Data Section -->
|
||||
<PatientData
|
||||
v-model:patientData="formData.patient"
|
||||
@update="updateSection('patient')"
|
||||
class="mt-6"
|
||||
/>
|
||||
|
||||
<!-- Appointment Schedule Section -->
|
||||
<AppointmentSchedule
|
||||
v-model:scheduleData="formData.schedule"
|
||||
@update="updateSection('schedule')"
|
||||
class="mt-6"
|
||||
/>
|
||||
|
||||
<!-- Risk Assessment Section -->
|
||||
<RiskAssessment
|
||||
v-model:riskData="formData.riskAssessment"
|
||||
@update="updateSection('riskAssessment')"
|
||||
class="mt-6"
|
||||
/>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<v-row class="mt-8">
|
||||
<v-col cols="12" class="d-flex justify-end gap-3">
|
||||
<v-btn variant="outlined" color="primary" @click="resetForm">
|
||||
Reset
|
||||
</v-btn>
|
||||
<v-btn color="primary" :loading="loading" @click="submitForm">
|
||||
Simpan Registrasi
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import RegistrationType from "~/components/apps/registration/form/RegistrationType.vue";
|
||||
import PatientData from "~/components/apps/registration/form/PatientData.vue";
|
||||
import AppointmentSchedule from "~/components/apps/registration/form/AppointmentSchedule.vue";
|
||||
import RiskAssessment from "~/components/apps/registration/form/RiskAssessment.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const form = ref();
|
||||
const valid = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const breadcrumbs = [
|
||||
{ title: "Rawat Jalan", to: "/appointment", disabled: false },
|
||||
{ title: "Registrasi", to: "/appointment/registration", disabled: false },
|
||||
{ title: "Buat Registrasi", to: "", disabled: true }
|
||||
];
|
||||
|
||||
const formData = reactive({
|
||||
registrationType: {
|
||||
visitType: "",
|
||||
treatmentType: "Rawat Jalan",
|
||||
triage: ""
|
||||
},
|
||||
patient: {
|
||||
name: "",
|
||||
medicalRecordNumber: "",
|
||||
category: "",
|
||||
birthDate: "",
|
||||
gender: "",
|
||||
address: "",
|
||||
phoneCode: "+62",
|
||||
phoneNumber: "",
|
||||
email: "",
|
||||
identityType: "",
|
||||
identityNumber: ""
|
||||
},
|
||||
schedule: {
|
||||
paymentMethod: "Pribadi",
|
||||
referralNumber: "",
|
||||
polyclinicName: "",
|
||||
doctorName: "",
|
||||
consultationDate: "",
|
||||
consultationTime: "",
|
||||
slotAvailable: true
|
||||
},
|
||||
riskAssessment: {
|
||||
walkingAbility: null,
|
||||
supportWhileSitting: null,
|
||||
score: 0
|
||||
}
|
||||
});
|
||||
|
||||
const updateSection = (section) => {
|
||||
console.log(`Section ${section} updated:`, formData[section]);
|
||||
};
|
||||
|
||||
const submitForm = async () => {
|
||||
const { valid } = await form.value.validate();
|
||||
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
try {
|
||||
// API call simulation
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
console.log("Form submitted:", formData);
|
||||
|
||||
// Navigate to success page or show notification
|
||||
router.push("/appointment/registration");
|
||||
} catch (error) {
|
||||
console.error("Error submitting form:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
form.value.reset();
|
||||
};
|
||||
</script>
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
/*Call Components*/
|
||||
import RevenueCard from '@/components/dashboard/RevenueCard.vue';
|
||||
import NewCustomer from '@/components/dashboard/NewCustomer.vue';
|
||||
import Totalincome from '@/components/dashboard/TotalIncome.vue';
|
||||
import RevenueProduct from '@/components/dashboard/RevenueProducts.vue';
|
||||
import DailyActivities from '@/components/dashboard/DailyActivities.vue';
|
||||
import BlogCards from '@/components/dashboard/BlogCards.vue';
|
||||
// definePageMeta({
|
||||
// middleware: 'role',
|
||||
// });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12" lg="8"><RevenueCard /></v-col>
|
||||
<v-col cols="12" lg="4"
|
||||
><NewCustomer class="mb-6" />
|
||||
<Totalincome />
|
||||
</v-col>
|
||||
<v-col cols="12" lg="8"><RevenueProduct/></v-col>
|
||||
<v-col cols="12" lg="4"><DailyActivities/> </v-col>
|
||||
<v-col cols="12"><BlogCards/></v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<!-- Breadcrumb -->
|
||||
<v-breadcrumbs :items="breadcrumbs" class="pa-0 mb-4">
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-home" size="small"></v-icon>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
|
||||
<!-- Form Container -->
|
||||
<v-card elevation="2">
|
||||
<v-form ref="form" v-model="valid" @submit.prevent="submitForm">
|
||||
<!-- Header Section -->
|
||||
<v-card-title class="bg-grey-lighten-3 py-3">
|
||||
<span class="text-subtitle-1 font-weight-medium"
|
||||
>DATA PASIEN</span
|
||||
>
|
||||
</v-card-title>
|
||||
|
||||
<!-- Data Diri Section -->
|
||||
<DataDiriSection
|
||||
v-model:formData="formData.dataDiri"
|
||||
v-model:isBayiBaru="formData.isBayiBaru"
|
||||
/>
|
||||
|
||||
<!-- Alamat Section -->
|
||||
<AlamatSection
|
||||
v-model:alamatIdentitas="formData.alamatIdentitas"
|
||||
v-model:alamatDomisili="formData.alamatDomisili"
|
||||
v-model:samaDenganIdentitas="formData.samaDenganIdentitas"
|
||||
/>
|
||||
|
||||
<!-- Kesehatan Section -->
|
||||
<KesehatanSection v-model:formData="formData.kesehatan" />
|
||||
|
||||
<!-- Pembayaran Section -->
|
||||
<PembayaranSection v-model:formData="formData.pembayaran" />
|
||||
|
||||
<!-- Sosial Section -->
|
||||
<SosialSection v-model:formData="formData.sosial" />
|
||||
|
||||
<!-- Penanggung Jawab Section -->
|
||||
<PenanggungJawabSection
|
||||
v-model:formData="formData.penanggungJawab"
|
||||
v-model:samaDenganPasien="formData.samaDenganPasien"
|
||||
:alamatPasien="formData.alamatDomisili"
|
||||
/>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<v-card-actions class="pa-4 bg-grey-lighten-4">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="outlined" color="grey" @click="resetForm">
|
||||
Reset
|
||||
</v-btn>
|
||||
<v-btn
|
||||
type="submit"
|
||||
variant="flat"
|
||||
color="primary"
|
||||
:loading="loading"
|
||||
:disabled="!valid"
|
||||
>
|
||||
Simpan Pasien
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import DataDiriSection from "~/components/apps/patient/form/DataDiriSection.vue";
|
||||
import AlamatSection from "~/components/apps/patient/form/AlamatSection.vue";
|
||||
import KesehatanSection from "~/components/apps/patient/form/KesehatanSection.vue";
|
||||
import PembayaranSection from "~/components/apps/patient/form/PembayaranSection.vue";
|
||||
import SosialSection from "~/components/apps/patient/form/SosialSection.vue";
|
||||
import PenanggungJawabSection from "~/components/apps/patient/form/PenanggungJawabSection.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const form = ref();
|
||||
const valid = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const breadcrumbs = [
|
||||
{
|
||||
title: "Pasien",
|
||||
disabled: false,
|
||||
href: "/patient"
|
||||
},
|
||||
{
|
||||
title: "Buat Pasien",
|
||||
disabled: true,
|
||||
href: "/patient/create"
|
||||
}
|
||||
];
|
||||
|
||||
const formData = reactive({
|
||||
isBayiBaru: false,
|
||||
samaDenganIdentitas: false,
|
||||
samaDenganPasien: false,
|
||||
dataDiri: {
|
||||
photo: null,
|
||||
namaLengkap: "",
|
||||
nomorTeleponSelular: "",
|
||||
nomorTeleponRumah: "",
|
||||
email: "",
|
||||
jenisKelamin: "",
|
||||
tempatLahir: "",
|
||||
tanggalLahir: "",
|
||||
jenisIdentitas: "",
|
||||
nomorIdentitas: "",
|
||||
namaIbuKandung: "",
|
||||
nomorRekamMedis: ""
|
||||
},
|
||||
alamatIdentitas: {
|
||||
desa: "",
|
||||
alamatLengkap: "",
|
||||
rt: "",
|
||||
rw: "",
|
||||
kodePos: ""
|
||||
},
|
||||
alamatDomisili: {
|
||||
desa: "",
|
||||
alamatLengkap: "",
|
||||
rt: "",
|
||||
rw: "",
|
||||
kodePos: ""
|
||||
},
|
||||
kesehatan: {
|
||||
golonganDarah: "",
|
||||
statusPerokok: "",
|
||||
jenisPenyakit: ""
|
||||
},
|
||||
pembayaran: {
|
||||
jenisPembayaran: ""
|
||||
},
|
||||
sosial: {
|
||||
agama: "",
|
||||
statusPernikahan: "",
|
||||
pendidikanTerakhir: "",
|
||||
pekerjaan: "",
|
||||
bahasaDikuasai: "",
|
||||
sukuEtnis: ""
|
||||
},
|
||||
penanggungJawab: {
|
||||
namaLengkap: "",
|
||||
jenisKelamin: "",
|
||||
hubunganDenganPasien: "",
|
||||
nomorTelepon: "",
|
||||
pekerjaan: "",
|
||||
alamat: ""
|
||||
}
|
||||
});
|
||||
|
||||
const submitForm = async () => {
|
||||
const validation = await form.value.validate();
|
||||
if (!validation.valid) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
// Simulasi API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
console.log("Submitting form data:", formData);
|
||||
|
||||
// Redirect ke halaman list pasien
|
||||
router.push("/patient");
|
||||
} catch (error) {
|
||||
console.error("Error saving patient:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
form.value.reset();
|
||||
};
|
||||
</script>
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
/*-For Set Blank Layout-*/
|
||||
definePageMeta({
|
||||
layout: "blank",
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="d-flex justify-center align-center text-center h-100vh">
|
||||
<div>
|
||||
<img src="@/assets/images/backgrounds/errorimg.svg" width="500" alt="404" />
|
||||
<h1 class="text-h1 pt-3">Opps!!!</h1>
|
||||
<h4 class="text-h4 my-8">This page you are looking for could not be found.</h4>
|
||||
<v-btn flat color="primary" class="mb-4" to="/">Go Back to Home</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
/*-For Set Blank Layout-*/
|
||||
definePageMeta({
|
||||
layout: "blank",
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="authentication">
|
||||
<v-container fluid class="pa-3">
|
||||
<v-row class="h-100vh d-flex justify-center align-center">
|
||||
<v-col cols="12" lg="4" xl="3" class="d-flex align-center">
|
||||
<v-card rounded="md" elevation="10" class="px-sm-1 px-0 withbg mx-auto" max-width="500">
|
||||
<v-card-item class="pa-sm-8">
|
||||
<div class="d-flex justify-center py-4">
|
||||
<LayoutFullLogo />
|
||||
</div>
|
||||
<div class="text-body-1 text-muted text-center mb-3">Your Social Campaigns</div>
|
||||
<AuthLoginForm />
|
||||
<h6 class="text-h6 text-muted font-weight-medium d-flex justify-center align-center mt-3">
|
||||
New to Matdash?
|
||||
<NuxtLink to="/auth/register"
|
||||
class="text-primary text-decoration-none text-body-1 opacity-1 font-weight-medium pl-2">
|
||||
Create an account</NuxtLink>
|
||||
</h6>
|
||||
</v-card-item>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
/*-For Set Blank Layout-*/
|
||||
definePageMeta({
|
||||
layout: "blank",
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="d-flex justify-center align-center text-center h-100">
|
||||
<div>
|
||||
<img src="~/assets/images/backgrounds/maintenance.svg" width="500" alt="under_construction" />
|
||||
<h1 class="text-h1">Opps!!!</h1>
|
||||
<h4 class="text-h4 my-8">Website is Under Construction. Check back later!</h4>
|
||||
<v-btn flat color="primary" class="mb-4" to="/">Go Back to Home</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import LogoIcon from '~/components/layout/full/logo/Logo.vue';
|
||||
/* Register form */
|
||||
import RegisterForm from '~/components/auth/RegisterForm.vue';
|
||||
/*-For Set Blank Layout-*/
|
||||
definePageMeta({
|
||||
layout: "blank",
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="pa-3">
|
||||
<v-row class="h-100vh mh-100 auth">
|
||||
<v-col cols="12" lg="5" xl="4" class="bg-surface auth">
|
||||
<div class="d-flex justify-center align-center h-100">
|
||||
<div class="mt-xl-0 mt-5 auth-card">
|
||||
<LogoIcon />
|
||||
<h2 class="text-h3 my-3 heading">Sign Up</h2>
|
||||
<div class="mb-6">Your Admin Dashboard</div>
|
||||
<RegisterForm />
|
||||
<p class="d-flex align-center justify-center textSecondary mt-6 font-weight-medium">
|
||||
Already have an Account?
|
||||
<RouterLink
|
||||
class="pl-0 text-primary opacity-1 pl-2 font-weight-medium text-decoration-none"
|
||||
height="auto"
|
||||
to="/auth/login"
|
||||
variant="plain"
|
||||
>Sign in</RouterLink
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" lg="7" xl="8" class="d-lg-flex d-none align-center justify-center authentication bg-darkgray position-relative">
|
||||
<div class="circle-top"></div>
|
||||
<div>
|
||||
<LogoIcon class="circle-bottom" />
|
||||
</div>
|
||||
<div class="d-flex justify-center align-center w-100 h-n80">
|
||||
<v-row class="justify-center z-index-2">
|
||||
<v-col xl="6" lg="7">
|
||||
<h1 class="text-h1 text-white lh-normal">
|
||||
Welcome to
|
||||
<br />
|
||||
MatDash
|
||||
</h1>
|
||||
<p class="text-h6 text-white opacity-80 font-weight-regular mt-4 lh-md">
|
||||
MatDash helps developers to build organized and well<br />
|
||||
coded dashboards full of beautiful and rich modules.
|
||||
</p>
|
||||
<v-btn to="/" size="large" color="primary" class="mt-5"> Learn More </v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,410 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<!-- Information Panel - Responsive -->
|
||||
<v-col cols="12" lg="6" xl="5">
|
||||
<v-card elevation="2" class="h-100">
|
||||
<v-card-title class="bg-primary text-white">
|
||||
<v-icon left>mdi-information</v-icon>
|
||||
INFORMATION
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-form ref="formRef" v-model="isFormValid">
|
||||
<!-- Title Menu -->
|
||||
<v-select
|
||||
v-model="menuForm.titleMenu"
|
||||
:items="menuOptions"
|
||||
label="Title Menu"
|
||||
variant="outlined"
|
||||
class="mb-3"
|
||||
prepend-inner-icon="mdi-format-title"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
|
||||
<!-- Side Menu -->
|
||||
<v-select
|
||||
v-model="menuForm.sideMenu"
|
||||
:items="menuOptions"
|
||||
label="Side Menu"
|
||||
variant="outlined"
|
||||
class="mb-3"
|
||||
prepend-inner-icon="mdi-menu"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
|
||||
<!-- Name Menu -->
|
||||
<v-text-field
|
||||
v-model="menuForm.nameMenu"
|
||||
label="Name Menu"
|
||||
variant="outlined"
|
||||
class="mb-3"
|
||||
prepend-inner-icon="mdi-tag"
|
||||
:rules="[rules.required]"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<!-- Link URL Menu -->
|
||||
<v-text-field
|
||||
v-model="menuForm.linkUrlMenu"
|
||||
label="Link URL Menu"
|
||||
variant="outlined"
|
||||
class="mb-3"
|
||||
prepend-inner-icon="mdi-link"
|
||||
placeholder="/example/path"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<!-- Icon Menu -->
|
||||
<v-text-field
|
||||
v-model="menuForm.iconMenu"
|
||||
label="Icon Menu"
|
||||
variant="outlined"
|
||||
class="mb-4"
|
||||
prepend-inner-icon="mdi-emoticon"
|
||||
placeholder="mdi-home"
|
||||
clearable
|
||||
>
|
||||
<template #append-inner>
|
||||
<v-icon
|
||||
v-if="menuForm.iconMenu"
|
||||
color="primary"
|
||||
>
|
||||
{{ menuForm.iconMenu }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-form>
|
||||
|
||||
<!-- Add Menu Button -->
|
||||
<v-btn
|
||||
color="teal"
|
||||
variant="elevated"
|
||||
@click="addMenuItem"
|
||||
:disabled="!isFormValid"
|
||||
:loading="isAdding"
|
||||
block
|
||||
size="large"
|
||||
class="mb-2"
|
||||
>
|
||||
<v-icon left>mdi-plus</v-icon>
|
||||
Add Menu
|
||||
</v-btn>
|
||||
|
||||
<!-- Clear Form Button -->
|
||||
<v-btn
|
||||
color="grey-darken-1"
|
||||
variant="outlined"
|
||||
@click="clearForm"
|
||||
block
|
||||
>
|
||||
<v-icon left>mdi-refresh</v-icon>
|
||||
Clear Form
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Menu Management Panel - Responsive -->
|
||||
<v-col cols="12" lg="6" xl="7">
|
||||
<v-card elevation="2" class="h-100">
|
||||
<v-card-title class="bg-blue text-white d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<v-icon left>mdi-menu-open</v-icon>
|
||||
CREATE CUSTOM MENU
|
||||
</div>
|
||||
<v-chip color="white" text-color="blue" size="small">
|
||||
{{ menuItems.length }} items
|
||||
</v-chip>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<!-- Action Bar -->
|
||||
<v-row class="mb-4" align="center">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
v-model="selectedReference"
|
||||
:items="references"
|
||||
label="Reference"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
prepend-inner-icon="mdi-database"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="d-flex justify-end">
|
||||
<v-btn
|
||||
color="teal"
|
||||
variant="elevated"
|
||||
@click="reloadMenu"
|
||||
:loading="isLoading"
|
||||
class="me-2"
|
||||
>
|
||||
<v-icon left>mdi-refresh</v-icon>
|
||||
<span class="d-none d-sm-inline">Reload</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="elevated"
|
||||
@click="saveAllMenus"
|
||||
:loading="isSaving"
|
||||
>
|
||||
<v-icon left>mdi-content-save</v-icon>
|
||||
<span class="d-none d-sm-inline">Save</span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Instructions -->
|
||||
<v-alert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
density="compact"
|
||||
>
|
||||
<v-icon>mdi-drag</v-icon>
|
||||
Drag The Menu List To Re-Order, Click Update Menu To Save The Position, To Add Item On Menu, Use Form Below
|
||||
</v-alert>
|
||||
|
||||
<!-- Menu Tree Header -->
|
||||
<div class="menu-header bg-blue text-white pa-2 rounded-t">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="6" sm="4">
|
||||
<strong>MENU TITLE</strong>
|
||||
</v-col>
|
||||
<v-col cols="4" sm="5" class="d-none d-sm-block">
|
||||
<strong>URL LINK</strong>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="3" class="text-right">
|
||||
<strong>ACTION</strong>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- Menu Tree -->
|
||||
<div class="menu-tree-container" style="max-height: 400px; overflow-y: auto;">
|
||||
<MenuTreeItem
|
||||
v-for="item in menuItems"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:level="0"
|
||||
@edit="editMenuItem"
|
||||
@delete="deleteMenuItem"
|
||||
@toggle="toggleMenuItem"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="menuItems.length === 0" class="text-center pa-8">
|
||||
<v-icon size="64" color="grey-lighten-1">mdi-menu-off</v-icon>
|
||||
<p class="text-grey-darken-1 mt-2">No menu items created yet</p>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Snackbar for notifications -->
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
:timeout="3000"
|
||||
location="top right"
|
||||
>
|
||||
{{ snackbar.message }}
|
||||
<template #actions>
|
||||
<v-btn icon="mdi-close" @click="snackbar.show = false" />
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MenuItem, MenuForm } from '../../types/menu'
|
||||
import type { VForm } from 'vuetify/components'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: 'Menu Builder'
|
||||
})
|
||||
|
||||
// Reactive data
|
||||
const isFormValid = ref(false)
|
||||
const isAdding = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const isSaving = ref(false)
|
||||
const formRef = ref<VForm | null>(null)
|
||||
|
||||
const menuForm = ref<MenuForm>({
|
||||
titleMenu: '',
|
||||
sideMenu: '',
|
||||
nameMenu: '',
|
||||
linkUrlMenu: '',
|
||||
iconMenu: ''
|
||||
})
|
||||
|
||||
const snackbar = ref({
|
||||
show: false,
|
||||
message: '',
|
||||
color: 'success'
|
||||
})
|
||||
|
||||
// Form validation rules
|
||||
const rules = {
|
||||
required: (value: string) => !!value || 'This field is required'
|
||||
}
|
||||
|
||||
// Use composables
|
||||
const {
|
||||
menuItems,
|
||||
menuOptions,
|
||||
references,
|
||||
selectedReference,
|
||||
loadMenus,
|
||||
saveMenu,
|
||||
deleteMenu,
|
||||
updateMenuOrder
|
||||
} = useMenuManagement()
|
||||
|
||||
// Load initial data
|
||||
onMounted(async () => {
|
||||
await loadMenus()
|
||||
})
|
||||
|
||||
// Methods
|
||||
const addMenuItem = async () => {
|
||||
if (!isFormValid.value) return
|
||||
|
||||
isAdding.value = true
|
||||
try {
|
||||
const newItem: MenuItem = {
|
||||
id: `menu-${Date.now()}`,
|
||||
title: menuForm.value.titleMenu || menuForm.value.nameMenu,
|
||||
url: menuForm.value.linkUrlMenu,
|
||||
icon: menuForm.value.iconMenu || 'mdi-circle-outline',
|
||||
parentId: undefined,
|
||||
order: [...menuItems.value].length + 1,
|
||||
isActive: true,
|
||||
reference: selectedReference.value,
|
||||
children: [] as MenuItem[]
|
||||
}
|
||||
|
||||
await saveMenu({
|
||||
...newItem,
|
||||
children: [...(newItem.children ?? [])]
|
||||
})
|
||||
clearForm()
|
||||
showSnackbar('Menu item added successfully!', 'success')
|
||||
} catch (error) {
|
||||
showSnackbar('Failed to add menu item', 'error')
|
||||
} finally {
|
||||
isAdding.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const clearForm = () => {
|
||||
menuForm.value = {
|
||||
titleMenu: '',
|
||||
sideMenu: '',
|
||||
nameMenu: '',
|
||||
linkUrlMenu: '',
|
||||
iconMenu: ''
|
||||
}
|
||||
if (formRef.value) {
|
||||
formRef.value.resetValidation()
|
||||
}
|
||||
}
|
||||
|
||||
const editMenuItem = (item: MenuItem) => {
|
||||
menuForm.value = {
|
||||
titleMenu: item.title,
|
||||
sideMenu: '',
|
||||
nameMenu: item.title,
|
||||
linkUrlMenu: item.url ?? '',
|
||||
iconMenu: item.icon
|
||||
}
|
||||
}
|
||||
|
||||
const deleteMenuItem = async (itemId: string) => {
|
||||
try {
|
||||
await deleteMenu(itemId)
|
||||
showSnackbar('Menu item deleted successfully!', 'success')
|
||||
} catch (error) {
|
||||
showSnackbar('Failed to delete menu item', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const toggleMenuItem = async (itemId: string) => {
|
||||
const item = findMenuItem(itemId)
|
||||
if (item) {
|
||||
item.isActive = !item.isActive
|
||||
await saveMenu({
|
||||
...item,
|
||||
children: [...(item.children ?? [])]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const reloadMenu = async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
await loadMenus()
|
||||
showSnackbar('Menu reloaded successfully!', 'success')
|
||||
} catch (error) {
|
||||
showSnackbar('Failed to reload menu', 'error')
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const saveAllMenus = async () => {
|
||||
isSaving.value = true
|
||||
try {
|
||||
// Save all menu changes
|
||||
for (const item of [...menuItems.value]) {
|
||||
// Cast children to mutable array if readonly
|
||||
const mutableItem = {
|
||||
...item,
|
||||
children: [...(item.children ?? [])]
|
||||
}
|
||||
await saveMenu(mutableItem)
|
||||
}
|
||||
showSnackbar('All menus saved successfully!', 'success')
|
||||
} catch (error) {
|
||||
showSnackbar('Failed to save menus', 'error')
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const findMenuItem = (id: string): MenuItem | null => {
|
||||
const findInItems = (items: MenuItem[], id: string): MenuItem | null => {
|
||||
for (const item of items) {
|
||||
if (item.id === id) return item
|
||||
if (item.children) {
|
||||
const found = findInItems(item.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return findInItems(menuItems.value, id)
|
||||
}
|
||||
|
||||
const showSnackbar = (message: string, color: string) => {
|
||||
snackbar.value = { show: true, message, color }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menu-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.menu-tree-container {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-top: none;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
</style>
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
/*Call Components*/
|
||||
import RevenueCard from '@/components/dashboard/RevenueCard.vue';
|
||||
import NewCustomer from '@/components/dashboard/NewCustomer.vue';
|
||||
import Totalincome from '@/components/dashboard/TotalIncome.vue';
|
||||
import RevenueProduct from '@/components/dashboard/RevenueProducts.vue';
|
||||
import DailyActivities from '@/components/dashboard/DailyActivities.vue';
|
||||
import BlogCards from '@/components/dashboard/BlogCards.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12" lg="8"><RevenueCard /></v-col>
|
||||
<v-col cols="12" lg="4"
|
||||
><NewCustomer class="mb-6" />
|
||||
<Totalincome />
|
||||
</v-col>
|
||||
<v-col cols="12" lg="8"><RevenueProduct/></v-col>
|
||||
<v-col cols="12" lg="4"><DailyActivities/> </v-col>
|
||||
<v-col cols="12"><BlogCards/></v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import UiParentCard from '@/components/shared/UiParentCard.vue';
|
||||
import UiChildCard from '@/components/shared/UiChildCard.vue';
|
||||
|
||||
import Basic from '@/components/ui-components/alert/Basic.vue';
|
||||
import Filled from '@/components/ui-components/alert/Filled.vue';
|
||||
import Closable from '@/components/ui-components/alert/Closable.vue';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-row>
|
||||
<!-- Basic -->
|
||||
<v-col cols="12">
|
||||
<UiChildCard title="Alert Basic">
|
||||
<Basic />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
<!-- Filled -->
|
||||
<v-col cols="12">
|
||||
<UiChildCard title="Alert Filled">
|
||||
<Filled />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
<!-- Closable -->
|
||||
<v-col cols="12">
|
||||
<UiChildCard title="Alert Closable">
|
||||
<Closable />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
/*import tabler icons*/
|
||||
import { TrashIcon, SendIcon, BellIcon } from 'vue-tabler-icons';
|
||||
import UiChildCard from '@/components/shared/UiChildCard.vue';
|
||||
// icons
|
||||
import { AccessPointIcon } from 'vue-tabler-icons';
|
||||
import BaseButtons from '@/components/ui-components/button/BaseButtons.vue';
|
||||
import ColorsButtons from '@/components/ui-components/button/ColorsButtons.vue';
|
||||
import OutlinedButtons from '@/components/ui-components/button/OutlinedButtons.vue';
|
||||
import SizeButtons from '@/components/ui-components/button/SizeButtons.vue';
|
||||
import TextButtons from '@/components/ui-components/button/TextButtons.vue';
|
||||
import IconColorSizes from '@/components/ui-components/button/IconColorSizes.vue';
|
||||
// buttons color data
|
||||
const btnsColor = ref(['primary', 'secondary', 'success', 'error', 'warning']);
|
||||
|
||||
</script>
|
||||
|
||||
// ===============================|| Ui Buttons ||=============================== //
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
|
||||
<v-row>
|
||||
<!-- Base Buttons -->
|
||||
<v-col cols="12" sm="12">
|
||||
<UiChildCard title="Default">
|
||||
<BaseButtons />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
<!-- Color Buttons -->
|
||||
<v-col cols="12" lg="6">
|
||||
<UiChildCard title="Colors">
|
||||
<ColorsButtons />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
<!-- Outlined Buttons -->
|
||||
<v-col cols="12" lg="6">
|
||||
<UiChildCard title="Outlined">
|
||||
<OutlinedButtons />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
<!-- Sizes -->
|
||||
<v-col cols="12" lg="12">
|
||||
<UiChildCard title="Size">
|
||||
<SizeButtons />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
<!-- Text Buttons -->
|
||||
<v-col cols="12" lg="6">
|
||||
<UiChildCard title="Text Color">
|
||||
<TextButtons />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
<!-- Icon Color Sizes -->
|
||||
<v-col cols="12" lg="6">
|
||||
<UiChildCard title="Icon Size">
|
||||
<IconColorSizes />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
import UiChildCard from '@/components/shared/UiChildCard.vue';
|
||||
|
||||
import CardsProps from "@/components/ui-components/cards/CardsProps.vue";
|
||||
import CardsSlots from "@/components/ui-components/cards/CardsSlots.vue";
|
||||
import CardsContentWrap from "@/components/ui-components/cards/CardsContentWrap.vue";
|
||||
import CardsMedia from "@/components/ui-components/cards/CardsMedia.vue";
|
||||
import CardsWeather from "@/components/ui-components/cards/CardsWeather.vue";
|
||||
import CardsTwitter from "@/components/ui-components/cards/CardsTwitter.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" lg="6">
|
||||
<UiChildCard title="With Props">
|
||||
<CardsProps />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" lg="6">
|
||||
<UiChildCard title="With Slots">
|
||||
<CardsSlots />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" lg="6" class="d-flex align-items-stretch">
|
||||
<UiChildCard title="Content Wrap">
|
||||
<CardsContentWrap />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="12" lg="6" class="d-flex align-items-stretch">
|
||||
<UiChildCard title="Card Media">
|
||||
<CardsMedia />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="12" lg="6" class="d-flex align-items-stretch">
|
||||
<UiChildCard title="Weather Card">
|
||||
<CardsWeather />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="12" lg="6">
|
||||
<UiChildCard title="Twitter Card">
|
||||
<CardsTwitter />
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
Executable
+111
@@ -0,0 +1,111 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import UiChildCard from '@/components/shared/UiChildCard.vue';
|
||||
const desserts = ref([
|
||||
{
|
||||
name: "Frozen Yogurt",
|
||||
calories: 159,
|
||||
},
|
||||
{
|
||||
name: "Ice cream sandwich",
|
||||
calories: 237,
|
||||
},
|
||||
{
|
||||
name: "Eclair",
|
||||
calories: 262,
|
||||
},
|
||||
{
|
||||
name: "Cupcake",
|
||||
calories: 305,
|
||||
},
|
||||
{
|
||||
name: "Gingerbread",
|
||||
calories: 356,
|
||||
},
|
||||
{
|
||||
name: "Jelly bean",
|
||||
calories: 375,
|
||||
},
|
||||
{
|
||||
name: "Lollipop",
|
||||
calories: 392,
|
||||
},
|
||||
{
|
||||
name: "Honeycomb",
|
||||
calories: 408,
|
||||
},
|
||||
{
|
||||
name: "Donut",
|
||||
calories: 452,
|
||||
},
|
||||
{
|
||||
name: "KitKat",
|
||||
calories: 518,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row class="month-table">
|
||||
<v-col cols="12" sm="12" >
|
||||
<UiChildCard title="General Table">
|
||||
<v-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Name</th>
|
||||
<th class="text-left">Calories</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in desserts" :key="item.name">
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.calories }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="12">
|
||||
<UiChildCard title="Dark Table">
|
||||
<v-table theme="dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Name</th>
|
||||
<th class="text-left">Calories</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in desserts" :key="item.name">
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.calories }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="12">
|
||||
<UiChildCard title="Header Fixed Table">
|
||||
<v-table fixed-header height="300px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Name</th>
|
||||
<th class="text-left">Calories</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in desserts" :key="item.name">
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.calories }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</UiChildCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import Shadow from "@/components/style-components/shadow/Shadow.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<Shadow/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import UiParentCard from '@/components/shared/UiParentCard.vue';
|
||||
import Heading from "@/components/style-components/typography/Heading.vue";
|
||||
import Default from "@/components/style-components/typography/DefaultText.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12" md="12">
|
||||
<UiParentCard title="Default Text">
|
||||
<Heading/>
|
||||
</UiParentCard>
|
||||
<UiParentCard title="Default Text" class="mt-6">
|
||||
<Default/>
|
||||
</UiParentCard>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
Reference in New Issue
Block a user