first commit

This commit is contained in:
2025-11-26 07:49:54 +00:00
commit d8685ccf10
468 changed files with 41346 additions and 0 deletions

View File

@@ -0,0 +1,236 @@
<template>
<div>
<!-- Alamat Identitas -->
<v-card-subtitle
class="bg-grey-lighten-4 text-subtitle-2 font-weight-medium py-2"
>
ALAMAT IDENTITAS
</v-card-subtitle>
<v-card-text>
<v-row>
<!-- Desa/Kelurahan -->
<v-col cols="12">
<v-text-field
v-model="localAlamatIdentitas.desa"
label="Desa/Kelurahan"
placeholder="Cari desa atau kelurahan..."
variant="outlined"
density="compact"
prepend-inner-icon="mdi-magnify"
/>
</v-col>
<!-- Alamat Lengkap -->
<v-col cols="12">
<v-textarea
v-model="localAlamatIdentitas.alamatLengkap"
label="Alamat Lengkap"
placeholder="Alamat"
variant="outlined"
density="compact"
rows="3"
:rules="[rules.required]"
required
/>
</v-col>
<!-- RT -->
<v-col cols="12" md="4">
<v-text-field
v-model="localAlamatIdentitas.rt"
label="RT"
placeholder="RT"
variant="outlined"
density="compact"
counter="3"
maxlength="3"
/>
</v-col>
<!-- RW -->
<v-col cols="12" md="4">
<v-text-field
v-model="localAlamatIdentitas.rw"
label="RW"
placeholder="RW"
variant="outlined"
density="compact"
counter="3"
maxlength="3"
/>
</v-col>
<!-- Kode Pos -->
<v-col cols="12" md="4">
<v-text-field
v-model="localAlamatIdentitas.kodePos"
label="Kode Pos"
placeholder="Kode Pos"
variant="outlined"
density="compact"
counter="5"
maxlength="5"
/>
</v-col>
</v-row>
</v-card-text>
<!-- Alamat Domisili -->
<v-card-subtitle
class="bg-grey-lighten-4 text-subtitle-2 font-weight-medium py-2"
>
ALAMAT DOMISILI
</v-card-subtitle>
<v-card-text>
<!-- Checkbox Samakan dengan alamat identitas -->
<v-row>
<v-col cols="12">
<v-checkbox
v-model="samaDenganIdentitas"
label="Samakan dengan alamat identitas"
density="compact"
@change="handleSamaDenganIdentitas"
/>
</v-col>
</v-row>
<v-row v-if="!samaDenganIdentitas">
<!-- Desa/Kelurahan -->
<v-col cols="12">
<v-text-field
v-model="localAlamatDomisili.desa"
label="Desa/Kelurahan"
placeholder="Cari desa atau kelurahan..."
variant="outlined"
density="compact"
prepend-inner-icon="mdi-magnify"
/>
</v-col>
<!-- Alamat Lengkap -->
<v-col cols="12">
<v-textarea
v-model="localAlamatDomisili.alamatLengkap"
label="Alamat Lengkap"
placeholder="Alamat"
variant="outlined"
density="compact"
rows="3"
/>
</v-col>
<!-- RT -->
<v-col cols="12" md="4">
<v-text-field
v-model="localAlamatDomisili.rt"
label="RT"
placeholder="RT"
variant="outlined"
density="compact"
counter="3"
maxlength="3"
/>
</v-col>
<!-- RW -->
<v-col cols="12" md="4">
<v-text-field
v-model="localAlamatDomisili.rw"
label="RW"
placeholder="RW"
variant="outlined"
density="compact"
counter="3"
maxlength="3"
/>
</v-col>
<!-- Kode Pos -->
<v-col cols="12" md="4">
<v-text-field
v-model="localAlamatDomisili.kodePos"
label="Kode Pos"
placeholder="Kode Pos"
variant="outlined"
density="compact"
counter="5"
maxlength="5"
/>
</v-col>
</v-row>
</v-card-text>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
const props = defineProps({
alamatIdentitas: {
type: Object,
required: true
},
alamatDomisili: {
type: Object,
required: true
},
samaDenganIdentitas: {
type: Boolean,
default: false
}
});
const emit = defineEmits([
"update:alamatIdentitas",
"update:alamatDomisili",
"update:samaDenganIdentitas"
]);
const localAlamatIdentitas = ref({ ...props.alamatIdentitas });
const localAlamatDomisili = ref({ ...props.alamatDomisili });
const samaDenganIdentitas = ref(props.samaDenganIdentitas);
const rules = {
required: (v) => !!v || "Field ini wajib diisi"
};
const handleSamaDenganIdentitas = () => {
if (samaDenganIdentitas.value) {
localAlamatDomisili.value = { ...localAlamatIdentitas.value };
} else {
localAlamatDomisili.value = {
desa: "",
alamatLengkap: "",
rt: "",
rw: "",
kodePos: ""
};
}
};
// Watch for changes
watch(
localAlamatIdentitas,
(newVal) => {
emit("update:alamatIdentitas", newVal);
if (samaDenganIdentitas.value) {
localAlamatDomisili.value = { ...newVal };
}
},
{ deep: true }
);
watch(
localAlamatDomisili,
(newVal) => {
emit("update:alamatDomisili", newVal);
},
{ deep: true }
);
watch(samaDenganIdentitas, (newVal) => {
emit("update:samaDenganIdentitas", newVal);
});
</script>

View File

@@ -0,0 +1,516 @@
<template>
<div>
<!-- Section Header -->
<v-card-subtitle
class="bg-grey-lighten-4 text-subtitle-2 font-weight-medium py-2"
>
DATA DIRI
</v-card-subtitle>
<v-card-text>
<!-- Checkbox Bayi Baru Lahir -->
<v-row>
<v-col cols="12" class="text-right py-0">
<v-checkbox
v-model="isBayiBaru"
label="Bayi baru lahir"
density="compact"
hide-details
/>
</v-col>
</v-row>
<v-row>
<!-- Photo Upload -->
<v-col cols="12" md="2" class="text-center">
<v-avatar size="100" color="grey-lighten-3">
<v-icon size="50" color="grey">mdi-account</v-icon>
</v-avatar>
<v-btn
variant="text"
color="primary"
size="small"
class="mt-2"
@click="uploadPhoto"
>
Upload Foto
</v-btn>
</v-col>
<!-- Form Fields -->
<v-col cols="12" md="10">
<v-row>
<!-- Nama Lengkap -->
<v-col cols="12">
<v-text-field
v-model="localData.namaLengkap"
label="Nama Lengkap"
placeholder="Nama Lengkap"
variant="outlined"
density="compact"
:rules="[rules.required]"
required
@input="debouncedParseNameToFhir"
@paste="handlePaste"
/>
<!-- FHIR Name Preview - Auto shows when typing -->
<v-expand-transition>
<v-alert
v-if="fhirName && localData.namaLengkap && isTypingComplete"
density="compact"
type="info"
variant="tonal"
class="mt-2"
closable
@click:close="hideFhirPreview"
>
<template v-slot:title>
<span class="text-body-2">FHIR Name Structure</span>
</template>
<div class="text-caption mt-2">
<v-row dense>
<v-col
v-if="fhirName.prefix && fhirName.prefix.length > 0"
cols="12"
>
<v-chip
size="x-small"
color="primary"
variant="outlined"
class="mr-1"
>
Prefix
</v-chip>
<span class="font-weight-medium">{{
fhirName.prefix.join(", ")
}}</span>
</v-col>
<v-col
v-if="fhirName.given && fhirName.given.length > 0"
cols="12"
>
<v-chip
size="x-small"
color="success"
variant="outlined"
class="mr-1"
>
Given
</v-chip>
<span class="font-weight-medium">{{
fhirName.given.join(" ")
}}</span>
</v-col>
<v-col v-if="fhirName.family" cols="12">
<v-chip
size="x-small"
color="warning"
variant="outlined"
class="mr-1"
>
Family
</v-chip>
<span class="font-weight-medium">{{
fhirName.family
}}</span>
</v-col>
<v-col
v-if="fhirName.suffix && fhirName.suffix.length > 0"
cols="12"
>
<v-chip
size="x-small"
color="info"
variant="outlined"
class="mr-1"
>
Suffix
</v-chip>
<span class="font-weight-medium">{{
fhirName.suffix.join(", ")
}}</span>
</v-col>
</v-row>
</div>
</v-alert>
</v-expand-transition>
<!-- Optional: Inline chips preview -->
<div
v-if="
fhirName &&
localData.namaLengkap &&
isTypingComplete &&
!showDetailedPreview
"
class="mt-2"
>
<v-chip-group>
<v-chip
v-for="(prefix, index) in fhirName.prefix || []"
:key="`prefix-${index}`"
size="x-small"
color="primary"
>
{{ prefix }}
</v-chip>
<v-chip
v-for="(given, index) in fhirName.given || []"
:key="`given-${index}`"
size="x-small"
color="success"
>
{{ given }}
</v-chip>
<v-chip v-if="fhirName.family" size="x-small" color="warning">
{{ fhirName.family }}
</v-chip>
<v-chip
v-for="(suffix, index) in fhirName.suffix || []"
:key="`suffix-${index}`"
size="x-small"
color="info"
>
{{ suffix }}
</v-chip>
</v-chip-group>
</div>
</v-col>
<!-- Nomor Telepon Selular -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.nomorTeleponSelular"
label="Nomor Telepon Selular"
placeholder="Nomor Telepon Selular"
variant="outlined"
density="compact"
:rules="[rules.required, rules.phone]"
required
>
<template v-slot:prepend>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn
variant="outlined"
density="compact"
v-bind="props"
class="mr-2"
>
+62
<v-icon end>mdi-menu-down</v-icon>
</v-btn>
</template>
<v-list density="compact">
<v-list-item value="+62">+62</v-list-item>
</v-list>
</v-menu>
</template>
</v-text-field>
</v-col>
<!-- Nomor Telepon Rumah -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.nomorTeleponRumah"
label="Nomor Telepon Rumah"
placeholder="Nomor Telepon Rumah"
variant="outlined"
density="compact"
>
<template v-slot:prepend>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn
variant="outlined"
density="compact"
v-bind="props"
class="mr-2"
>
+62
<v-icon end>mdi-menu-down</v-icon>
</v-btn>
</template>
<v-list density="compact">
<v-list-item value="+62">+62</v-list-item>
</v-list>
</v-menu>
</template>
</v-text-field>
</v-col>
<!-- Email -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.email"
label="Email"
placeholder="Email"
type="email"
variant="outlined"
density="compact"
:rules="[rules.email]"
/>
</v-col>
<!-- Jenis Kelamin -->
<v-col cols="12" md="6">
<v-select
v-model="localData.jenisKelamin"
label="Jenis Kelamin"
:items="jenisKelaminOptions"
variant="outlined"
density="compact"
:rules="[rules.required]"
required
/>
</v-col>
<!-- Tempat Lahir -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.tempatLahir"
label="Tempat Lahir"
placeholder="Tempat Lahir"
variant="outlined"
density="compact"
:rules="[rules.required]"
required
/>
</v-col>
<!-- Tanggal Lahir -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.tanggalLahir"
label="Tanggal Lahir"
placeholder="Tanggal Lahir (DD/MM/YYYY)"
variant="outlined"
density="compact"
:rules="[rules.required]"
required
>
<template v-slot:append-inner>
<v-icon>mdi-calendar</v-icon>
</template>
</v-text-field>
</v-col>
<!-- Jenis Identitas -->
<v-col cols="12" md="6">
<v-select
v-model="localData.jenisIdentitas"
label="Jenis Identitas"
:items="jenisIdentitasOptions"
variant="outlined"
density="compact"
:rules="[rules.required]"
required
/>
</v-col>
<!-- Nomor Identitas -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.nomorIdentitas"
label="Nomor Identitas"
placeholder="Nomor Identitas"
variant="outlined"
density="compact"
:rules="[rules.required]"
required
/>
</v-col>
<!-- Nama Ibu Kandung -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.namaIbuKandung"
label="Nama Ibu Kandung"
placeholder="Nama Lengkap Ibu Kandung"
variant="outlined"
density="compact"
/>
</v-col>
<!-- No. Rekam Medis -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.nomorRekamMedis"
label="No. Rekam Medis Lama / Manual"
placeholder="No. Rekam medis"
variant="outlined"
density="compact"
/>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from "vue";
import { debounce } from "lodash-es"; // or use VueUse's useDebounceFn
import {
parseFhirHumanName,
validateFhirHumanName
} from "~/utils/module/fhirNameParser";
import type { FhirHumanName } from "~/types/fhir/humanName";
// Types
interface DataDiriForm {
photo: File | null;
namaLengkap: string;
nomorTeleponSelular: string;
nomorTeleponRumah: string;
email: string;
jenisKelamin: string;
tempatLahir: string;
tanggalLahir: string;
jenisIdentitas: string;
nomorIdentitas: string;
namaIbuKandung: string;
nomorRekamMedis: string;
fhirName?: FhirHumanName | null;
}
interface Props {
formData: DataDiriForm;
isBayiBaru: boolean;
}
// Props & Emits
const props = defineProps<Props>();
const emit = defineEmits<{
"update:formData": [value: DataDiriForm];
"update:isBayiBaru": [value: boolean];
}>();
// Reactive data
const localData = ref<DataDiriForm>({ ...props.formData });
const isBayiBaru = ref(props.isBayiBaru);
const fhirName = ref<FhirHumanName | null>(null);
const isTypingComplete = ref(false);
const showDetailedPreview = ref(true); // Toggle between detailed and chip view
// Rules
const rules = {
required: (v: string) => !!v || "Field ini wajib diisi",
email: (v: string) => !v || /.+@.+\..+/.test(v) || "Format email tidak valid",
phone: (v: string) =>
!v || /^[0-9+\-() ]+$/.test(v) || "Format nomor telepon tidak valid"
};
// Options
interface SelectOption {
title: string;
value: string;
}
const jenisKelaminOptions: SelectOption[] = [
{ title: "Laki-laki", value: "L" },
{ title: "Perempuan", value: "P" }
];
const jenisIdentitasOptions: SelectOption[] = [
{ title: "KTP", value: "KTP" },
{ title: "SIM", value: "SIM" },
{ title: "Paspor", value: "PASSPORT" }
];
// Methods
import { formatFhirName } from "~/utils/module/fhirNameParser";
const parseNameToFhir = (): void => {
if (localData.value.namaLengkap && localData.value.namaLengkap.trim()) {
fhirName.value = parseFhirHumanName(localData.value.namaLengkap);
if (fhirName.value) {
const errors = validateFhirHumanName(fhirName.value);
if (errors.length > 0) {
console.warn("FHIR Name validation errors:", errors);
}
// Store FHIR name in form data
localData.value.fhirName = fhirName.value;
// Format the FHIR name back to a string and update namaLengkap
const formattedName = formatFhirName(fhirName.value);
if (formattedName !== localData.value.namaLengkap) {
localData.value.namaLengkap = formattedName;
}
// Show preview after parsing
isTypingComplete.value = true;
}
} else {
// Clear FHIR name if input is empty
fhirName.value = null;
localData.value.fhirName = null;
isTypingComplete.value = false;
}
};
// Debounced version of parseNameToFhir (wait 500ms after user stops typing)
const debouncedParseNameToFhir = debounce(() => {
parseNameToFhir();
}, 500);
// Handle paste event
const handlePaste = (event: ClipboardEvent): void => {
// Reset typing complete flag
isTypingComplete.value = false;
// Parse after a short delay to ensure v-model is updated
setTimeout(() => {
parseNameToFhir();
}, 100);
};
// Hide FHIR preview
const hideFhirPreview = (): void => {
isTypingComplete.value = false;
};
const uploadPhoto = (): void => {
// Implementation for photo upload
console.log("Upload photo clicked");
};
// Watchers
watch(
localData,
(newVal) => {
emit("update:formData", newVal);
},
{ deep: true }
);
watch(isBayiBaru, (newVal) => {
emit("update:isBayiBaru", newVal);
});
// Reset typing complete when name is cleared
watch(
() => localData.value.namaLengkap,
(newVal) => {
if (!newVal) {
isTypingComplete.value = false;
}
}
);
</script>
<style scoped>
/* Optional: Add smooth transitions */
.v-chip-group {
gap: 4px;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div>
<v-card-subtitle
class="bg-grey-lighten-4 text-subtitle-2 font-weight-medium py-2"
>
KESEHATAN
</v-card-subtitle>
<v-card-text>
<v-row>
<!-- Golongan Darah -->
<v-col cols="12" md="6">
<v-select
v-model="localData.golonganDarah"
label="Golongan Darah"
:items="golonganDarahOptions"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Status Perokok -->
<v-col cols="12" md="6">
<v-select
v-model="localData.statusPerokok"
label="Status Perokok"
:items="statusPerokokOptions"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Jenis Penyakit -->
<v-col cols="12">
<v-select
v-model="localData.jenisPenyakit"
label="Jenis Penyakit"
:items="jenisPenyakitOptions"
variant="outlined"
density="compact"
multiple
chips
closable-chips
placeholder="Pilih Jenis Penyakit"
/>
</v-col>
</v-row>
</v-card-text>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
const props = defineProps({
formData: {
type: Object,
required: true
}
});
const emit = defineEmits(["update:formData"]);
const localData = ref({ ...props.formData });
const golonganDarahOptions = [
{ title: "A", value: "A" },
{ title: "B", value: "B" },
{ title: "AB", value: "AB" },
{ title: "O", value: "O" }
];
const statusPerokokOptions = [
{ title: "Tidak Merokok", value: "tidak" },
{ title: "Perokok Aktif", value: "aktif" },
{ title: "Perokok Pasif", value: "pasif" },
{ title: "Mantan Perokok", value: "mantan" }
];
const jenisPenyakitOptions = [
"Diabetes",
"Hipertensi",
"Jantung",
"Asma",
"TBC",
"Hepatitis",
"HIV/AIDS",
"Kanker",
"Ginjal",
"Stroke"
];
watch(
localData,
(newVal) => {
emit("update:formData", newVal);
},
{ deep: true }
);
</script>

View File

@@ -0,0 +1,53 @@
<template>
<div>
<v-card-subtitle class="bg-grey-lighten-4 text-subtitle-2 font-weight-medium py-2">
PEMBAYARAN
</v-card-subtitle>
<v-card-text>
<v-row>
<v-col cols="12">
<v-select
v-model="localData.jenisPembayaran"
label="Pembayaran"
:items="pembayaranOptions"
variant="outlined"
density="compact"
:rules="[rules.required]"
required
/>
</v-col>
</v-row>
</v-card-text>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
formData: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:formData'])
const localData = ref({ ...props.formData })
const rules = {
required: v => !!v || 'Field ini wajib diisi'
}
const pembayaranOptions = [
{ title: 'BPJS', value: 'BPJS' },
{ title: 'Umum', value: 'UMUM' },
{ title: 'Asuransi', value: 'ASURANSI' },
{ title: 'Perusahaan', value: 'PERUSAHAAN' }
]
watch(localData, (newVal) => {
emit('update:formData', newVal)
}, { deep: true })
</script>

View File

@@ -0,0 +1,167 @@
<template>
<div>
<v-card-subtitle
class="bg-grey-lighten-4 text-subtitle-2 font-weight-medium py-2"
>
PENANGGUNG JAWAB / WALI PASIEN
</v-card-subtitle>
<v-card-text>
<v-row>
<!-- Nama Lengkap -->
<v-col cols="12">
<v-text-field
v-model="localData.namaLengkap"
label="Nama Lengkap"
placeholder="Nama Lengkap"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Jenis Kelamin -->
<v-col cols="12" md="6">
<v-select
v-model="localData.jenisKelamin"
label="Jenis Kelamin"
:items="jenisKelaminOptions"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Hubungan dengan Pasien -->
<v-col cols="12" md="6">
<v-select
v-model="localData.hubunganDenganPasien"
label="Hubungan dengan Pasien"
:items="hubunganOptions"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Nomor Telepon -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.nomorTelepon"
label="Nomor Telepon Selular / Rumah"
placeholder="Nomor Telepon"
variant="outlined"
density="compact"
>
<template v-slot:prepend>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn
variant="outlined"
density="compact"
v-bind="props"
class="mr-2"
>
+62
<v-icon end>mdi-menu-down</v-icon>
</v-btn>
</template>
<v-list density="compact">
<v-list-item value="+62">+62</v-list-item>
</v-list>
</v-menu>
</template>
</v-text-field>
</v-col>
<!-- Pekerjaan -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.pekerjaan"
label="Pekerjaan"
placeholder="Pekerjaan"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Alamat -->
<v-col cols="12">
<label class="text-subtitle-2 font-weight-medium">Alamat</label>
<v-checkbox
v-model="samaDenganPasien"
label="Samakan dengan alamat pasien"
density="compact"
@change="handleSamakanAlamat"
/>
</v-col>
<v-col cols="12" v-if="!samaDenganPasien">
<v-textarea
v-model="localData.alamat"
placeholder="Alamat"
variant="outlined"
density="compact"
rows="3"
/>
</v-col>
</v-row>
</v-card-text>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
const props = defineProps({
formData: {
type: Object,
required: true
},
samaDenganPasien: {
type: Boolean,
default: false
},
alamatPasien: {
type: Object,
default: () => ({})
}
});
const emit = defineEmits(["update:formData", "update:samaDenganPasien"]);
const localData = ref({ ...props.formData });
const samaDenganPasien = ref(props.samaDenganPasien);
const jenisKelaminOptions = [
{ title: "Laki-laki", value: "L" },
{ title: "Perempuan", value: "P" }
];
const hubunganOptions = [
"Suami",
"Istri",
"Anak",
"Orang Tua",
"Saudara",
"Kerabat",
"Lainnya"
];
const handleSamakanAlamat = () => {
if (samaDenganPasien.value && props.alamatPasien) {
localData.value.alamat = props.alamatPasien.alamatLengkap || "";
} else {
localData.value.alamat = "";
}
};
watch(
localData,
(newVal) => {
emit("update:formData", newVal);
},
{ deep: true }
);
watch(samaDenganPasien, (newVal) => {
emit("update:samaDenganPasien", newVal);
});
</script>

View File

@@ -0,0 +1,145 @@
<template>
<div>
<v-card-subtitle
class="bg-grey-lighten-4 text-subtitle-2 font-weight-medium py-2"
>
SOSIAL
</v-card-subtitle>
<v-card-text>
<v-row>
<!-- Agama -->
<v-col cols="12" md="6">
<v-select
v-model="localData.agama"
label="Agama"
:items="agamaOptions"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Status Pernikahan -->
<v-col cols="12" md="6">
<v-select
v-model="localData.statusPernikahan"
label="Status Pernikahan"
:items="statusPernikahanOptions"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Pendidikan Terakhir -->
<v-col cols="12" md="6">
<v-select
v-model="localData.pendidikanTerakhir"
label="Pendidikan Terakhir"
:items="pendidikanOptions"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Pekerjaan -->
<v-col cols="12" md="6">
<v-select
v-model="localData.pekerjaan"
label="Pekerjaan"
:items="pekerjaanOptions"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Bahasa yang Dikuasai -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.bahasaDikuasai"
label="Bahasa yang Dikuasai"
placeholder="Bahasa yang Dikuasai"
variant="outlined"
density="compact"
/>
</v-col>
<!-- Suku/Etnis -->
<v-col cols="12" md="6">
<v-text-field
v-model="localData.sukuEtnis"
label="Suku/Etnis"
placeholder="Suku/Etnis"
variant="outlined"
density="compact"
/>
</v-col>
</v-row>
</v-card-text>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
const props = defineProps({
formData: {
type: Object,
required: true
}
});
const emit = defineEmits(["update:formData"]);
const localData = ref({ ...props.formData });
const agamaOptions = [
"Islam",
"Kristen",
"Katolik",
"Hindu",
"Buddha",
"Konghucu",
"Lainnya"
];
const statusPernikahanOptions = [
{ title: "Belum Menikah", value: "belum_menikah" },
{ title: "Menikah", value: "menikah" },
{ title: "Cerai Hidup", value: "cerai_hidup" },
{ title: "Cerai Mati", value: "cerai_mati" }
];
const pendidikanOptions = [
"Tidak Sekolah",
"SD",
"SMP",
"SMA/SMK",
"D1",
"D3",
"D4/S1",
"S2",
"S3"
];
const pekerjaanOptions = [
"PNS",
"TNI/Polri",
"Pegawai Swasta",
"Wiraswasta",
"Petani",
"Nelayan",
"Buruh",
"Ibu Rumah Tangga",
"Pelajar/Mahasiswa",
"Tidak Bekerja",
"Lainnya"
];
watch(
localData,
(newVal) => {
emit("update:formData", newVal);
},
{ deep: true }
);
</script>

View File

@@ -0,0 +1,227 @@
<template>
<v-card>
<v-card-title class="d-flex justify-space-between align-center">
<span class="text-h6">NAKES & JADWAL</span>
<v-btn variant="text" color="primary" size="small" @click="resetSection">
Atur Ulang
</v-btn>
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-select
v-model="localData.paymentMethod"
:items="paymentMethods"
label="Pembiayaan"
variant="outlined"
density="compact"
:rules="[rules.required]"
append-inner-icon="mdi-chevron-down"
>
<template v-slot:label>
Pembiayaan <span class="text-red">*</span>
</template>
</v-select>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="localData.referralNumber"
label="Nomor Rujukan (Opsional)"
variant="outlined"
density="compact"
placeholder="Nomor Rujukan"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-select
v-model="localData.polyclinicName"
:items="polyclinics"
label="Nama Poliklinik"
variant="outlined"
density="compact"
placeholder="Pilih"
:rules="[rules.required]"
@update:modelValue="onPolyclinicChange"
append-inner-icon="mdi-chevron-down"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-select
v-model="localData.doctorName"
:items="doctors"
label="Nama Nakes"
variant="outlined"
density="compact"
placeholder="Pilih"
:rules="[rules.required]"
:disabled="!localData.polyclinicName"
@update:modelValue="onDoctorChange"
append-inner-icon="mdi-chevron-down"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="localData.consultationDate"
label="Tanggal Konsultasi"
variant="outlined"
density="compact"
type="date"
:rules="[rules.required]"
:min="minDate"
>
<template v-slot:label>
Tanggal Konsultasi <span class="text-red">*</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="localData.consultationTime"
:items="timeSlots"
label="Jam Konsultasi"
variant="outlined"
density="compact"
placeholder="Pilih Jam Konsultasi"
:rules="[rules.required]"
:disabled="!localData.consultationDate || !localData.doctorName"
append-inner-icon="mdi-clock-outline"
>
<template v-slot:label>
Jam Konsultasi <span class="text-red">*</span>
</template>
</v-select>
</v-col>
</v-row>
<v-row v-if="localData.doctorName && localData.consultationDate">
<v-col cols="12">
<v-alert type="info" variant="tonal" density="compact">
Slot Nakes: {{ availableSlots }} slot tersedia
</v-alert>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script setup>
import { ref, watch, computed } from "vue";
const props = defineProps({
scheduleData: {
type: Object,
required: true
}
});
const emit = defineEmits(["update:scheduleData", "update"]);
const localData = ref({ ...props.scheduleData });
const paymentMethods = ["Pribadi", "BPJS", "Asuransi Swasta", "Perusahaan"];
const polyclinics = [
"Poli Umum",
"Poli Gigi",
"Poli Anak",
"Poli Kandungan",
"Poli Penyakit Dalam",
"Poli THT"
];
const doctors = ref([]);
const timeSlots = ref([]);
const availableSlots = ref(0);
const minDate = computed(() => {
const today = new Date();
return today.toISOString().split("T")[0];
});
const rules = {
required: (value) => !!value || "Field ini wajib diisi"
};
const onPolyclinicChange = (value) => {
// Reset doctor and time when polyclinic changes
localData.value.doctorName = "";
localData.value.consultationTime = "";
// Simulate loading doctors based on polyclinic
if (value) {
doctors.value = [
"dr. Ahmad Subhan, Sp.A",
"dr. Siti Nurhaliza",
"dr. Budi Santoso, Sp.PD",
"dr. Maria Christina"
];
}
};
const onDoctorChange = (value) => {
// Reset time when doctor changes
localData.value.consultationTime = "";
// Simulate loading time slots
if (value && localData.value.consultationDate) {
loadTimeSlots();
}
};
const loadTimeSlots = () => {
// Simulate available time slots
timeSlots.value = [
{ title: "08:00 - 08:30", value: "08:00" },
{ title: "08:30 - 09:00", value: "08:30" },
{ title: "09:00 - 09:30", value: "09:00" },
{ title: "09:30 - 10:00", value: "09:30" },
{ title: "10:00 - 10:30", value: "10:00" },
{ title: "10:30 - 11:00", value: "10:30" }
];
availableSlots.value = 6;
};
const resetSection = () => {
localData.value = {
paymentMethod: "Pribadi",
referralNumber: "",
polyclinicName: "",
doctorName: "",
consultationDate: "",
consultationTime: "",
slotAvailable: true
};
doctors.value = [];
timeSlots.value = [];
};
watch(
localData,
(newVal) => {
emit("update:scheduleData", newVal);
emit("update");
},
{ deep: true }
);
watch(
() => localData.value.consultationDate,
(newVal) => {
if (newVal && localData.value.doctorName) {
loadTimeSlots();
}
}
);
</script>

View File

@@ -0,0 +1,249 @@
<template>
<v-card>
<v-card-title class="d-flex justify-space-between align-center">
<span class="text-h6">DATA PASIEN</span>
<v-btn variant="text" color="primary" size="small" @click="resetSection">
Atur Ulang
</v-btn>
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12">
<v-text-field
v-model="localData.name"
label="Nama Pasien"
variant="outlined"
density="compact"
placeholder="Masukkan Nama Pasien"
:rules="[rules.required]"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="localData.medicalRecordNumber"
label="No. Rekam medis"
variant="outlined"
density="compact"
placeholder="MRN Pasien"
:rules="[rules.required]"
/>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="localData.category"
:items="patientCategories"
label="Kategori Pasien"
variant="outlined"
density="compact"
placeholder="Pilih Kategori Pasien"
append-inner-icon="mdi-chevron-down"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="localData.birthDate"
label="Tanggal Lahir"
variant="outlined"
density="compact"
type="date"
placeholder="Tanggal Lahir (DD/MM/YYYY)"
:rules="[rules.required]"
>
<template v-slot:label>
Tanggal Lahir <span class="text-red">*</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="localData.gender"
:items="genderOptions"
label="Jenis Kelamin"
variant="outlined"
density="compact"
placeholder="Pilih"
:rules="[rules.required]"
append-inner-icon="mdi-chevron-down"
>
<template v-slot:label>
Jenis Kelamin <span class="text-red">*</span>
</template>
</v-select>
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-textarea
v-model="localData.address"
label="Alamat"
variant="outlined"
density="compact"
rows="3"
placeholder="Alamat"
:rules="[rules.required]"
counter="100"
>
<template v-slot:label>
Alamat <span class="text-red">*</span>
</template>
</v-textarea>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="2">
<v-select
v-model="localData.phoneCode"
:items="phoneCodes"
label="Kode"
variant="outlined"
density="compact"
:rules="[rules.required]"
>
<template v-slot:label>
Kode <span class="text-red">*</span>
</template>
</v-select>
</v-col>
<v-col cols="12" md="5">
<v-text-field
v-model="localData.phoneNumber"
label="Nomor Telepon"
variant="outlined"
density="compact"
placeholder="Nomor Telepon"
:rules="[rules.required, rules.phone]"
>
<template v-slot:label>
Nomor Telepon <span class="text-red">*</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="5">
<v-text-field
v-model="localData.email"
label="Email"
variant="outlined"
density="compact"
placeholder="Email"
:rules="[rules.email]"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-select
v-model="localData.identityType"
:items="identityTypes"
label="Jenis Identitas"
variant="outlined"
density="compact"
placeholder="Pilih"
:rules="[rules.required]"
append-inner-icon="mdi-chevron-down"
>
<template v-slot:label>
Jenis Identitas <span class="text-red">*</span>
</template>
</v-select>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="localData.identityNumber"
label="Nomor Identitas"
variant="outlined"
density="compact"
placeholder="Nomor Identitas"
:rules="[rules.required]"
>
<template v-slot:label>
Nomor Identitas <span class="text-red">*</span>
</template>
</v-text-field>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script setup>
import { ref, watch } from "vue";
const props = defineProps({
patientData: {
type: Object,
required: true
}
});
const emit = defineEmits(["update:patientData", "update"]);
const localData = ref({ ...props.patientData });
const patientCategories = ["Umum", "BPJS", "Asuransi Lain"];
const genderOptions = [
{ title: "Laki-laki", value: "L" },
{ title: "Perempuan", value: "P" }
];
const phoneCodes = ["+62", "+65", "+60", "+1"];
const identityTypes = [
{ title: "KTP", value: "ktp" },
{ title: "SIM", value: "sim" },
{ title: "Paspor", value: "paspor" },
{ title: "Kartu Pelajar", value: "kartu_pelajar" }
];
const rules = {
required: (value) => !!value || "Field ini wajib diisi",
email: (value) => {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return !value || pattern.test(value) || "Email tidak valid";
},
phone: (value) => {
const pattern = /^[0-9]{8,15}$/;
return pattern.test(value) || "Nomor telepon tidak valid";
}
};
const resetSection = () => {
localData.value = {
name: "",
medicalRecordNumber: "",
category: "",
birthDate: "",
gender: "",
address: "",
phoneCode: "+62",
phoneNumber: "",
email: "",
identityType: "",
identityNumber: ""
};
};
watch(
localData,
(newVal) => {
emit("update:patientData", newVal);
emit("update");
},
{ deep: true }
);
</script>

View File

@@ -0,0 +1,106 @@
<template>
<v-card>
<v-card-title class="d-flex justify-space-between align-center">
<span class="text-h6">JENIS PENDAFTARAN</span>
<v-btn variant="text" color="primary" size="small" @click="resetSection">
Atur Ulang
</v-btn>
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<div class="mb-2">
<span class="text-subtitle-2">
Jenis Kunjungan <span class="text-red">*</span>
</span>
</div>
<v-radio-group
v-model="localData.visitType"
:rules="[rules.required]"
inline
>
<v-radio label="Kunjungan Sakit" value="sakit" />
<v-radio label="Kunjungan Sehat" value="sehat" />
</v-radio-group>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-select
v-model="localData.treatmentType"
:items="treatmentTypes"
label="Jenis Perawatan"
variant="outlined"
density="compact"
:rules="[rules.required]"
append-inner-icon="mdi-chevron-down"
>
<template v-slot:label>
Jenis Perawatan <span class="text-red">*</span>
</template>
</v-select>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="localData.triage"
:items="triageOptions"
label="Triase"
variant="outlined"
density="compact"
placeholder="Pilih"
clearable
append-inner-icon="mdi-chevron-down"
/>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script setup>
import { ref, watch } from "vue";
const props = defineProps({
registrationData: {
type: Object,
required: true
}
});
const emit = defineEmits(["update:registrationData", "update"]);
const localData = ref({ ...props.registrationData });
const treatmentTypes = ["Rawat Jalan", "Rawat Inap", "IGD"];
const triageOptions = [
{ title: "Merah (Emergency)", value: "merah" },
{ title: "Kuning (Urgent)", value: "kuning" },
{ title: "Hijau (Less Urgent)", value: "hijau" },
{ title: "Hitam (Non Urgent)", value: "hitam" }
];
const rules = {
required: (value) => !!value || "Field ini wajib diisi"
};
const resetSection = () => {
localData.value = {
visitType: "",
treatmentType: "Rawat Jalan",
triage: ""
};
};
watch(
localData,
(newVal) => {
emit("update:registrationData", newVal);
emit("update");
},
{ deep: true }
);
</script>

View File

@@ -0,0 +1,159 @@
<template>
<v-card>
<v-card-title class="d-flex justify-space-between align-center">
<div class="d-flex align-center gap-2">
<span class="text-h6">SKALA RISIKO JATUH (OPSIONAL)</span>
<v-icon color="warning" size="small">mdi-alert-box</v-icon>
</div>
<v-btn variant="text" color="primary" size="small" @click="resetSection">
Atur Ulang
</v-btn>
</v-card-title>
<v-expansion-panels v-model="panel" class="mt-2">
<v-expansion-panel>
<v-expansion-panel-title>
<span class="font-weight-medium"
>SKALA RISIKO JATUH - GET UP AND GO</span
>
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-card flat>
<v-card-text>
<!-- Walking Ability Question -->
<div class="mb-6">
<div class="mb-3">
<p class="text-subtitle-1 font-weight-medium mb-1">
Cara berjalan pasien:
</p>
<ul class="ml-4">
<li>Jalan tidak seimbang, sempoyongan, atau limbung</li>
<li>
Menggunakan alat bantu (kruk, tripot, kursi roda, orang
lain)
</li>
</ul>
</div>
<v-radio-group
v-model="localData.walkingAbility"
inline
@update:modelValue="calculateScore"
>
<v-radio label="Ya" :value="true" />
<v-radio label="Tidak" :value="false" />
</v-radio-group>
</div>
<!-- Sitting Support Question -->
<div class="mb-6">
<div class="mb-3">
<p class="text-subtitle-1 font-weight-medium mb-1">
Menopang saat akan duduk
</p>
<p class="text-body-2 text-grey-darken-1">
Tampak memegang sandaran kursi atau meja / benda lain
sebagai penopang saat akan duduk
</p>
</div>
<v-radio-group
v-model="localData.supportWhileSitting"
inline
@update:modelValue="calculateScore"
>
<v-radio label="Ya" :value="true" />
<v-radio label="Tidak" :value="false" />
</v-radio-group>
</div>
<!-- Score Display -->
<v-divider class="my-4" />
<div class="d-flex justify-space-between align-center">
<span class="text-subtitle-1 font-weight-medium">Skor</span>
<v-chip :color="scoreColor" variant="tonal" size="large">
{{ localData.score }}
</v-chip>
</div>
<!-- Risk Level Alert -->
<v-alert
v-if="riskLevel"
:type="riskLevel.type"
variant="tonal"
density="compact"
class="mt-4"
>
{{ riskLevel.message }}
</v-alert>
</v-card-text>
</v-card>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card>
</template>
<script setup>
import { ref, watch, computed } from "vue";
const props = defineProps({
riskData: {
type: Object,
required: true
}
});
const emit = defineEmits(["update:riskData", "update"]);
const localData = ref({ ...props.riskData });
const panel = ref(0);
const calculateScore = () => {
let score = 0;
if (localData.value.walkingAbility === true) score += 1;
if (localData.value.supportWhileSitting === true) score += 1;
localData.value.score = score;
};
const scoreColor = computed(() => {
if (localData.value.score === 0) return "success";
if (localData.value.score === 1) return "warning";
return "error";
});
const riskLevel = computed(() => {
if (localData.value.score === 0) {
return {
type: "success",
message: "Risiko Rendah: Tidak berisiko jatuh"
};
} else if (localData.value.score === 1) {
return {
type: "warning",
message: "Risiko Sedang: Memerlukan pengawasan"
};
} else if (localData.value.score === 2) {
return {
type: "error",
message: "Risiko Tinggi: Memerlukan bantuan dan pengawasan ketat"
};
}
return null;
});
const resetSection = () => {
localData.value = {
walkingAbility: null,
supportWhileSitting: null,
score: 0
};
};
watch(
localData,
(newVal) => {
emit("update:riskData", newVal);
emit("update");
},
{ deep: true }
);
</script>

View File

@@ -0,0 +1,73 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useAuthStore } from '@/stores/auth';
import { Form } from 'vee-validate';
/*Social icons*/
import google from '@/assets/images/svgs/google-icon.svg';
import facebook from '@/assets/images/svgs/facebook-icon.svg';
const checkbox = ref(false);
const valid = ref(false);
const show1 = ref(false);
const password = ref('admin123');
const username = ref('info@wrappixel.com');
const passwordRules = ref([
(v: string) => !!v || 'Password is required',
(v: string) => (v && v.length <= 10) || 'Password must be less than 10 characters'
]);
const emailRules = ref([(v: string) => !!v || 'E-mail is required', (v: string) => /.+@.+\..+/.test(v) || 'E-mail must be valid']);
function validate(values: any, { setErrors }: any) {
const authStore = useAuthStore();
return authStore.login(username.value, password.value).catch((error) => setErrors({ apiError: error }));
}
</script>
<template>
<v-row class="d-flex mb-3">
<v-col cols="6" sm="6" class="pr-2">
<v-btn variant="outlined" size="large" class="border text-subtitle-1 font-weight-semibold hover-link-primary" block>
<img :src="facebook" width="20" class="mr-1" alt="facebook" />
<span class="d-md-flex d-none mr-1">Sign in with</span> Facebook
</v-btn>
</v-col>
<v-col cols="6" sm="6" class="pl-2">
<v-btn variant="outlined" size="large" class="border text-subtitle-1 font-weight-semibold hover-link-primary" block>
<img :src="google" height="16" class="mr-2" alt="google" />
<span class="d-md-flex d-none mr-1">Sign in with</span> Google
</v-btn>
</v-col>
</v-row>
<div class="d-flex align-center text-center mb-6">
<div class="text-h6 w-100 px-5 font-weight-regular auth-divider position-relative">
<span class="bg-surface px-5 py-3 position-relative textSecondary">Or sign in with email</span>
</div>
</div>
<Form @submit="validate" v-slot="{ errors, isSubmitting }" class="mt-5">
<v-label class="font-weight-semibold pb-2 ">Username</v-label>
<VTextField v-model="username" :rules="emailRules" class="mb-8" required hide-details="auto"></VTextField>
<div class="d-flex justify-space-between align-center pb-2">
<v-label class="font-weight-semibold ">Password</v-label>
<RouterLink to="/auth/forgot-password2" class="text-primary text-decoration-none font-weight-medium">Forgot Password ?</RouterLink>
</div>
<VTextField v-model="password" :rules="passwordRules" required hide-details="auto" type="password" class="pwdInput"></VTextField>
<div class="d-flex flex-wrap align-center my-3 ml-n2">
<v-checkbox
class="pe-2"
v-model="checkbox"
:rules="[(v: any) => !!v || 'You must agree to continue!']"
required
hide-details
color="primary"
>
<template v-slot:label class="font-weight-medium">Keep me logged in</template>
</v-checkbox>
</div>
<v-btn size="large" :loading="isSubmitting" color="darkgray" :disabled="valid" block type="submit" flat>Sign In</v-btn>
<div v-if="errors.apiError" class="mt-2">
<v-alert color="error">{{ errors.apiError }}</v-alert>
</div>
</Form>
</template>

View File

@@ -0,0 +1,61 @@
<script setup lang="ts">
import { ref } from 'vue';
import Logo from '@/layouts/full/logo/Logo.vue';
/*Social icons*/
import google from '@/assets/images/svgs/google-icon.svg';
import facebook from '@/assets/images/svgs/facebook-icon.svg';
const checkbox = ref(false);
const valid = ref(true);
const show1 = ref(false);
const password = ref('');
const email = ref('');
const passwordRules = ref([
(v: string) => !!v || 'Password is required',
(v: string) => (v && v.length <= 10) || 'Password must be less than 10 characters'
]);
const emailRules = ref([(v: string) => !!v || 'E-mail is required', (v: string) => /.+@.+\..+/.test(v) || 'E-mail must be valid']);
const fname = ref('');
const fnameRules = ref([
(v: string) => !!v || 'Name is required',
(v: string) => (v && v.length <= 10) || 'Name must be less than 10 characters'
]);
</script>
<template>
<v-row class="d-flex mb-6">
<v-col cols="6" sm="6" class="pr-2">
<v-btn variant="outlined" size="large" class="border text-subtitle-1 font-weight-semibold hover-link-primary" block>
<img :src="facebook" width="20" class="mr-1" alt="facebook" />
<span class="d-md-flex d-none mr-1">Sign in with</span> Facebook
</v-btn>
</v-col>
<v-col cols="6" sm="6" class="pl-2">
<v-btn variant="outlined" size="large" class="border text-subtitle-1 font-weight-semibold hover-link-primary" block>
<img :src="google" height="16" class="mr-2" alt="google" />
<span class="d-md-flex d-none mr-1">Sign in with</span> Google
</v-btn>
</v-col>
</v-row>
<div class="d-flex align-center text-center mb-6">
<div class="text-h6 w-100 px-5 font-weight-regular auth-divider position-relative">
<span class="bg-surface px-5 py-3 position-relative">Or sign up with email</span>
</div>
</div>
<v-form ref="form" v-model="valid" lazy-validation action="/pages/boxedlogin" class="mt-5">
<v-label class="font-weight-medium pb-2">Name</v-label>
<VTextField v-model="fname" :rules="fnameRules" required ></VTextField>
<v-label class="font-weight-medium pb-2">Email Adddress</v-label>
<VTextField v-model="email" :rules="emailRules" required ></VTextField>
<v-label class="font-weight-medium pb-2">Password</v-label>
<VTextField
v-model="password"
:counter="10"
:rules="passwordRules"
required
variant="outlined"
type="password"
color="primary"
></VTextField>
<v-btn size="large" class="mt-2" color="darkgray" block submit flat>Sign Up</v-btn>
</v-form>
</template>

75
components/auth/LoginForm.vue Executable file
View File

@@ -0,0 +1,75 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Form } from 'vee-validate';
/*Social icons*/
import google from '@/assets/images/svgs/google-icon.svg';
import facebook from '@/assets/images/svgs/facebook-icon.svg';
const checkbox = ref(false);
const valid = ref(false);
const show1 = ref(false);
const password = ref('admin123');
const username = ref('info@wrappixel.com');
const passwordRules = ref([
(v: string) => !!v || 'Password is required',
(v: string) => (v && v.length <= 10) || 'Password must be less than 10 characters'
]);
const emailRules = ref([(v: string) => !!v || 'E-mail is required', (v: string) => /.+@.+\..+/.test(v) || 'E-mail must be valid']);
</script>
<template>
<v-row class="d-flex mb-3">
<v-col cols="6" sm="6" class="pr-2">
<v-btn variant="outlined" size="large" class="border text-subtitle-1 hover-link-primary" block>
<img :src="google" height="16" class="mr-2" alt="google" />
Google
</v-btn>
</v-col>
<v-col cols="6" sm="6" class="pl-2">
<v-btn variant="outlined" size="large" class="border text-subtitle-1 hover-link-primary" block>
<img :src="facebook" width="20" class="mr-1" alt="facebook" />
Facebook
</v-btn>
</v-col>
</v-row>
<div class="d-flex align-center text-center mb-6">
<div class="text-h6 w-100 px-5 font-weight-regular auth-divider position-relative">
<span class="bg-surface px-5 py-3 position-relative">or sign in with</span>
</div>
</div>
<Form class="mt-5">
<v-label class="font-weight-semibold pb-2 ">Username</v-label>
<VTextField
v-model="username"
:rules="emailRules"
class="mb-8"
required
hide-details="auto"
></VTextField>
<v-label class="font-weight-semibold pb-2 ">Password</v-label>
<VTextField
v-model="password"
:rules="passwordRules"
required
hide-details="auto"
type="password"
class="pwdInput"
></VTextField>
<div class="d-flex flex-wrap align-center my-3 ml-n2">
<v-checkbox class="pe-2" v-model="checkbox" :rules="[(v:any) => !!v || 'You must agree to continue!']" required hide-details color="primary">
<template v-slot:label class="font-weight-medium">Remeber this Device</template>
</v-checkbox>
<div class="ml-sm-auto">
<RouterLink to="" class="text-primary text-decoration-none font-weight-medium"
>Forgot Password ?</RouterLink
>
</div>
</div>
<v-btn size="large" color="primary" :disabled="valid" block type="submit" flat>Sign In</v-btn>
<div class="mt-2">
<v-alert color="error"></v-alert>
</div>
</Form>
</template>

View File

@@ -0,0 +1,61 @@
<script setup lang="ts">
import { ref } from 'vue';
import Logo from '~/components/layout/full/logo/Logo.vue';
/*Social icons*/
import google from '~/assets/images/svgs/google-icon.svg';
import facebook from '~/assets/images/svgs/facebook-icon.svg';
const checkbox = ref(false);
const valid = ref(true);
const show1 = ref(false);
const password = ref('');
const email = ref('');
const passwordRules = ref([
(v: string) => !!v || 'Password is required',
(v: string) => (v && v.length <= 10) || 'Password must be less than 10 characters'
]);
const emailRules = ref([(v: string) => !!v || 'E-mail is required', (v: string) => /.+@.+\..+/.test(v) || 'E-mail must be valid']);
const fname = ref('');
const fnameRules = ref([
(v: string) => !!v || 'Name is required',
(v: string) => (v && v.length <= 10) || 'Name must be less than 10 characters'
]);
</script>
<template>
<v-row class="d-flex mb-6">
<v-col cols="6" sm="6" class="pr-2">
<v-btn variant="outlined" size="large" class="border text-subtitle-1 hover-link-primary" block>
<img :src="google" height="16" class="mr-2" alt="google" />
Google
</v-btn>
</v-col>
<v-col cols="6" sm="6" class="pl-2">
<v-btn variant="outlined" size="large" class="border text-subtitle-1 hover-link-primary" block>
<img :src="facebook" width="20" class="mr-1" alt="facebook" />
Facebook
</v-btn>
</v-col>
</v-row>
<div class="d-flex align-center text-center mb-6">
<div class="text-h6 w-100 px-5 font-weight-regular auth-divider position-relative">
<span class="bg-surface px-5 py-3 position-relative">or sign in with</span>
</div>
</div>
<v-form ref="form" v-model="valid" lazy-validation action="/pages/boxedlogin" class="mt-5">
<v-label class=" font-weight-medium pb-2">Name</v-label>
<VTextField v-model="fname" :rules="fnameRules" required ></VTextField>
<v-label class=" font-weight-medium pb-2">Email Adddress</v-label>
<VTextField v-model="email" :rules="emailRules" required ></VTextField>
<v-label class=" font-weight-medium pb-2">Password</v-label>
<VTextField
v-model="password"
:counter="10"
:rules="passwordRules"
required
variant="outlined"
type="password"
color="primary"
></VTextField>
<v-btn size="large" class="mt-2" color="primary" block submit flat>Sign Up</v-btn>
</v-form>
</template>

15
components/auth/ResetForm.vue Executable file
View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
import { ref } from 'vue';
import Logo from '@/layouts/full/logo/Logo.vue';
const valid = ref(true);
const show1 = ref(false);
const email = ref('');
const emailRules = ref([(v: string) => !!v || 'E-mail is required', (v: string) => /.+@.+\..+/.test(v) || 'E-mail must be valid']);
</script>
<template>
<v-form ref="form" v-model="valid" lazy-validation action="/dashboards/analytical" class="mt-6">
<v-label class="font-weight-semibold pb-2">Email Address</v-label>
<VTextField v-model="email" :rules="emailRules" required ></VTextField>
<v-btn size="large" color="darkgray" to="/" block submit flat>Forgot Password</v-btn>
</v-form>
</template>

18
components/auth/TwoStepForm.vue Executable file
View File

@@ -0,0 +1,18 @@
<template>
<div class="mt-6">
<v-label class="text-subtitle-1 font-weight-semibold pb-2 text-lightText">Type your 6 digits security code</v-label>
<div class="d-flex justify-space-between gap-3 mb-2 verification">
<VTextField></VTextField>
<VTextField></VTextField>
<VTextField></VTextField>
<VTextField></VTextField>
<VTextField></VTextField>
<VTextField></VTextField>
</div>
<v-btn color="darkgray" size="large" block flat>Verify My Account</v-btn>
<h6 class="text-16 text-medium-emphasis d-flex align-center mt-6" >
Didn't get the code?
<RouterLink to="/" class="pl-0 text-primary text-16 opacity-1 pl-2 text-decoration-none"> Resend</RouterLink>
</h6>
</div>
</template>

View File

@@ -0,0 +1,186 @@
<template>
<div class="menu-tree-item">
<v-row
no-gutters
class="pa-2 border-b"
:class="{
'bg-grey-lighten-4': level > 0,
'bg-red-lighten-5': !item.isActive
}"
align="center"
>
<!-- Menu Title with Icon -->
<v-col cols="6" sm="4" class="d-flex align-center">
<div :style="{ marginLeft: `${level * 20}px` }" class="d-flex align-center">
<!-- Expand/Collapse Button -->
<v-btn
v-if="item.children && item.children.length > 0"
icon
size="x-small"
variant="text"
@click="toggleExpanded"
class="me-1"
>
<v-icon>{{ isExpanded ? 'mdi-chevron-down' : 'mdi-chevron-right' }}</v-icon>
</v-btn>
<!-- Menu Icon -->
<v-icon
:icon="item.icon || 'mdi-circle-outline'"
size="small"
class="me-2"
:color="item.isActive ? 'primary' : 'grey'"
/>
<!-- Menu Title -->
<span
class="text-body-2 font-weight-medium"
:class="{ 'text-grey': !item.isActive }"
>
{{ item.title }}
</span>
<!-- Active Status Badge -->
<v-chip
v-if="!item.isActive"
size="x-small"
color="error"
class="ml-2"
>
Inactive
</v-chip>
</div>
</v-col>
<!-- URL Link (Hidden on mobile) -->
<v-col cols="4" sm="5" class="d-none d-sm-block">
<span class="text-body-2 text-grey-darken-1">
{{ item.url || '-' }}
</span>
</v-col>
<!-- Action Buttons -->
<v-col cols="6" sm="3" class="text-right">
<v-btn-group variant="text" density="compact">
<!-- Toggle Active/Inactive -->
<v-btn
:icon="item.isActive ? 'mdi-eye' : 'mdi-eye-off'"
size="small"
:color="item.isActive ? 'success' : 'warning'"
@click="$emit('toggle', item.id)"
>
<v-icon />
<v-tooltip activator="parent" location="top">
{{ item.isActive ? 'Active' : 'Inactive' }}
</v-tooltip>
</v-btn>
<!-- Edit Button -->
<v-btn
icon="mdi-pencil"
size="small"
color="primary"
@click="$emit('edit', item)"
>
<v-icon />
<v-tooltip activator="parent" location="top">Edit</v-tooltip>
</v-btn>
<!-- Delete Button -->
<v-btn
icon="mdi-delete"
size="small"
color="error"
@click="confirmDelete"
>
<v-icon />
<v-tooltip activator="parent" location="top">Delete</v-tooltip>
</v-btn>
</v-btn-group>
</v-col>
</v-row>
<!-- Child Items -->
<template v-if="item.children && isExpanded">
<MenuTreeItem
v-for="child in item.children"
:key="child.id"
:item="child"
:level="level + 1"
@edit="$emit('edit', $event)"
@delete="$emit('delete', $event)"
@toggle="$emit('toggle', $event)"
/>
</template>
<!-- Delete Confirmation Dialog -->
<v-dialog v-model="showDeleteDialog" max-width="400">
<v-card>
<v-card-title class="text-h6">Confirm Delete</v-card-title>
<v-card-text>
Are you sure you want to delete "{{ item.title }}"?
<span v-if="item.children && item.children.length > 0" class="text-error">
This will also delete all child items.
</span>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="grey" variant="text" @click="showDeleteDialog = false">
Cancel
</v-btn>
<v-btn color="error" variant="elevated" @click="handleDelete">
Delete
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script setup lang="ts">
import type { MenuItem } from '../../types/menu'
interface Props {
item: MenuItem
level: number
}
const props = defineProps<Props>()
const emit = defineEmits<{
edit: [item: MenuItem]
delete: [itemId: string]
toggle: [itemId: string]
}>()
const isExpanded = ref(props.level === 0) // Top level items expanded by default
const showDeleteDialog = ref(false)
const toggleExpanded = () => {
isExpanded.value = !isExpanded.value
}
const confirmDelete = () => {
showDeleteDialog.value = true
}
const handleDelete = () => {
showDeleteDialog.value = false
// Emit delete event
emit('delete', props.item.id)
}
</script>
<style scoped>
.menu-tree-item {
border-bottom: 1px solid #e0e0e0;
}
.menu-tree-item:last-child {
border-bottom: none;
}
.border-b {
border-bottom: 1px solid #e0e0e0;
}
</style>

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
import { BlogCardData } from '@/data/dashboard/dashboardData';
</script>
<template>
<v-row>
<v-col cols="12" lg="4" v-for="card in BlogCardData" :key="card.title">
<v-card elevation="10" rounded="md" class="card-hover">
<div>
<v-img :src="card.coveravatar" height="250px" cover class="rounded-t-md align-end text-right">
<v-card-item
><v-chip class="bg-surface text-body-2 font-weight-medium" size="small" rounded="sm" v-text="card.read"></v-chip
></v-card-item>
</v-img>
<v-avatar size="40" class="mt-n7 mx-6">
<img :src="card.avatar" alt="icon" height="40" />
</v-avatar>
<v-card-item class="pt-4">
<v-chip class="text-body-2 font-weight-medium bg-grey100" size="small" rounded="sm" v-text="card.category"></v-chip>
<h5 class="text-h5 text-13 my-6 custom-text-primary">
<NuxtLink class="text-decoration-none color-inherits custom-title" :to="card.link">{{ card.title }}</NuxtLink>
</h5>
<div class="d-flex align-center justify-space-between">
<div>
<v-avatar class="" size="18">
<EyeIcon size="18" class="text-textPrimary" />
</v-avatar>
<span class="text-subtitle-1 ml-2 text-textSecondary" v-text="card.view"></span>
<v-avatar class="ml-4" size="18">
<Message2Icon size="18" class="text-textPrimary" />
</v-avatar>
<span class="text-subtitle-1 ml-2 text-textSecondary" v-text="card.comments"></span>
</div>
<div>
<v-avatar size="10">
<CircleIcon size="10" class="text-textPrimary" />
</v-avatar>
<span class="text-subtitle-2 ml-2 text-textSecondary" v-text="card.time"></span>
</div>
</div>
</v-card-item>
</div>
</v-card>
</v-col>
</v-row>
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import { DailyActivitiesData } from '@/data/dashboard/dashboardData';
import { Icon } from '@iconify/vue';
</script>
<template>
<v-card elevation="10">
<v-card-item>
<v-card-title class="text-h5">Daily activities</v-card-title>
<div class="daily-activities mt-8 px-3">
<div v-for="list in DailyActivitiesData" :key="list.title">
<v-row class="d-flex mb-1">
<v-col cols="4" lg="3" md="auto" sm="auto" class="px-0 pt-0 pb-0 d-flex align-start">
<p class="text-body-1 text-textSecondary text-no-wrap">{{ list.title }}</p>
</v-col>
<v-col cols="1" sm="1" class="px-0 text-center pt-0 pb-0 mt-1">
<Icon icon="tabler:circle-filled" size="13" :class="'text-' + list.textcolor" />
<div v-if="list.line" class="line mx-auto bg-grey100"></div>
</v-col>
<v-col cols="7" sm="8" class="pt-0 pb-0">
<h6 v-if="list.boldtext" class="text-body-1 text-textPrimary">{{ list.subtitle }}</h6>
<p v-else class="text-body-1 text-textPrimary">{{ list.subtitle }}</p>
<div class="mt-n1">
<NuxtLink :to="list.url" class="text-body-1 text-primary text-decoration-none" v-if="list.link">{{
list.link
}}</NuxtLink>
</div>
</v-col>
</v-row>
</div>
</div>
</v-card-item>
</v-card>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue';
</script>
<template>
<v-card elevation="10">
<v-card-item>
<div class="d-flex ga-3 align-center">
<v-avatar size="48" class="rounded-md bg-lightsecondary">
<Icon icon="solar:football-outline" class="text-secondary" height="25" />
</v-avatar>
<h6 class="text-h6 heading">New Customers</h6>
</div>
<div class="d-flex align-center justify-space-between mb-3 mt-12">
<h5 class="text-textPrimary text-subtitle-1 font-weight-medium">New Goals</h5>
<div class="text-textPrimary text-subtitle-1 font-weight-medium">83%</div>
</div>
<v-progress-linear model-value="83" height="7" color="secondary" bg-color="lightsecondary" rounded></v-progress-linear>
</v-card-item>
</v-card>
</template>

View File

@@ -0,0 +1,115 @@
<script setup lang="ts">
import { ref } from 'vue';
import { computed } from 'vue';
import { Icon } from '@iconify/vue';
const select = ref('Sept 2025');
const items = ref(['Sept 2025', 'Oct 2025', 'Nov 2025']);
/* Chart */
const chartOptions = computed(() => {
return {
chart: {
toolbar: {
show: false
},
type: 'bar',
fontFamily: 'inherit',
foreColor: '#adb0bb',
height: 285,
stacked: true,
offsetX: -15
},
colors: ['rgba(var(--v-theme-primary))', 'rgba(var(--v-theme-error))'],
plotOptions: {
bar: {
horizontal: false,
barHeight: '60%',
columnWidth: '15%',
borderRadius: [6],
borderRadiusApplication: 'end',
borderRadiusWhenStacked: 'all'
}
},
dataLabels: {
enabled: false
},
legend: {
show: false
},
grid: {
show: true,
padding: {
top: 0,
bottom: 0,
right: 0
},
borderColor: 'rgba(0,0,0,0.05)',
xaxis: {
lines: {
show: true
}
},
yaxis: {
lines: {
show: true
}
}
},
yaxis: {
min: -5,
max: 5,
tickAmount: 4
},
xaxis: {
axisBorder: {
show: false
},
axisTicks: {
show: false
},
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'July', 'Aug', 'Sep'],
labels: {
style: { fontSize: '13px', colors: '#adb0bb', fontWeight: '400' }
}
},
tooltip: {
theme: 'dark'
}
};
});
const Chart = {
series: [
{
name: '2025',
data: [1.2, 2.7, 1, 3.6, 2.1, 2.7, 2.2, 1.3, 2.5]
},
{
name: '2023',
data: [-2.8, -1.1, -2.5, -1.5, -2.3, -1.9, -1, -2.1, -1.3]
}
]
};
</script>
<template>
<v-card elevation="10">
<v-card-item>
<div class="d-md-flex justify-space-between mb-mb-0 mb-3">
<v-card-title class="text-h5">Revenue Forecast</v-card-title>
<div>
<v-select
v-model="select"
:items="items"
variant="outlined"
density="compact"
class="text-body-1"
hide-details
></v-select>
</div>
</div>
<div class="mx-n1 mt-4 pt-2">
<apexchart type="bar" height="285" class="rounded-bars" :options="chartOptions" :series="Chart.series"> </apexchart>
</div>
</v-card-item>
</v-card>
</template>

View File

@@ -0,0 +1,54 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
import { RevenueProjectsData } from '@/data/dashboard/dashboardData';
</script>
<template>
<v-card elevation="10" class="revenue-products">
<v-card-item class="pb-4">
<div class="d-flex ga-3 align-center justify-space-between">
<v-card-title class="text-h5">Revenue by Product</v-card-title>
</div>
<div class="mt-4">
<v-table class="revenue-table">
<template v-slot:default>
<thead>
<tr>
<th class="text-body-1">Assigned</th>
<th class="text-body-1">Progress</th>
<th class="text-body-1">Priority</th>
<th class="text-body-1">Budget</th>
</tr>
</thead>
<tbody>
<tr v-for="item in RevenueProjectsData" :key="item.leadname" class="month-item">
<td>
<div class="d-flex align-center">
<v-avatar size="48" rounded="md"> <img :src="item.img" :alt="item.img" width="48" /></v-avatar>
<div class="mx-3">
<h6 class="text-subtitle-1 text-no-wrap font-weight-medium">{{ item.leadname }}</h6>
<span class="text-body-1 text-no-wrap text-textSecondary">{{ item.designation }}</span>
</div>
</div>
</td>
<td>
<p class="text-no-wrap text-body-1 text-textSecondary">
{{ item.projectname }}
</p>
</td>
<td>
<v-chip rounded="sm" class="font-weight-semibold" :color="item.statuscolor" size="small" label>{{
item.statustext
}}</v-chip>
</td>
<td>
<p class="text-body-1">{{ item.money }}</p>
</td>
</tr>
</tbody>
</template>
</v-table>
</div>
</v-card-item>
</v-card>
</template>

View File

@@ -0,0 +1,68 @@
<script setup lang="ts">
import { computed } from 'vue';
import { Icon } from '@iconify/vue';
/* Chart */
const chartOptions = computed(() => {
return {
chart: {
type: 'line',
fontFamily: 'inherit',
foreColor: '#adb0bb',
height: 60,
sparkline: {
enabled: true
},
group: 'sparklines'
},
colors:['rgba(var(--v-theme-error))'],
stroke: {
curve: 'smooth',
width: 2
},
markers: {
size: 0
},
tooltip: {
theme: 'dark',
fixed: {
enabled: true,
position: 'right'
},
x: {
show: false
}
}
};
});
const Chart = {
series: [
{
name: 'Income',
data: [30, 25, 35, 20, 30, 40]
}
]
};
</script>
<template>
<v-card elevation="10">
<v-card-item>
<div class="d-flex ga-3 align-center">
<v-avatar size="48" class="rounded-md bg-lighterror">
<Icon icon="solar:box-linear" class="text-error" height="25" />
</v-avatar>
<h6 class="text-h6 heading">Total Income</h6>
</div>
<v-row class="mt-6">
<v-col cols="6">
<h3 class="text-h3 heading">$680</h3>
<div class="text-success text-subtitle-2 font-weight-medium mt-2">+18%</div>
</v-col>
<v-col cols="6">
<apexchart type="line" height="60" :options="chartOptions" :series="Chart.series"> </apexchart>
</v-col>
</v-row>
</v-card-item>
</v-card>
</template>

65
components/layout/full/Main.vue Executable file
View File

@@ -0,0 +1,65 @@
<script setup lang="ts">
import { onMounted, ref, shallowRef, watch } from "vue";
import { useDisplay } from "vuetify";
import sidebarItems from "~/components/layout/full/vertical-sidebar/sidebarItem";
import { Menu2Icon } from "vue-tabler-icons";
import { useCustomizerStore } from "~/store/customizer";
const sidebarMenu = shallowRef(sidebarItems);
const customizer = useCustomizerStore();
const { mdAndDown } = useDisplay();
const sDrawer = ref(true);
onMounted(() => {
sDrawer.value = !mdAndDown.value; // hide on mobile, show on desktop
});
watch(mdAndDown, (val) => {
sDrawer.value = !val;
});
</script>
<template>
<!------Sidebar-------->
<v-locale-provider>
<v-app
:theme="customizer.actTheme"
class="bg-containerBg"
:class="[
customizer.actTheme,
customizer.mini_sidebar ? 'mini-sidebar' : '',
customizer.setHorizontalLayout ? 'horizontalLayout' : 'verticalLayout',
customizer.setBorderCard ? 'cardBordered' : ''
]"
>
<!---Customizer location left side--->
<v-navigation-drawer
app
temporary
elevation="10"
location="left"
v-model="customizer.Customizer_drawer"
width="320"
class="left-customizer"
>
<Customizer />
</v-navigation-drawer>
<LazyLayoutFullVerticalSidebar v-if="!customizer.setHorizontalLayout" />
<div :class="customizer.boxed ? 'maxWidth' : 'full-header'">
<LazyLayoutFullVerticalHeader v-if="!customizer.setHorizontalLayout" />
</div>
<v-main class="ml-md-4">
<div class="rtl-lyt mb-3 hr-layout bg-containerBg">
<v-container
fluid
class="page-wrapper bg-background pt-md-8 rounded-xl"
>
<div>
<div class="">
<NuxtPage />
</div>
</div>
</v-container>
</div>
</v-main>
</v-app>
</v-locale-provider>
</template>

View File

@@ -0,0 +1,154 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useTheme } from 'vuetify';
import { useCustomizerStore } from '~/store/customizer';
import {
CheckIcon,
LayoutColumnsIcon,
LayoutDistributeHorizontalIcon,
LayoutDistributeVerticalIcon,
LayoutNavbarIcon,
LayoutSidebarLeftCollapseIcon,
TextDirectionLtrIcon,
TextDirectionRtlIcon
} from 'vue-tabler-icons';
const theme = useTheme();
const customizer = useCustomizerStore();
// template skin color options
const themeColors = ref([
{
name: 'BLUE_THEME',
bg: 'themeBlue'
},
{
name: 'AQUA_THEME',
bg: 'themeAqua'
},
{
name: 'PURPLE_THEME',
bg: 'themePurple'
},
{
name: 'GREEN_THEME',
bg: 'themeGreen'
},
{
name: 'CYAN_THEME',
bg: 'themeCyan'
},
{
name: 'ORANGE_THEME',
bg: 'themeOrange'
}
]);
// Dark Theme Colors
const DarkthemeColors = ref([
{ name: 'DARK_BLUE_THEME', bg: 'themeDarkBlue' },
{ name: 'DARK_AQUA_THEME', bg: 'themeDarkAqua' },
{ name: 'DARK_PURPLE_THEME', bg: 'themeDarkPurple' },
{ name: 'DARK_GREEN_THEME', bg: 'themeDarkGreen' },
{ name: 'DARK_CYAN_THEME', bg: 'themeDarkCyan' },
{ name: 'DARK_ORANGE_THEME', bg: 'themeDarkOrange' }
]);
</script>
<!------------------------------------->
<!-- Customizer -->
<!------------------------------------->
<template>
<div class="pa-6">
<h5 class="text-h5">Settings</h5>
</div>
<v-divider></v-divider>
<perfect-scrollbar style="height: calc(100vh - 90px)">
<div class="pa-6">
<h6 class="text-h6 mb-2">Sidebar Layout</h6>
<v-btn-toggle v-model="customizer.setHorizontalLayout" color="primary" class="my-2 btn-group-custom gap-3" rounded="0" group>
<v-btn :value="false" variant="text" elevation="9" class="rounded-md">
<LayoutColumnsIcon stroke-width="1.5" size="21" class="mr-2 icon" /> Vertical
</v-btn>
<v-btn :value="true" variant="text" elevation="9" class="rounded-md">
<LayoutNavbarIcon stroke-width="1.5" size="21" class="mr-2 icon" /> Horizontal
</v-btn>
</v-btn-toggle>
<!------Template Direction------>
<h6 class="text-h6 mt-8 mb-5">Template Color</h6>
<v-item-group mandatory v-model="customizer.actTheme" class="ml-n2 v-row">
<v-col cols="4" v-for="theme in themeColors" :key="theme.name" class="pa-2">
<v-item v-slot="{ isSelected, toggle }" :value="theme.name">
<v-sheet
rounded="md"
class="border cursor-pointer d-block text-center px-5 py-4 hover-btns"
elevation="9"
@click="toggle"
>
<v-avatar :class="theme.bg" size="25" variant="text">
<CheckIcon color="white" size="18" v-if="isSelected" />
</v-avatar>
</v-sheet>
</v-item>
</v-col>
</v-item-group>
<h6 class="text-h6 mt-11 mb-5">Template Dark Color</h6>
<v-item-group mandatory v-model="customizer.actTheme" class="ml-n2 v-row">
<v-col cols="4" v-for="theme in DarkthemeColors" :key="theme.name" class="pa-2">
<v-item v-slot="{ isSelected, toggle }" :value="theme.name">
<v-sheet
rounded="md"
class="border cursor-pointer d-block text-center px-5 py-4 hover-btns"
elevation="9"
@click="toggle"
>
<v-avatar :class="theme.bg" size="25">
<CheckIcon color="white" size="18" v-if="isSelected" />
</v-avatar>
</v-sheet>
</v-item>
</v-col>
</v-item-group>
<h6 class="text-h6 mt-11 mb-2">Container Option</h6>
<v-btn-toggle v-model="customizer.boxed" color="primary" class="my-2 btn-group-custom gap-3" rounded="0" group>
<v-btn :value="true" variant="text" elevation="9" class="rounded-md">
<LayoutDistributeVerticalIcon stroke-width="1.5" size="21" class="mr-2 icon" />
Boxed
</v-btn>
<v-btn :value="false" variant="text" elevation="9" class="rounded-md ">
<LayoutDistributeHorizontalIcon stroke-width="1.5" size="21" class="mr-2 icon" />
Full
</v-btn>
</v-btn-toggle>
<!---Horizontal demo hide this option --->
<v-sheet v-if="customizer.setHorizontalLayout != true">
<h6 class="text-h6 mt-11 mb-2">Sidebar Type</h6>
<v-btn-toggle v-model="customizer.mini_sidebar" color="primary" class="my-2 btn-group-custom gap-3" rounded="0" group>
<v-btn :value="false" variant="text" elevation="9" class="rounded-md">
<LayoutSidebarIcon stroke-width="1.5" size="21" class="mr-2 icon" />
Full
</v-btn>
<v-btn :value="true" variant="text" elevation="9" class="rounded-md">
<LayoutSidebarLeftCollapseIcon stroke-width="1.5" size="21" class="mr-2 icon" />
Collapse
</v-btn>
</v-btn-toggle>
</v-sheet>
<h6 class="text-h6 mt-11 mb-2">Card with</h6>
<v-btn-toggle v-model="customizer.setBorderCard" color="primary" class="my-2 btn-group-custom gap-3" rounded="0" group>
<v-btn :value="false" variant="text" elevation="9" class="rounded-md">
<LayoutSidebarLeftCollapseIcon stroke-width="1.5" size="21" class="mr-2 icon" />
Shadow
</v-btn>
<v-btn :value="true" variant="text" elevation="9" class="rounded-md">
<LayoutSidebarIcon stroke-width="1.5" size="21" class="mr-2 icon" />
Border
</v-btn>
</v-btn-toggle>
</div>
</perfect-scrollbar>
</template>
<style lang="scss"></style>

View File

@@ -0,0 +1,26 @@
<script setup>
import { computed } from 'vue';
import { useCustomizerStore } from '~/store/customizer';
const customizer = useCustomizerStore();
//const dark = ref(false);
const dark = computed(() => {
if (
customizer.actTheme === 'DARK_BLUE_THEME' ||
customizer.actTheme === 'DARK_AQUA_THEME' ||
customizer.actTheme === 'DARK_ORANGE_THEME' ||
customizer.actTheme === 'DARK_PURPLE_THEME' ||
customizer.actTheme === 'DARK_GREEN_THEME' ||
customizer.actTheme === 'DARK_CYAN_THEME'
) {
return true;
} else {
return false;
}
});
</script>
<template>
<LayoutFullLogoLight v-if="dark" />
<LayoutFullLogoDark v-else />
</template>

View File

@@ -0,0 +1,10 @@
<script setup lang="ts">
import Logoimg from "/images/logos/dark-logo.svg";
</script>
<template>
<div class="logo">
<NuxtLink to="/">
<img :src="Logoimg" alt="home" />
</NuxtLink>
</div>
</template>

View File

@@ -0,0 +1,10 @@
<script setup lang="ts">
import Logoimg from "/images/logos/light-logo.svg";
</script>
<template>
<div class="logo">
<NuxtLink to="/">
<img :src="Logoimg" alt="home" />
</NuxtLink>
</div>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
import {appsLink} from '~/_mockApis/headerData';
import { Icon } from '@iconify/vue';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- apps link -->
<!-- ---------------------------------------------- -->
<v-row>
<v-col cols="12" md="6" v-for="(item, i) in appsLink" :key="i">
<router-link :to="item.href" class="text-decoration-none custom-text-primary">
<div class="d-flex align-center">
<v-avatar size="45" :color=" 'light' + item.color" rounded="md">
<Icon :icon="'solar:' + item.avatar" height="24" :class="'text-' + item.color"/>
</v-avatar>
<div class="ml-3">
<h6 class="text-subtitle-1 mb-1 heading custom-title">{{ item.title }}</h6>
<p class="text-subtitle-2 textSecondary">{{ item.subtext }}</p>
</div>
</div>
</router-link>
</v-col>
</v-row>
</template>

View File

@@ -0,0 +1,47 @@
<script setup lang="ts">
import { ref } from 'vue';
import { languageDD } from '@/_mockApis/headerData';
import flag1 from '~/assets/images/flag/icon-flag-en.svg';
import flag2 from '~/assets/images/flag/icon-flag-ro.svg';
import flag3 from '~/assets/images/flag/icon-flag-zh.svg';
import flag4 from '~/assets/images/flag/icon-flag-fr.svg';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- language DD -->
<!-- ---------------------------------------------- -->
<v-menu open-on-hover open-on-click>
<template v-slot:activator="{ props }">
<v-btn icon class="custom-hover-primary" size="small" variant="text" color="primary" v-bind="props">
<v-avatar size="20">
<img v-if="$i18n.locale === 'en'" :src="flag1" :alt="$i18n.locale" width="22" height="22" class="obj-cover" />
<img v-if="$i18n.locale === 'fr'" :src="flag4" :alt="$i18n.locale" width="22" height="22" class="obj-cover" />
<img v-if="$i18n.locale === 'ro'" :src="flag2" :alt="$i18n.locale" width="22" height="22" class="obj-cover" />
<img v-if="$i18n.locale === 'zh'" :src="flag3" :alt="$i18n.locale" width="22" height="22" class="obj-cover" />
</v-avatar>
</v-btn>
</template>
<v-sheet rounded="md" width="200" elevation="10">
<v-list class="theme-list">
<v-list-item
v-for="(item, index) in languageDD"
:key="index"
color="primary"
:active="$i18n.locale == item.value"
class="d-flex align-center"
@click="() => ($i18n.locale = item.value)"
>
<template v-slot:prepend>
<v-avatar size="22">
<img :src="item.avatar" :alt="item.avatar" width="22" height="22" class="obj-cover" />
</v-avatar>
</template>
<v-list-item-title class="text-subtitle-1 font-weight-regular">
{{ item.title }}
<span class="text-disabled text-subtitle-1 pl-2">({{ item.subtext }})</span>
</v-list-item-title>
</v-list-item>
</v-list>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- mega menu DD -->
<!-- ---------------------------------------------- -->
<v-menu open-on-hover open-on-click >
<template v-slot:activator="{ props }">
<v-btn class="hidden-sm-and-down custom-hover-primary" size="small" variant="text" icon color="primary" v-bind="props"> <Icon icon="solar:widget-3-line-duotone" height="20" /> </v-btn>
</template>
<v-sheet width="900" height="300" elevation="10" rounded="md" class="pa-0 overflow-hidden">
<div>
<v-row>
<v-col cols="12" md="8" class="d-flex">
<div class="pa-6">
<LazyLayoutFullVerticalHeaderAppsLink />
</div>
</v-col>
<v-col cols="12" md="4" class="pa-0">
<img src="@/assets/images/backgrounds/mega-dd-bg.jpg" alt="matdash-img" height="320" class="w-100" />
</v-col>
</v-row>
</div>
</v-sheet>
</v-menu>
</template>
s

View File

@@ -0,0 +1,43 @@
<script setup lang="ts">
import {notifications} from '~/_mockApis/headerData';
import { Icon } from '@iconify/vue';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- notifications DD -->
<!-- ---------------------------------------------- -->
<v-menu open-on-hover open-on-click >
<template v-slot:activator="{ props }">
<v-btn icon class="custom-hover-primary" size="small" variant="text" color="primary" v-bind="props">
<Icon icon="solar:bell-bing-line-duotone" height="22" />
</v-btn>
</template>
<v-sheet rounded="md" width="360" elevation="10">
<div class="px-6 pt-6 pb-4">
<div class="d-flex align-center justify-space-between">
<h6 class="text-h5">Notifications</h6>
<v-chip color="primary" variant="flat" size="small" class="text-white rounded-sm">5 New</v-chip>
</div>
</div>
<perfect-scrollbar style="height: 350px">
<v-list class="py-0 theme-list" lines="two">
<v-list-item v-for="item in notifications" :key="item.title" :value="item" color="primary" class="py-3 px-6">
<template v-slot:prepend>
<v-avatar size="45" :color=" 'light' + item.color" rounded="circle">
<Icon :icon="'solar:' + item.avatar" height="20" :class="'text-' + item.color"/>
</v-avatar>
</template>
<div class="d-flex justify-space-between">
<h6 class="text-subtitle-1 heading mb-1">{{ item.title }}</h6>
<span class="text-subtitle-2 textSecondary">{{ item.time }}</span>
</div>
<p class="text-subtitle-2 font-weight-regular textSecondary">{{ item.subtitle }}</p>
</v-list-item>
</v-list>
</perfect-scrollbar>
<div class="py-4 px-6 text-center">
<v-btn color="primary" variant="flat" size="large" block>See all Notifications</v-btn>
</div>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,227 @@
<!-- components/layout/ProfileDD.vue -->
<script setup lang="ts">
import { MailIcon } from "vue-tabler-icons";
import { profileDD } from "~/_mockApis/headerData";
import { PerfectScrollbar } from "vue3-perfect-scrollbar";
import { useUserInfo } from "~/composables/useUserInfo";
import { computed } from "vue";
const userInfo = useUserInfo();
// Enhanced logout with proper error handling
const logout = async () => {
try {
// Use the updated logout method from useUserInfo that handles Keycloak logout and session clearing
await userInfo.logout({
reason: "idle",
confirmDialog: false // Show confirmation dialog
});
} catch (error) {
console.error("Logout from profile failed:", error);
}
};
// **TAMBAHAN: Full logout function dengan konfirmasi**
const fullLogout = async () => {
try {
// Tampilkan konfirmasi sebelum full logout
const confirmed = confirm(
"Apakah Anda yakin ingin keluar dari semua sesi? Ini akan menghapus semua data lokal dan sesi Keycloak."
);
if (!confirmed) return;
console.log("Initiating full logout from ProfileDD...");
// Gunakan fullLogout dari useUserInfo composable
await userInfo.fullLogout();
} catch (error) {
console.error("Full logout from profile failed:", error);
// Fallback jika fullLogout gagal
try {
console.log("Attempting fallback logout...");
await userInfo.logout({
reason: "manual",
clearStorage: true
});
} catch (fallbackError) {
console.error("Fallback logout also failed:", fallbackError);
// Last resort - force redirect
if (process.client) {
localStorage.clear();
sessionStorage.clear();
window.location.href = "/auth/login?reason=force";
}
}
}
};
// **TAMBAHAN: Logout dengan konfirmasi untuk UX yang lebih baik**
const logoutWithConfirmation = async () => {
try {
const confirmed = confirm("Apakah Anda yakin ingin keluar?");
if (!confirmed) return;
await logout();
} catch (error) {
console.error("Logout with confirmation failed:", error);
}
};
// Get user display info from session
const getUserDisplayInfo = () => {
if (!userInfo.user.value && !userInfo.data.value?.user)
return {
name: "Guest User",
email: "guest@example.com",
role: "Guest"
};
const user = userInfo.user.value || userInfo.data.value?.user || {};
return {
name: user.name || user.given_name || user.preferred_username || "User",
email: user.email || "No email",
role: userInfo.userRoles.value[0] || "User"
};
};
const displayInfo = computed(() => getUserDisplayInfo());
// Computed properties for decodedToken and clientScopes
// const decodedToken = computed(() => userInfo.decodedToken.value);
// const clientScopes = computed(() => userInfo.clientScopes.value);
// **TAMBAHAN: Computed property untuk menampilkan status session**
const sessionInfo = computed(() => {
return {
isAuthenticated: userInfo.isAuthenticated.value,
sessionExpires: userInfo.sessionExpires.value,
hasValidToken: !!userInfo.idToken.value
};
});
</script>
<template>
<v-menu :close-on-content-click="false">
<template v-slot:activator="{ props }">
<v-btn class="custom-hover-primary" variant="text" v-bind="props" icon>
<v-avatar size="35">
<img
src="~/assets/images/profile/user-1.jpg"
width="35"
alt="User Avatar"
/>
</v-avatar>
</v-btn>
</template>
<v-sheet rounded="md" width="360" elevation="10">
<div class="px-8 pt-6">
<h6 class="text-h5 font-weight-medium">User Profile</h6>
<div class="d-flex align-center mt-4 pb-6">
<v-avatar size="80">
<img src="~/assets/images/profile/user-1.jpg" width="80" />
</v-avatar>
<div class="ml-3">
<h6 class="text-h6 mb-n1">{{ displayInfo.name }}</h6>
<span class="text-subtitle-1 font-weight-regular textSecondary">
{{ displayInfo.role }}
</span>
<div class="d-flex align-center mt-1">
<MailIcon size="18" stroke-width="1.5" />
<span
class="text-subtitle-1 font-weight-regular textSecondary ml-2"
>
{{ displayInfo.email }}
</span>
</div>
<!-- **TAMBAHAN: Tampilkan status session** -->
<div class="mt-2">
<v-chip
:color="sessionInfo.isAuthenticated ? 'success' : 'error'"
size="small"
variant="outlined"
>
{{ sessionInfo.isAuthenticated ? "Active" : "Inactive" }}
</v-chip>
</div>
</div>
</div>
<v-divider></v-divider>
</div>
<PerfectScrollbar style="height: calc(100vh - 240px); max-height: 240px">
<v-list class="py-0 theme-list" lines="two">
<v-list-item
v-for="item in profileDD"
:key="item.title"
class="py-4 px-8 custom-text-primary"
:to="item.href"
>
<template v-slot:prepend>
<v-avatar
size="48"
color="lightprimary"
class="mr-3"
rounded="md"
>
<img
:src="item.avatar"
width="24"
height="24"
:alt="item.avatar"
/>
</v-avatar>
</template>
<div>
<h6 class="text-subtitle-1 font-weight-bold mb-2 custom-title">
{{ item.title }}
</h6>
</div>
<p class="text-subtitle-1 font-weight-regular textSecondary">
{{ item.subtitle }}
</p>
</v-list-item>
</v-list>
</PerfectScrollbar>
<!-- **DIPERBAIKI: Logout buttons section dengan multiple options** -->
<div class="pt-4 pb-6 px-8">
<!-- Regular Logout Button -->
<v-btn
color="primary"
variant="outlined"
block
@click="logoutWithConfirmation"
prepend-icon="mdi-logout"
class="mb-2"
>
Logout
</v-btn>
<!-- **TAMBAHAN: Full Logout Button** -->
<v-btn
color="error"
variant="outlined"
block
@click="fullLogout"
prepend-icon="mdi-logout-variant"
class="mb-2"
>
Full Logout
</v-btn>
<!-- **TAMBAHAN: Quick info text** -->
<div class="text-center mt-2">
<span class="text-caption textSecondary">
Full logout akan menghapus semua sesi dan data lokal
</span>
</div>
</div>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
</script>
<template>
<v-menu open-on-hover open-on-click >
<template v-slot:activator="{ props }">
<v-btn icon class="custom-hover-primary" size="small" variant="text" color="primary" v-bind="props">
<Icon icon="solar:widget-line-duotone" height="22" />
</v-btn>
</template>
<v-sheet rounded="md" width="360" elevation="10">
<perfect-scrollbar style="height: 370px">
<div class="pa-6">
<LazyLayoutFullVerticalHeaderAppsLink />
</div>
</perfect-scrollbar>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,39 @@
<script setup>
import { Icon } from '@iconify/vue';
import { searchSugg } from '~/_mockApis/headerData';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- search1 -->
<!-- ------------------------------------------------>
<v-menu :close-on-content-click="false">
<template v-slot:activator="{ props }">
<v-btn icon class="custom-hover-primary" size="small" variant="text" color="primary" v-bind="props">
<Icon icon="solar:magnifer-linear" height="20" />
</v-btn>
</template>
<v-sheet width="360" elevation="10" rounded="md">
<div class="d-flex align-center justify-space-between pa-5">
<v-text-field placeholder="Search" color="primary" density="compact" variant="outlined" hide-details></v-text-field>
</div>
<v-divider></v-divider>
<h5 class="text-h5 mt-4 px-5 pb-4">Quick Page Links</h5>
<perfect-scrollbar style="height: 380px">
<v-list class="pt-0 pb-5" lines="two">
<v-list-item
:value="item"
v-for="(item, index) in searchSugg"
:key="index"
:to="item.href"
color="primary"
class="px-5 py-2"
>
<h6 class="text-subtitle-1 heading mb-1">{{ item.title }}</h6>
<p class="text-subtitle-2 textSecondary">{{ item.href }}</p>
</v-list-item>
</v-list>
</perfect-scrollbar>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useTheme } from 'vuetify';
import { useCustomizerStore } from '~/store/customizer';
import { Icon } from '@iconify/vue';
const theme = useTheme();
const customizer = useCustomizerStore();
// template skin color options
const themeColors = ref([
{
name: 'BLUE_THEME',
bg: 'togglethemeBlue'
},
{
name: 'DARK_BLUE_THEME',
bg: 'togglethemeDarkBlue'
}
]);
</script>
<template>
<div class="position-relative">
<v-item-group mandatory v-model="customizer.actTheme" class="d-flex">
<div v-for="theme in themeColors" :key="theme.name">
<v-item v-slot="{ toggle }" :value="theme.name">
<v-sheet rounded="circle" class="cursor-pointer text-center hover-btns" elevation="0" @click="toggle">
<v-btn icon :class="theme.bg" class="custom-hover-primary" size="small" variant="text" color="primary">
<SunIcon v-if="theme.bg == 'togglethemeBlue'" :class="theme.bg" height="22" />
<MoonIcon v-if="theme.bg == 'togglethemeDarkBlue'" :class="theme.bg" height="22" />
</v-btn>
</v-sheet>
</v-item>
</div>
</v-item-group>
</div>
</template>

View File

@@ -0,0 +1,120 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
import { useCustomizerStore } from '~/store/customizer';
// import { useEcomStore } from '@/stores/apps/eCommerce';
// import LanguageDD from './LanguageDD.vue';
import NotificationDD from './NotificationDD.vue';
import ProfileDD from './ProfileDD.vue';
import Searchbar from './Searchbar.vue';
import RightMobileSidebar from './RightMobileSidebar.vue';
import Navigations from './Navigations.vue';
import { Icon } from '@iconify/vue';
import Logo from '../logo/Logo.vue';
import ThemeToggler from './ThemeToggler.vue';
const customizer = useCustomizerStore();
const showSearch = ref(false);
const priority = ref(customizer.setHorizontalLayout ? 0 : 0);
function searchbox() {
showSearch.value = !showSearch.value;
}
watch(priority, (newPriority) => {
priority.value = newPriority;
});
// count items
// const store = useEcomStore();
// const getCart = computed(() => {
// return store.cart;
// });
</script>
<template>
<v-app-bar elevation="0" :priority="priority" height="70" id="top" class="main-head">
<v-btn
class="hidden-lg-and-up custom-hover-primary"
size="small"
variant="text"
color="primary"
icon
@click.stop="customizer.SET_SIDEBAR_DRAWER"
>
<Icon icon="solar:hamburger-menu-line-duotone" height="22" />
</v-btn>
<!-- ---------------------------------------------- -->
<!-- Search part -->
<!-- ---------------------------------------------- -->
<Searchbar />
<!-- ---------------------------------------------- -->
<!-- Mega menu -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<Navigations />
</div>
<v-spacer class="hidden-sm-and-down" />
<!-- ---------------------------------------------- -->
<!-- Mobile Logo -->
<!-- ---------------------------------------------- -->
<div class="hidden-md-and-up">
<Logo />
</div>
<ThemeToggler />
<!-- ---------------------------------------------- -->
<!-- translate -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<LanguageDD />
</div>
<!-- ---------------------------------------------- -->
<!-- ShoppingCart -->
<!-- ---------------------------------------------- -->
<!-- <v-btn icon class="custom-hover-primary hidden-sm-and-down" size="small" variant="text" color="primary" to="/ecommerce/checkout">
<v-badge color="error" :content="getCart?.length">
<Icon icon="solar:cart-large-2-outline" height="22" />
</v-badge>
</v-btn> -->
<!-- ---------------------------------------------- -->
<!-- Notification -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<NotificationDD />
</div>
<!-- ---------------------------------------------- -->
<!-- User Profile -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<ProfileDD />
</div>
<!----Mobile ----->
<v-menu :close-on-content-click="true" class="mobile_popup">
<template v-slot:activator="{ props }">
<v-btn icon class="hidden-md-and-up custom-hover-primary" color="primary" variant="text" v-bind="props" size="small">
<Icon icon="solar:menu-dots-bold-duotone" height="22" />
</v-btn>
</template>
<v-sheet rounded="lg" elevation="10" class="mt-4 dropdown-box px-4 py-3">
<div class="d-flex justify-space-between align-center">
<RightMobileSidebar />
<!-- <LanguageDD /> -->
<!-- <v-btn icon variant="text" class="mr-sm-3 mr-2 custom-hover-primary" to="/ecommerce/checkout" size="small">
<v-badge color="primary" :content="getCart?.length" offset-x="-4" offset-y="-6">
<Icon icon="solar:cart-large-2-outline" height="22" />
</v-badge>
</v-btn> -->
<NotificationDD />
<ProfileDD />
</div>
</v-sheet>
</v-menu>
</v-app-bar>
</template>

View File

@@ -0,0 +1,53 @@
<script setup>
const props = defineProps({ item: Object, level: Number });
</script>
<template>
<div class="mb-0">
<!---Single Item-->
<v-list-item
:href="item.external ? item.to : undefined"
:to="!item.external ? item.to : undefined"
rounded="lg"
class=""
color=""
:ripple="false"
:disabled="item.disabled"
:target="item.external === true ? '_blank' : undefined"
>
<!---If icon-->
<template v-slot:prepend>
<Icon
:icon="'solar:' + item.icon"
height="18"
width="18"
:level="level"
class="dot"
:class="'text-' + item.BgColor"
/>
</template>
<v-list-item-title class="text-body-1 text-darkText">{{
item.title
}}</v-list-item-title>
<!---If Caption-->
<v-list-item-subtitle
v-if="item.subCaption"
class="text-caption mt-n1 hide-menu"
>
{{ item.subCaption }}
</v-list-item-subtitle>
<!---If any chip or label-->
<template v-slot:append v-if="item.chip">
<v-chip
color="secondary"
class="font-weight-bold"
size="x-small"
rounded="sm"
>
{{ item.chip }}
</v-chip>
</template>
</v-list-item>
</div>
</template>

View File

@@ -0,0 +1,23 @@
<script setup>
const props = defineProps({ item: Object, level: Number });
</script>
<template>
<template v-if="level > 0">
<component
:is="item"
size="5"
fill="currentColor"
stroke-width="1.5"
class="iconClass"
></component>
</template>
<template v-else>
<component
:is="item"
size="20"
stroke-width="1.5"
class="iconClass"
></component>
</template>
</template>

View File

@@ -0,0 +1,50 @@
export interface minisidebar {
icon?: string;
id?:number;
tooltip?:string
}
const MiniSideIcons: minisidebar[] = [
{
icon: 'layers-line-duotone',
tooltip:'Modules',
id: 1
},
{
icon: 'notes-line-duotone',
tooltip:'Pages',
id: 2,
},
{
icon: 'palette-round-line-duotone',
tooltip:'Forms',
id: 3
},
{
icon:'align-vertical-spacing-broken',
tooltip:'Tables',
id: 4
},
{
icon:'chart-line-duotone',
tooltip:'Charts',
id: 5
},
{
icon:'widget-6-line-duotone',
tooltip:'UI Components',
id: 6
},
{
icon:'lock-keyhole-line-duotone',
tooltip:'Authentication Pages',
id: 7
},
{
icon:'mirror-left-line-duotone',
tooltip:'Others',
id: 8
}
]
export default MiniSideIcons;

View File

@@ -0,0 +1,42 @@
<script setup>
import NavItem from '../NavItem/NavItem.vue';
import { Icon } from '@iconify/vue';
const props = defineProps({ item: Object, level: Number });
</script>
<template>
<!-- ---------------------------------------------- -->
<!---Item Childern -->
<!-- ---------------------------------------------- -->
<v-list-group no-action>
<!-- ---------------------------------------------- -->
<!---Dropdown -->
<!-- ---------------------------------------------- -->
<template v-slot:activator="{ props }">
<v-list-item v-bind="props" :value="item.title" rounded>
<!---Icon -->
<template v-slot:prepend>
<!-- <span class="dot"></span> -->
<Icon :icon="'solar:' + item.icon" height="18" width="18" :level="level" class="dot" :class="'text-' + item.BgColor" />
</template>
<!---Title -->
<v-list-item-title class="mr-auto">{{ $t(item.title) }}</v-list-item-title>
<!---If Caption-->
<v-list-item-subtitle v-if="item.subCaption" class="text-caption mt-n1 hide-menu">
{{ item.subCaption }}
</v-list-item-subtitle>
</v-list-item>
</template>
<!-- ---------------------------------------------- -->
<!---Sub Item-->
<!-- ---------------------------------------------- -->
<template v-for="(subitem, i) in item.children" :key="i" v-if="item.children">
<NavCollapse :item="subitem" v-if="subitem.children" :level="level + 1" />
<NavItem :item="subitem" :level="level + 1" v-else></NavItem>
</template>
</v-list-group>
<!-- ---------------------------------------------- -->
<!---End Item Sub Header -->
<!-- ---------------------------------------------- -->
</template>

View File

@@ -0,0 +1,14 @@
<script setup>
const props = defineProps({ item: Object });
</script>
<template>
<v-list-subheader
class="smallCap text-uppercase text-subtitle-2 mt-3 font-weight-bold d-flex align-items-center"
>
<span class="mini-icon"
><DotsIcon size="16" stroke-width="1.5" class="iconClass"
/></span>
<span class="mini-text">{{ $t(props.item.header) }}</span>
</v-list-subheader>
</template>

View File

@@ -0,0 +1,49 @@
<script setup>
import { Icon } from "@iconify/vue";
const props = defineProps({ item: Object, level: Number });
</script>
<template>
<!---Single Item-->
<v-list-item
:to="item.type === 'external' ? '' : item.to"
:href="item.type === 'external' ? item.to : ''"
rounded
:disabled="item.disabled"
:target="item.type === 'external' ? '_blank' : ''"
v-scroll-to="{ el: '#top' }"
>
<!---If icon-->
<template v-slot:prepend>
<Icon
:icon="'solar:' + item.icon"
height="18"
width="18"
:level="level"
class="dot"
:class="'text-' + item.BgColor"
/>
</template>
<v-list-item-title>{{ $t(item.title) }}</v-list-item-title>
<!---If Caption-->
<v-list-item-subtitle
v-if="item.subCaption"
class="text-caption mt-n1 hide-menu"
>
{{ item.subCaption }}
</v-list-item-subtitle>
<!---If any chip or label-->
<template v-slot:append v-if="item.chip">
<v-chip
:color="item.chipColor"
:class="'sidebarchip hide-menu bg-' + item.chipBgColor"
:size="item.chipIcon ? 'small' : 'small'"
:variant="item.chipVariant"
:prepend-icon="item.chipIcon"
>
{{ item.chip }}
</v-chip>
</template>
</v-list-item>
</template>

View File

@@ -0,0 +1,159 @@
<script setup lang="ts">
import { ref, shallowRef } from "vue";
import { useCustomizerStore } from "~/store/customizer";
import sidebarItems from "./sidebarItem";
import Logo from "../logo/Logo.vue";
import { Icon } from "@iconify/vue";
import { useRoute } from "vue-router";
// MiniSidebar Icons
import MiniSideIcons from "./MinIconItems";
const route = useRoute();
const findTitleByPath = (items: any, path: any) => {
let title = "";
for (const item of items) {
if (item.to === path) {
title = item.id;
break;
} else if (item.children) {
for (const child of item.children) {
if (child.to === path) {
title = item.id;
break;
} else if (child.children) {
for (const grandChild of child.children) {
if (grandChild.to === path) {
title = item.id;
break;
}
}
}
}
}
}
return title;
};
const foundId = findTitleByPath(sidebarItems, route.path);
const getCurrent = foundId ? foundId : 1;
const currentMenu = ref<any>(getCurrent);
function showData(data: any) {
currentMenu.value = data;
//customizer.SET_MINI_SIDEBAR(!customizer.mini_sidebar)
}
// MiniSidebar Icons End
const customizer = useCustomizerStore();
const sidebarMenu = shallowRef(sidebarItems);
</script>
<template>
<!-- Minisidebar Icons -->
<v-navigation-drawer
class="bg-light"
v-model="customizer.Sidebar_drawer"
top="0"
rail
rail-width="80"
>
<!-- <perfect-scrollbar class="miniscrollnavbar"> -->
<v-list-item class="px-0">
<!-- Toggle Sidebar Button -->
<div class="px-4 mb-3">
<v-btn
class="hidden-md-and-down my-2"
icon
rounded="md"
variant="plain"
@click.stop="customizer.SET_MINI_SIDEBAR(!customizer.mini_sidebar)"
>
<Icon icon="solar:hamburger-menu-line-duotone" height="25" />
</v-btn>
</div>
<div class="miniicons mt-lg-0 mt-4">
<!-- MiniSidebar Icons -->
<div class="d-flex flex-column gap-2">
<div
class="miniicons-list px-4"
v-for="menu in MiniSideIcons"
:key="menu.icon"
>
<v-btn
rounded="md"
flat
icon
variant="plain"
@click="showData(menu.id)"
:class="{ 'bg-primary opacity-1': currentMenu === menu.id }"
>
<Icon :icon="'solar:' + menu.icon" width="25" />
<!-- Tooltip on Hover -->
<v-tooltip
activator="parent"
location="end"
class="custom-tooltip"
>{{ menu.tooltip }}</v-tooltip
>
</v-btn>
</div>
</div>
</div>
</v-list-item>
<!-- </perfect-scrollbar> -->
</v-navigation-drawer>
<!-- LeftSidebar Items -->
<v-navigation-drawer
v-model="customizer.Sidebar_drawer"
elevation="0"
rail-width="1"
app
top="0"
class="leftSidebar"
:rail="customizer.mini_sidebar"
width="240"
>
<!---Logo part -->
<div class="pa-4 pb-0">
<Logo />
</div>
<!-- ---------------------------------------------- -->
<!---Navigation -->
<!-- ---------------------------------------------- -->
<perfect-scrollbar class="scrollnavbar">
<div class="px-4 py-0 sidebar-menus">
<v-list class="py-1">
<template v-for="(item, i) in sidebarMenu">
<template v-if="currentMenu == item.id">
<!---Item Sub Header -->
<LazyLayoutFullVerticalSidebarNavGroup
:item="item"
v-if="item.header"
:key="item.title"
/>
<!---If Has Child -->
<template v-for="sItem in item.children">
<LazyLayoutFullVerticalSidebarNavCollapse
class="leftPadding"
:item="sItem"
:level="0"
v-if="sItem.children"
/>
<LazyLayoutFullVerticalSidebarNavItem
:item="sItem"
class="leftPadding"
v-else
/>
</template>
</template>
</template>
</v-list>
</div>
</perfect-scrollbar>
</v-navigation-drawer>
</template>

View File

@@ -0,0 +1,709 @@
export interface menu {
header?: string;
title?: string;
icon?: any;
id?: number;
to?: string;
chip?: string;
BgColor?: string;
chipBgColor?: string;
chipColor?: string;
chipVariant?: string;
chipIcon?: string;
children?: menu[];
disabled?: boolean;
type?: string;
subCaption?: string;
}
const sidebarItem: menu[] = [
{
header: 'dashboards',
id: 1,
children: [
{
title: 'Dashboard1',
icon: 'widget-add-line-duotone',
to: '/dashboards/dashboard1'
},
{
title: 'Dashboard2',
icon: 'chart-line-duotone',
to: '/dashboards/dashboard2'
},
{
title: 'Dashboard3',
icon: 'screencast-2-line-duotone',
to: '/dashboards/dashboard3'
},
{
title: 'Front Pages',
icon: 'home-angle-linear',
to: '/',
children: [
{
title: 'Homepage',
to: '/front-page/homepage'
},
{
title: 'About Us',
to: '/front-page/about-us'
},
{
title: 'Blog',
to: '/front-page/blog/posts'
},
{
title: 'Blog Details',
to: '/front-page/blog/early-black-friday-amazon-deals-cheap-tvs-headphones'
},
{
title: 'Contact Us',
to: '/front-page/contact-us'
},
{
title: 'Portfolio',
to: '/front-page/portfolio'
},
{
title: 'Pricing',
to: '/front-page/pricing'
}
]
},
]
},
{
header: 'apps',
id: 1,
children: [
{
title: 'ECommerce',
icon: 'cart-3-line-duotone',
to: '/ecommerce/',
children: [
{
title: 'Shop',
to: '/ecommerce/products'
},
{
title: 'Detail',
to: '/ecommerce/product/detail/1'
},
{
title: 'List',
to: '/ecommerce/productlist'
},
{
title: 'Checkout',
to: '/ecommerce/checkout'
},
{
title: 'Add Product',
to: '/ecommerce/add-product'
},
{
title: 'Edit Product',
to: '/ecommerce/edit-product'
}
]
},
{
title: 'Blog',
icon: 'widget-4-line-duotone',
to: '/',
children: [
{
title: 'Blog Posts',
to: '/apps/blog/posts'
},
{
title: 'Blog Details',
to: '/apps/blog/early-black-friday-amazon-deals-cheap-tvs-headphones'
}
]
},
{
title: 'User Profile',
icon: 'shield-user-line-duotone',
to: '/',
children: [
{
title: 'Profile',
to: '/apps/user/profile'
},
{
title: 'Followers',
to: '/apps/user/profile/followers'
},
{
title: 'Friends',
to: '/apps/user/profile/friends'
},
{
title: 'Gallery',
to: '/apps/user/profile/gallery'
}
]
},
{
title: 'Invoice',
icon: 'bill-check-outline',
to: '/',
children: [
{
title: 'List',
to: '/apps/invoice'
},
{
title: 'Details',
to: '/apps/invoice/details/102'
},
{
title: 'Create',
to: '/apps/invoice/create'
},
{
title: 'Edit',
to: '/apps/invoice/edit/102'
}
]
},
{
title: 'Calendar',
icon: 'calendar-mark-line-duotone',
to: '/apps/calendar'
},
{
title: 'Email',
icon: 'letter-linear',
to: '/apps/email'
},
{
title: 'Chats',
icon: 'chat-round-line-line-duotone',
to: '/apps/chats'
},
{
title: 'Notes',
icon: 'document-text-line-duotone',
to: '/apps/notes'
},
{
title: 'Kanban',
icon: 'airbuds-case-minimalistic-line-duotone',
to: '/apps/kanban'
},
{
title: 'Contact',
icon: 'iphone-line-duotone',
to: '/apps/contacts'
},
{
title: 'Tickets',
icon: 'ticker-star-outline',
to: '/apps/tickets'
},
]
},
{
header: 'pages',
id: 2,
children: [
{
title: 'Account Setting',
icon: 'settings-minimalistic-line-duotone',
to: '/pages/account-settings'
},
{
title: 'Banners Widgets',
icon: 'align-vertical-spacing-line-duotone',
to: '/widgets/banners'
},
{
title: 'Cards Widgets',
icon: 'cardholder-line-duotone',
to: '/widgets/cards'
},
{
title: 'Charts Widgets',
icon: 'chart-square-line-duotone',
to: '/widgets/charts'
},
{
title: 'FAQ',
icon: 'question-circle-line-duotone',
to: '/pages/faq'
},
{
title: 'Gallery Lightbox',
icon: 'gallery-bold-duotone',
to: '/pages/gallery-lightbox',
},
{
title: 'Landing Page',
icon: 'passport-line-duotone',
to: '/'
},
{
title: 'Pricing',
icon: 'dollar-line-duotone',
to: '/pages/pricing'
},
{
title: 'Search Results',
icon: 'card-search-line-duotone',
to: '/pages/search-results'
},
{
title: 'Social Contacts',
icon: 'chat-round-like-linear',
to: '/pages/social-media-contacts'
},
{
title: 'Treeview',
icon: 'transmission-line-duotone',
to: '/pages/treeview'
},
]
},
{
header: 'icons',
id: 2,
children: [
{
title: 'Tabler Icons',
icon: 'sticker-smile-circle-2-line-duotone',
to: '/icons/tabler'
},
{
title: 'Solar Icons',
icon: 'sticker-smile-circle-2-line-duotone',
to: '/icons/solar'
}
]
},
{
header: 'Forms',
id: 3,
children: [
{
title: 'Form Elements',
icon: 'text-selection-line-duotone',
to: '/components/',
children: [
{
title: 'Autocomplete',
to: '/forms/form-elements/autocomplete'
},
{
title: 'Combobox',
to: '/forms/form-elements/combobox'
},
{
title: 'Button',
to: '/forms/form-elements/button'
},
{
title: 'Checkbox',
to: '/forms/form-elements/checkbox'
},
{
title: 'Custom Inputs',
to: '/forms/form-elements/custominputs'
},
{
title: 'File Inputs',
to: '/forms/form-elements/fileinputs'
},
{
title: 'Radio',
to: '/forms/form-elements/radio'
},
{
title: 'Date Time',
to: '/forms/form-elements/date-time'
},
{
title: 'Select',
to: '/forms/form-elements/select'
},
{
title: 'Slider',
to: '/forms/form-elements/slider'
},
{
title: 'Switch',
to: '/forms/form-elements/switch'
},
{
title: 'Time Picker',
to: '/forms/form-elements/time-picker'
},
{
title: 'Stepper',
to: '/forms/form-elements/stepper'
}
]
},
{
title: 'Form Layout',
icon: 'layers-minimalistic-outline',
to: '/forms/form-layouts'
},
{
title: 'Form Horizontal',
icon: 'password-minimalistic-input-line-duotone',
to: '/forms/form-horizontal'
},
{
title: 'Form Vertical',
icon: 'slider-vertical-line-duotone',
to: '/forms/form-vertical'
},
{
title: 'Form Custom',
icon: 'clapperboard-play-outline',
to: '/forms/form-custom'
},
{
title: 'Form Validation',
icon: 'soundwave-square-line-duotone',
to: '/forms/form-validation'
},
{
title: 'Editor',
icon: 'clapperboard-edit-line-duotone',
to: '/forms/editor'
}
]
},
{
header: 'tables',
id: 4,
children: [
{
title: 'Basic Table',
icon: 'tablet-line-duotone',
to: '/tables/basic'
},
{
title: 'Dark Table',
icon: 'bedside-table-4-outline',
to: '/tables/dark'
},
{
title: 'Density Table',
icon: 'bedside-table-3-linear',
to: '/tables/density'
},
{
title: 'Fixed Header Table',
icon: 'archive-up-minimlistic-broken',
to: '/tables/fixed-header'
},
{
title: 'Height Table',
icon: 'archive-down-minimlistic-broken',
to: '/tables/height'
},
{
title: 'Editable Table',
icon: 'document-add-linear',
to: '/tables/editable'
}
]
},
{
header: 'datatables',
id: 4,
children: [
{
title: 'Basic Table',
icon: 'database-outline',
to: '/tables/datatables/basic'
},
{
title: 'Header Table',
icon: 'folder-open-broken',
to: '/tables/datatables/header'
},
{
title: 'Selection Table',
icon: 'chart-square-broken',
to: '/tables/datatables/selection'
},
{
title: 'Sorting Table',
icon: 'card-send-line-duotone',
to: '/tables/datatables/sorting'
},
{
title: 'Pagination Table',
icon: 'tag-horizontal-broken',
to: '/tables/datatables/pagination'
},
{
title: 'Filtering Table',
icon: 'tuning-square-2-line-duotone',
to: '/tables/datatables/filtering'
},
{
title: 'Grouping Table',
icon: 'tuning-square-2-line-duotone',
to: '/tables/datatables/grouping'
},
{
title: 'Table Slots',
icon: 'closet-line-duotone',
to: '/tables/datatables/slots'
},
{
title: 'CRUD Table',
icon: 'text-underline-cross-broken',
to: '/tables/datatables/crudtable'
}
]
},
{
header: 'Charts',
id: 5,
children: [
{
title: 'Line',
icon: 'chat-square-2-outline',
to: '/charts/line-chart'
},
{
title: 'Gredient',
icon: 'round-graph-linear',
to: '/charts/gredient-chart'
},
{
title: 'Area',
icon: 'graph-up-linear',
to: '/charts/area-chart'
},
{
title: 'Candlestick',
icon: 'chandelier-broken',
to: '/charts/candlestick-chart'
},
{
title: 'Column',
icon: 'colour-tuneing-broken',
to: '/charts/column-chart'
},
{
title: 'Doughnut & Pie',
icon: 'pie-chart-2-linear',
to: '/charts/doughnut-pie-chart'
},
{
title: 'Radialbar & Radar',
icon: 'radar-2-outline',
to: '/charts/radialbar-chart'
}
]
},
{
header: 'UI',
id: 6,
children: [
{
title: 'Alert',
icon: 'info-circle-linear',
to: '/ui-components/alert'
},
{
title: 'Accordion',
icon: 'waterdrops-line-duotone',
to: '/ui-components/accordion'
},
{
title: 'Avatar',
icon: 'expressionless-circle-outline',
to: '/ui-components/avatar'
},
{
title: 'Chip',
icon: 'tag-horizontal-line-duotone',
to: '/ui-components/chip'
},
{
title: 'Dialog',
icon: 'bolt-line-duotone',
to: '/ui-components/dialogs'
},
{
title: 'List',
icon: 'checklist-bold-duotone',
to: '/ui-components/list'
},
{
title: 'Menus',
icon: 'menu-dots-circle-outline',
to: '/ui-components/menus'
},
{
title: 'Rating',
icon: 'shield-star-outline',
to: '/ui-components/rating'
},
{
title: 'Tabs',
icon: 'box-minimalistic-line-duotone',
to: '/ui-components/tabs'
},
{
title: 'Tooltip',
icon: 'transmission-square-outline',
to: '/ui-components/tooltip'
},
{
title: 'Typography',
icon: 'text-circle-linear',
to: '/ui-components/typography'
}
]
},
{
header: 'Auth',
id: 7,
children: [
{
title: 'Error',
icon: 'bug-minimalistic-line-duotone',
to: '/auth/404'
},
{
title: 'Side Login',
icon: 'login-3-line-duotone',
to: '/auth/login'
},
{
title: 'Boxed Login',
icon: 'login-3-line-duotone',
to: '/auth/login2'
},
{
title: 'Side Register',
icon: 'user-plus-rounded-line-duotone',
to: '/auth/register'
},
{
title: 'Boxed Register',
icon: 'user-plus-rounded-line-duotone',
to: '/auth/register2'
},
{
title: 'Side Forgot Pwd',
icon: 'password-outline',
to: '/auth/forgot-password'
},
{
title: 'Boxed Forgot Pwd',
icon: 'password-outline',
to: '/auth/forgot-password2'
},
{
title: 'Side Two Steps',
icon: 'siderbar-line-duotone',
to: '/auth/two-step'
},
{
title: 'Boxed Two Steps',
icon: 'siderbar-line-duotone',
to: '/auth/two-step2'
},
{
title: 'Maintenance',
icon: 'settings-line-duotone',
to: '/auth/maintenance'
}
]
},
{
header: 'Multi Level',
id: 8,
children: [
{
title: 'Menu Level',
icon: 'align-left-line-duotone',
to: '#',
id: 8,
children: [
{
title: 'Level 1',
to: '/level1'
},
{
title: 'Level 1 ',
to: '/2level',
children: [
{
title: 'Level 2',
to: '/barry'
},
{
title: 'Level 2',
to: '/2.2level',
children: [
{
title: 'Level 3',
to: '/barry'
}
]
}
]
}
]
}
]
},
{
header: 'More Options',
id: 8,
children: [
{
title: 'Applications',
icon: 'check-circle-bold',
BgColor: 'primary'
},
{
title: 'Form Options',
icon: 'check-circle-bold',
BgColor: 'secondary'
},
{
title: 'Table Variations',
icon: 'check-circle-bold',
BgColor: 'error'
},
{
title: 'Charts Selection',
icon: 'check-circle-bold',
BgColor: 'warning'
},
{
title: 'Widgets',
icon: 'check-circle-bold',
BgColor: 'success'
}
]
}
];
export default sidebarItem;

View File

@@ -0,0 +1,68 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useDisplay } from 'vuetify';
const { xs, lgAndUp } = useDisplay();
const sDrawer = ref(false);
</script>
<template>
<!---/Left chat list -->
<div class="d-flex mainbox rounded-md">
<div class="left-part" v-if="lgAndUp">
<!-- <perfect-scrollbar style="height: calc(100vh - 290px)"> -->
<slot name="leftpart"></slot>
<!-- </perfect-scrollbar> -->
</div>
<!---right chat conversation -->
<div class="right-part">
<!---Toggle Button For mobile-->
<v-btn block @click="sDrawer = !sDrawer" variant="text" class="d-lg-none d-md-flex d-sm-flex">
<Menu2Icon size="20" class="mr-2" /> Menu
</v-btn>
<v-divider class="d-lg-none d-block" />
<slot name="rightpart"></slot>
</div>
<!---right chat conversation -->
</div>
<v-navigation-drawer temporary v-model="sDrawer" width="320" top v-if="!lgAndUp">
<v-card-text class="pa-0">
<slot name="mobileLeftContent"></slot>
</v-card-text>
</v-navigation-drawer>
</template>
<style lang="scss">
.mainbox {
position: relative;
overflow: hidden;
}
.left-part {
width: 320px;
border-inline-end-width: thin !important;
border-inline-end-style: solid !important;
border-inline-end-color: rgba(var(--v-border-color), var(--v-border-opacity)) !important;
min-height: 500px;
transition: 0.1s ease-in;
flex-shrink: 0;
}
.v-theme--light {
.left-part {
background: white;
}
}
.v-theme--dark {
.left-part {
background: #2b2b2b;
}
}
.right-part{
width: 100%;
}
</style>

View File

@@ -0,0 +1,62 @@
<script setup lang="ts">
import { ref } from "vue";
import { useDisplay } from "vuetify";
const { xs, lgAndUp } = useDisplay();
const sDrawer = ref(false); // Sidebar drawer for mobile
const eDrawer = ref(false); // Email details drawer
const selectedEmail = ref(null); // To store the selected email details
// Method to select an email and open the email details drawer
const openEmailDetails = (email:any) => {
selectedEmail.value = email; // Store the selected email
eDrawer.value = true; // Open the email details drawer
};
</script>
<template>
<div class="d-flex mainbox">
<div class="compose pa-6 " v-if="lgAndUp">
<slot name="mailCompose"></slot>
</div>
<div class="mail-list">
<v-divider class="d-lg-none d-block" />
<slot name="mailList" :openEmailDetails="openEmailDetails"></slot> <!-- Pass the method as a slot prop -->
</div>
<div class="mail-details pa-6 d-md-block d-none">
<slot name="mailDetail"></slot>
</div>
</div>
</template>
<style lang="scss">
.compose {
max-width: 235px;
width: 100%;
}
.mail-list {
max-width: 340px;
width: 100%;
}
.mail-details {
width: 100%;
}
@media screen and (max-width: 991px) {
.mail-list {
max-width: 100%;
}
}
@media screen and (min-width: 991px) {
.compose,.mail-list{
border-inline-end-width: thin !important;
border-inline-end-style: solid !important;
border-inline-end-color: rgba(var(--v-border-color), var(--v-border-opacity)) !important;
}
}
</style>

View File

@@ -0,0 +1,43 @@
<script setup >
import { Icon } from '@iconify/vue';
const props = defineProps({
title: String,
breadcrumbs: Array ,
icon: String,
text: String
});
</script>
<template>
<v-card elevation="10" class="mb-8">
<div class="px-6 py-4">
<div class="d-flex justify-space-between align-center">
<h5 class="text-h5">{{ title }}</h5>
<v-breadcrumbs :items="breadcrumbs" class="pa-0">
<template v-slot:prepend>
<router-link to="/" class="textSecondary lh-0">
<Icon icon="solar:home-2-line-duotone" height="20" />
</router-link>
</template>
<template v-slot:divider>
<div class="d-flex align-center textSecondary "></div>
</template>
<template v-slot:title="{ item }">
<v-chip size="small" class="rounded-sm" color="primary" >{{ item.text }}</v-chip>
</template>
</v-breadcrumbs>
</div>
</div>
</v-card>
</template>
<style lang="scss">
.page-breadcrumb {
.v-toolbar {
background: transparent;
}
}
</style>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
const props = defineProps({
title: String
});
</script>
<template>
<!-- -------------------------------------------------------------------- -->
<!-- Card with Header & Footer -->
<!-- -------------------------------------------------------------------- -->
<v-card variant="outlined" elevation="0" >
<v-card-item>
<v-card-title class="text-18">{{ title }}</v-card-title>
</v-card-item>
<v-divider></v-divider>
<v-card-text>
<slot />
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<slot name="footer" />
</v-card-actions>
</v-card>
</template>

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
const props = defineProps({
title: String
});
</script>
<template>
<v-card variant="outlined">
<v-card-item class="py-4 px-6">
<v-card-title class="text-h5 mb-0">{{ title }}</v-card-title>
</v-card-item>
<v-divider />
<v-card-text>
<slot />
</v-card-text>
</v-card>
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
const props = defineProps({
title: String
});
</script>
// ===============================|| Ui Parent Card||=============================== //
<template>
<v-card elevation="10" >
<v-card-item class="py-4 px-6">
<div class="d-sm-flex align-center justify-space-between">
<v-card-title class="text-h5 mb-0">{{ title }}</v-card-title>
<slot name="action"></slot>
<!-- </template> -->
</div>
</v-card-item>
<v-divider></v-divider>
<v-card-text>
<slot />
</v-card-text>
</v-card>
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import { ref } from 'vue';
import Logo from "@/layouts/full/logo/Logo.vue";
</script>
// ===============================|| Ui Parent Card||=============================== //
<template>
<v-card elevation="10" >
<v-card-item>
<div class="d-sm-flex align-center justify-space-between">
<v-card-title class="text-h5"><Logo/></v-card-title>
<!-- <template v-slot:append> -->
<slot name="action"></slot>
<!-- </template> -->
</div>
</v-card-item>
<v-divider></v-divider>
<v-card-text>
<slot />
</v-card-text>
</v-card>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
const props = defineProps({
title: String
});
</script>
<template>
<!-- ---------------------------------------------------- -->
<!-- Table Card -->
<!-- ---------------------------------------------------- -->
<v-card variant="outlined" elevation="0" >
<v-card-item>
<v-card-title class="text-18">{{ title }}</v-card-title>
</v-card-item>
<v-divider></v-divider>
<slot />
</v-card>
</template>

View File

@@ -0,0 +1,9 @@
<script setup lang="ts">
// const props = defineProps({
// title: String,
// });
</script>
<template>
<v-text-field color="primary"><slot /></v-text-field>
</template>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
const props = defineProps({
title: String
});
</script>
<template>
<!-- -------------------------------------------------------------------- -->
<!-- Card with Header & Footer -->
<!-- -------------------------------------------------------------------- -->
<v-card variant="outlined" elevation="0" class=" mb-6 overflow-hidden">
<v-card-item>
<v-card-title class="text-18">{{ title }}</v-card-title>
</v-card-item>
<v-divider></v-divider>
<v-card-text>
<slot />
</v-card-text>
</v-card>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
const props = defineProps({
title: String,
hideaction: Boolean
});
</script>
<template>
<!-- -------------------------------------------------------------------- -->
<!-- Card with Header & Footer -->
<!-- -------------------------------------------------------------------- -->
<v-card variant="outlined" elevation="0" class=" mb-6 overflow-hidden">
<v-card-item>
<v-card-title class="text-18">{{ title }}</v-card-title>
</v-card-item>
<v-divider></v-divider>
<slot />
<v-divider></v-divider>
<v-card-actions :class="`${hideaction ? 'd-none' : ''}`">
<slot name="footer" />
</v-card-actions>
</v-card>
</template>

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
import UiParentCard from '@/components/shared/UiParentCard.vue';
</script>
<template>
<UiParentCard title="Shadow">
<v-row justify="center" class="mb-5 mt-1 px-8 px-3">
<v-col v-for="(m, n) in 25" :key="n" cols="6" sm="auto">
<v-card
:class="['d-flex justify-center align-center bg-primary py-sm-4 py-3 px-sm-8 px-4', `elevation-${n}`]">
<div>{{ n }}</div>
</v-card>
</v-col>
</v-row>
</UiParentCard>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { ref } from "vue";
const colortext = ref(
[
['Text Primary', 'text-primary text-h5','text-primary text-subtitle-1 mt-1'],
['Text Secondary', 'text-secondary text-h5','text-secondary text-subtitle-1 mt-1'],
['Text Info', 'text-info text-h5','text-info text-subtitle-1 mt-1'],
['Text Warning', 'text-warning text-h5','text-warning text-subtitle-1 mt-1'],
['Text Error', 'text-error text-h5','text-error text-subtitle-1 mt-1'],
['Text Success', 'text-success text-h5','text-success text-subtitle-1 mt-1'],
]
)
</script>
<template>
<div class="d-flex flex-column gap-1 mx-1 pa-7 pt-0 pb-0">
<div class="mb-6" v-for="[name1, cls1,cls2] in colortext" :key="name1" >
<v-card elevation="10" >
<div class="py-6 px-4">
<h5 :class="[cls1, '']">{{ name1 }} </h5>
<div :class="[cls2, '']">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos blanditiis tenetur</div>
</div>
</v-card>
</div>
</div>
</template>

View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import { ref } from "vue";
const headings = ref([
['h1.Heading', 'text-h1', 'font size: 30 | line-height: 45 | font weight: 500'],
['h2.Heading', 'text-h2', 'font size: 24 | line-height: 36 | font weight: 500'],
['h3.Heading', 'text-h3', 'font size: 21 | line-height: 31.5 | font weight: 500'],
['h4.Heading', 'text-h4', 'font size: 18 | line-height: 27 | font weight: 500'],
['h5.Heading', 'text-h5', 'font size: 16 | line-height: 24 | font weight: 500'],
['h6.Heading', 'text-h6', 'font size: 14 | line-height: 21 | font weight: 500'],
]);
const subtext = ref(
[
['Subtitle 1.', 'text-subtitle-1', ' Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos blanditiis tenetur', 'font size: 16 | line-height: 28 | font weight: 400'],
['Subtitle 2.', 'text-subtitle-2', ' Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos blanditiis tenetur', 'font size: 14 | line-height: 21 | font weight: 400'],
['Body 1.', 'text-body-1', ' Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos blanditiis tenetur', 'font size: 16 | line-height: 24 | font weight: 400'],
['Body 2.', 'text-body-2', ' Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos blanditiis tenetur', 'font size: 14 | line-height: 20 | font weight: 400'],
['Caption.', 'text-caption', ' Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos blanditiis tenetur', 'font size: 12 | line-height: 19 | font weight: 400'],
['OVERLINE.', 'text-overline letter-spacing-0', ' Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos blanditiis tenetur', 'font size: 12 | line-height: 31 | font weight: 400']
]
)
</script>
<template>
<div class="d-flex flex-column gap-1 mx-1 pa-7 pt-0 pb-0">
<div class="mb-6" v-for="[name, cls, val] in headings" :key="name">
<v-card elevation="10">
<div class="py-6 px-4">
<div :class="[cls, '']">{{ name }}</div>
<div class="text-body-1">
<div class="text-muted">{{ val }}</div>
</div>
</div>
</v-card>
</div>
</div>
<div class="d-flex flex-column gap-1 mx-1 pt-0 pa-7 pb-0">
<div class="mb-6" v-for="[name1, cls1, val1, prop] in subtext" :key="name1">
<v-card elevation="10">
<div class="py-6 px-4">
<div :class="[cls1, '']">{{ name1 }} <span>{{ val1 }}</span></div>
<div class="text-body-1 ">
<div class="text-muted">{{ prop }}</div>
</div>
</div>
</v-card>
</div>
</div>
</template>

View File

@@ -0,0 +1,6 @@
<template>
<v-alert class="mb-3 " color="error" variant="tonal">This is an error alert check it out!</v-alert>
<v-alert class="mb-3 " color="warning" variant="tonal">This is a warning alert check it out!</v-alert>
<v-alert class="mb-3 " color="info" variant="tonal">This is an info alert check it out!</v-alert>
<v-alert color="success" variant="tonal">This is a success alert check it out!</v-alert>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { ref } from 'vue';
const alert = ref(true);
</script>
<template>
<div>
<v-alert
v-model="alert"
border="start"
variant="tonal"
closable
close-label="Close Alert"
color="primary"
title="Closable Alert"
>
Aenean imperdiet. Quisque id odio. Cras dapibus. Pellentesque ut neque. Cras dapibus.
Vivamus consectetuer hendrerit lacus. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non
</v-alert>
<div
v-if="!alert"
>
<v-btn
color="primary"
@click="alert = true" flat>
Reset
</v-btn>
</div>
</div>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<div>
<v-alert class="mb-3" type="error">This is an error alert check it out!</v-alert>
<v-alert class="mb-3" type="warning">This is a warning alert check it out!</v-alert>
<v-alert class="mb-3" type="info">This is an info alert check it out!</v-alert>
<v-alert type="success">This is a success alert check it out!</v-alert>
</div>
</template>

View File

@@ -0,0 +1,10 @@
<template>
<div class="d-flex ga-3 align-center flex-column flex-wrap flex-xl-nowrap flex-sm-row fill-height d-flex justify-space-between">
<v-btn >elevates (default)</v-btn>
<v-btn variant="flat" color="primary">flat</v-btn>
<v-btn variant="tonal" color="primary">tonal</v-btn>
<v-btn variant="outlined" color="primary">outlined</v-btn>
<v-btn variant="text" color="primary">text</v-btn>
<v-btn variant="plain" color="primary">plain</v-btn>
</div>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import { ref } from 'vue';
// buttons color data
const btnsColor = ref(['primary', 'secondary', 'error', 'warning','success']);
</script>
<template>
<div class="d-flex ga-3 align-center flex-column flex-wrap flex-xl-nowrap flex-sm-row fill-height">
<v-btn v-for="btn in btnsColor" :key="btn" :color="btn" variant="flat">
{{ btn }}
</v-btn>
</div>
</template>

View File

@@ -0,0 +1,9 @@
<template>
<div class="d-flex flex-wrap justify-center ga-3 align-center flex-column flex-sm-row fill-height">
<v-btn color="primary" icon size="x-small" flat><BellIcon stroke-width="1.5" /></v-btn>
<v-btn color="secondary" icon size="small" flat><BellIcon stroke-width="1.5" /></v-btn>
<v-btn color="success" icon flat><BellIcon stroke-width="1.5" /></v-btn>
<v-btn color="error" icon size="large" flat><BellIcon stroke-width="1.5" /></v-btn>
<v-btn color="warning" icon size="x-large" flat><BellIcon stroke-width="1.5" /></v-btn>
</div>
</template>

View File

@@ -0,0 +1,11 @@
<template>
<div class="d-flex ga-3 justify-center align-center flex-column flex-wrap flex-xl-nowrap flex-sm-row fill-height">
<v-btn color="primary" variant="outlined">primary</v-btn>
<v-btn color="secondary" variant="outlined">secondary</v-btn>
<v-btn variant="flat" disabled>
Disabled
</v-btn>
<v-btn color="info" variant="outlined">link</v-btn>
</div>
</template>

View File

@@ -0,0 +1,23 @@
<template>
<div class="d-flex gap-2 justify-space-around align-center flex-column flex-md-row fill-height">
<v-btn size="x-small" color="primary" flat>
Extra small
</v-btn>
<v-btn size="small" color="primary" flat>
Small
</v-btn>
<v-btn color="primary" flat>
Normal
</v-btn>
<v-btn color="primary" size="large" flat>
Large
</v-btn>
<v-btn size="x-large" color="primary" flat>
Extra large
</v-btn>
</div>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import { ref } from 'vue';
// buttons color data
const btnsColor = ref(['primary', 'secondary', 'success', 'error', 'warning']);
</script>
<template>
<div class="d-flex flex-wrap ga-3 my-2 align-center flex-column flex-wrap flex-xl-nowrap flex-sm-row fill-height">
<v-btn v-for="btn in btnsColor" :key="btn" :color="btn" variant="text">
{{ btn }}
</v-btn>
</div>
</template>

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import { ref } from "vue";
import proimg1 from '/images/blog/blog-img1.jpg';
const messages = ref([
{
from: "You",
message: `Sure, I'll see you later.`,
time: "10:42am",
color: "primary",
},
{
from: "John Doe",
message: "Yeah, sure. Does 1:00pm work?",
time: "10:37am",
color: "secondary",
},
{
from: "You",
message: "Did you still want to grab lunch today?",
time: "9:47am",
color: "success",
},
]);
</script>
<template>
<!-- ----------------------------------------------------------------------------- -->
<!-- Content Wrap -->
<!-- ----------------------------------------------------------------------------- -->
<div class="pa-3">
<v-row justify="space-around">
<v-card elevation="0">
<v-img
height="200"
:src='proimg1'
cover
class="text-white"
>
<v-layout full-height>
<v-app-bar
density="comfortable"
color="rgba(0, 0, 0, 0)"
flat
theme="dark"
>
<template v-slot:prepend>
<v-app-bar-nav-icon></v-app-bar-nav-icon>
</template>
<v-toolbar-title class="text-subtitle-1"> Messages </v-toolbar-title>
<template v-slot:append>
<v-icon icon="mdi-dots-vertical"></v-icon>
</template>
</v-app-bar>
</v-layout>
</v-img>
<v-card-text>
<div class="font-weight-bold ml-1 mb-2">Today</div>
<v-timeline density="compact">
<v-timeline-item
v-for="message in messages"
:key="message.time"
:dot-color="message.color"
size="x-small"
>
<div class="mb-4">
<div class="font-weight-normal">
<strong>{{ message.from }}</strong> @{{ message.time }}
</div>
<div>{{ message.message }}</div>
</div>
</v-timeline-item>
</v-timeline>
</v-card-text>
</v-card>
</v-row>
</div>
</template>

View File

@@ -0,0 +1,34 @@
<script setup lang="ts">
import proimg2 from '/images/blog/blog-img3.jpg';
</script>
<template>
<!-- ----------------------------------------------------------------------------- -->
<!-- Media -->
<!-- ----------------------------------------------------------------------------- -->
<v-card elevation="0">
<v-img
class="align-end text-white"
height="200"
:src='proimg2'
cover
>
<v-card-title>Top 10 Australian beaches</v-card-title>
</v-img>
<v-card-subtitle class="pt-4"> Number 10 </v-card-subtitle>
<v-card-text>
<div>Whitehaven Beach</div>
<div>Whitsunday Island, Whitsunday Islands</div>
</v-card-text>
<v-card-actions>
<v-btn color="orange"> Share </v-btn>
<v-btn color="orange"> Explore </v-btn>
</v-card-actions>
</v-card>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts"></script>
<template>
<!-- ----------------------------------------------------------------------------- -->
<!-- Props -->
<!-- ----------------------------------------------------------------------------- -->
<v-card elevation="0"
title="This is a title"
subtitle="This is a subtitle"
text="This is content"
></v-card>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts"></script>
<template>
<!-- ----------------------------------------------------------------------------- -->
<!-- Props -->
<!-- ----------------------------------------------------------------------------- -->
<v-card elevation="0">
<template v-slot:title> This is a title </template>
<template v-slot:subtitle> This is a subtitle </template>
<template v-slot:text> This is content </template></v-card
>
</template>

View File

@@ -0,0 +1,49 @@
<script setup lang="ts"></script>
<template>
<!-- ----------------------------------------------------------------------------- -->
<!-- Twiteter -->
<!-- ----------------------------------------------------------------------------- -->
<v-card
class="mx-auto"
color="#26c6da"
theme="dark"
max-width="450"
prepend-icon="mdi-twitter"
title="Twitter"
elevation="0"
>
<template v-slot:prepend>
<v-icon size="x-large"></v-icon>
</template>
<v-card-text class="text-h5 py-2">
"Turns out semicolon-less style is easier and safer in TS because most gotcha edge cases are type invalid as well."
</v-card-text>
<v-card-actions>
<v-list-item class="w-100">
<template v-slot:prepend>
<v-avatar
color="grey-darken-3"
image="https://avataaars.io/?avatarStyle=Transparent&topType=ShortHairShortCurly&accessoriesType=Prescription02&hairColor=Black&facialHairType=Blank&clotheType=Hoodie&clotheColor=White&eyeType=Default&eyebrowType=DefaultNatural&mouthType=Default&skinColor=Light"
></v-avatar>
</template>
<v-list-item-title>Evan You</v-list-item-title>
<v-list-item-subtitle>Vue Creator</v-list-item-subtitle>
<template v-slot:append>
<div class="justify-self-end">
<v-icon class="me-1" icon="mdi-heart"></v-icon>
<span class="subheading me-2">256</span>
<span class="me-1">·</span>
<v-icon class="me-1" icon="mdi-share-variant"></v-icon>
<span class="subheading">45</span>
</div>
</template>
</v-list-item>
</v-card-actions>
</v-card>
</template>

View File

@@ -0,0 +1,115 @@
<script setup lang="ts">
import { ref } from "vue";
const labels = ref({
0: "SU",
1: "MO",
2: "TU",
3: "WED",
4: "TH",
5: "FR",
6: "SA",
});
const expand = ref(false);
const time = ref(0);
const forecast = ref([
{ day: "Tuesday", icon: "mdi-white-balance-sunny", temp: "24\xB0/12\xB0" },
{ day: "Wednesday", icon: "mdi-white-balance-sunny", temp: "22\xB0/14\xB0" },
{ day: "Thursday", icon: "mdi-cloud", temp: "25\xB0/15\xB0" },
]);
</script>
<template>
<!-- ----------------------------------------------------------------------------- -->
<!-- Weather -->
<!-- ----------------------------------------------------------------------------- -->
<v-card class="mx-auto" elevation="0">
<v-card-item title="Florida">
<template v-slot:subtitle>
<v-icon
icon="mdi-alert"
size="18"
color="error"
class="me-1 pb-1"
></v-icon>
Extreme Weather Alert
</template>
</v-card-item>
<v-card-text class="py-0">
<v-row align="center" no-gutters>
<v-col
class="text-h3"
cols="6"
>
64&deg;F
</v-col>
<v-col cols="6" class="text-right">
<v-icon
color="error"
icon="mdi-weather-hurricane"
size="40"
></v-icon>
</v-col>
</v-row>
</v-card-text>
<div class="d-flex py-3 justify-space-between">
<v-list-item
density="compact"
prepend-icon="mdi-weather-windy"
>
<v-list-item-subtitle>123 km/h</v-list-item-subtitle>
</v-list-item>
<v-list-item
density="compact"
prepend-icon="mdi-weather-pouring"
>
<v-list-item-subtitle>48%</v-list-item-subtitle>
</v-list-item>
</div>
<v-expand-transition>
<div v-if="expand">
<div class="py-2">
<v-slider
v-model="time"
:max="6"
:step="1"
:ticks="labels"
class="mx-4"
color="primary"
density="compact"
hide-details
show-ticks="always"
thumb-size="10"
></v-slider>
</div>
<v-list class="bg-transparent">
<v-list-item
v-for="item in forecast"
:key="item.day"
:title="item.day"
:append-icon="item.icon"
:subtitle="item.temp"
>
</v-list-item>
</v-list>
</div>
</v-expand-transition>
<v-divider></v-divider>
<v-card-actions>
<v-btn @click="expand = !expand">
{{ !expand ? 'Full Report' : 'Hide Report' }}
</v-btn>
</v-card-actions>
</v-card>
</template>