This commit is contained in:
2025-07-11 15:25:07 +07:00
parent a8068553be
commit 14dbe01920
13 changed files with 984 additions and 751 deletions
File diff suppressed because it is too large Load Diff
+68
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>
+62
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>
+43
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>
+2 -1
View File
@@ -7,13 +7,14 @@ const props = defineProps({
// ===============================|| Ui Parent Card||=============================== //
<template>
<v-card elevation="10" >
<v-card-item>
<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>
+34
View File
@@ -19,6 +19,7 @@
"axios-mock-adapter": "^2.1.0",
"jsonwebtoken": "^9.0.2",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1",
"sass": "1.70.0",
"sass-loader": "^16.0.5",
"typescript": "^5.8.3",
@@ -4993,6 +4994,12 @@
"callsite": "^1.0.0"
}
},
"node_modules/deep-pick-omit": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/deep-pick-omit/-/deep-pick-omit-1.2.1.tgz",
"integrity": "sha512-2J6Kc/m3irCeqVG42T+SaUMesaK7oGWaedGnQQK/+O0gYc+2SP5bKh/KKTE7d7SJ+GCA9UUE1GRzh6oDe0EnGw==",
"license": "MIT"
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -8925,6 +8932,33 @@
}
}
},
"node_modules/pinia-plugin-persistedstate": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.4.1.tgz",
"integrity": "sha512-lmuMPpXla2zJKjxEq34e1E9P9jxkWEhcVwwioCCE0izG45kkTOvQfCzvwhW3i38cvnaWC7T1eRdkd15Re59ldw==",
"license": "MIT",
"dependencies": {
"deep-pick-omit": "^1.2.1",
"defu": "^6.1.4",
"destr": "^2.0.5"
},
"peerDependencies": {
"@nuxt/kit": ">=3.0.0",
"@pinia/nuxt": ">=0.10.0",
"pinia": ">=3.0.0"
},
"peerDependenciesMeta": {
"@nuxt/kit": {
"optional": true
},
"@pinia/nuxt": {
"optional": true
},
"pinia": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/@vue/devtools-api": {
"version": "7.7.7",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
+1
View File
@@ -21,6 +21,7 @@
"axios-mock-adapter": "^2.1.0",
"jsonwebtoken": "^9.0.2",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1",
"sass": "1.70.0",
"sass-loader": "^16.0.5",
"typescript": "^5.8.3",
+2 -1
View File
@@ -15,6 +15,7 @@ definePageMeta({
});
const permissions = route.meta || []
const permStore = aksesMenu.permissionPage || []
</script>
<template>
<v-row>
@@ -22,7 +23,7 @@ const permissions = route.meta || []
<v-card elevation="10">
<v-card-item>
<h5 class="text-h5 mb-3">Sample Page {{ permissions }}</h5>
<p class="text-body-1">This is a sample page</p>
<p class="text-body-1">This is a sample page {{ permStore }}</p>
</v-card-item>
</v-card>
</v-col>
+7 -7
View File
@@ -1,15 +1,15 @@
<script setup lang="ts">
import { ref } from 'vue';
// common components
import BaseBreadcrumb from '@/components/shared/BaseBreadcrumb.vue';
import UiParentCard from '@/components/shared/UiParentCard.vue';
import BaseBreadcrumb from '~/components/shared/BaseBreadcrumb.vue';
import UiParentCard from '~/components/shared/UiParentCard.vue';
//Table
import Table1 from '@/components/table/Table1.vue';
import Table2 from '@/components/table/Table2.vue';
import Table3 from '@/components/table/Table3.vue';
import Table4 from '@/components/table/Table4.vue';
import Table5 from '@/components/table/Table5.vue';
import Table1 from '~/components/table/Table1.vue';
import Table2 from '~/components/table/Table2.vue';
import Table3 from '~/components/table/Table3.vue';
import Table4 from '~/components/table/Table4.vue';
import Table5 from '~/components/table/Table5.vue';
// template breadcrumb
const page = ref({ title: 'Basic Table' });
+1
View File
@@ -172,6 +172,7 @@ initialize()
<template>
<BaseBreadcrumb :title="page.title" :breadcrumbs="breadcrumbs"></BaseBreadcrumb>
<!-- <SharedBaseBreadcrumb :title="page.title" :breadcrumbs="breadcrumbs"></SharedBaseBreadcrumb> -->
<v-row>
<v-col cols="12">
<UiParentCard title="Crud Table">
+22 -20
View File
@@ -1,23 +1,25 @@
import { defineStore } from 'pinia';
import { defineStore } from "pinia";
// project imports
import axios from '@/utils/axios';
import axios from "@/utils/axios";
export const useContactStore = defineStore({
id: 'Contact',
state: () => ({
contacts: []
}),
getters: {},
actions: {
// Fetch followers from action
async fetchContacts() {
try {
const response = await axios.get('/api/contacts');
this.contacts = response.data.contacts;
} catch (error) {
alert(error);
console.log(error);
}
}
}
export const useContactStore = defineStore('Contact', {
state: () => ({
contacts: [],
}),
getters: {},
actions: {
// Fetch followers from action
async fetchContacts() {
try {
const response = await axios.get("/api/contacts");
this.contacts = response.data.contacts;
} catch (error) {
alert(error);
console.log(error);
}
},
},
});
+22 -2
View File
@@ -74,9 +74,29 @@ const rolePages = [
permissions: ["create", "view", "update"],
},
{
title: "Sample Editable Table",
title: "CRUD Table",
path: "/tables/datatables/crudtable",
permissions: ["view"],
},
{
title: "Basic Table",
path: "/tables/datatables/BasicTable",
permissions: ["view"],
},
{
title: "Basic Table",
permissions: ["view"],
path: "/tables/TableBasic",
},
{
title: "Editable Table",
permissions: ["view"],
path: "/tables/TableEditable",
permissions: ["create", "view", "update"],
},
{
title: "Height Table",
permissions: ["view"],
path: "/tables/tableheight",
},
],
},
+25 -24
View File
@@ -1,65 +1,66 @@
import type { SidebarItem } from '~/types/menuAkses/sidebar'
import type { PageAccess } from '~/types/menuAkses/access'
import type { SidebarItem } from "~/types/menuAkses/sidebar";
import type { PageAccess } from "~/types/menuAkses/access";
interface SidebarItemWithPermissions extends SidebarItem {
permissions: string[];
}
export function mergeSidebar(menuItems: SidebarItem[], allowedPages: PageAccess[]): SidebarItem[] {
const result: SidebarItem[] = []
export function mergeSidebar(
menuItems: SidebarItem[],
allowedPages: PageAccess[]
): SidebarItem[] {
const result: SidebarItem[] = [];
for (const item of menuItems) {
// Jika punya anak (children)
//console.log('item', item);
//console.log('allowedPages', allowedPages);
if (Array.isArray(item.children)) {
const filteredChildren = mergeSidebar(item.children, allowedPages)
const filteredChildren = mergeSidebar(item.children, allowedPages);
// console.log('ada array item', item);
//console.log('filteredChildren', filteredChildren);
//console.log('filteredChildren', filteredChildren);
if (filteredChildren.length > 0) {
result.push({
...item,
children: filteredChildren
})
children: filteredChildren,
});
}
//console.log('result', result);
}
// Jika punya path, cocokkan dengan json dummy / backend
else if (item.to) {
const matchPage = allowedPages.find(page => page.title === item.title && page.path === item.to) //cocokkan title dan path link path halaman
const matchPage = allowedPages.find(
(page) =>
page.title?.toLowerCase() === item.title?.toLowerCase() &&
page.path?.toLowerCase() === item.to?.toLowerCase()
); //cocokkan title dan path link path halaman
//console.log('match', matchPage,matchPage?.permissions);
if (matchPage && matchPage.permissions?.includes('view')) {
if (matchPage && matchPage.permissions?.includes("view")) {
result.push({
...item,
permissions: matchPage?.permissions,
} as SidebarItemWithPermissions)
} as SidebarItemWithPermissions);
}
}
}
return result
return result;
}
export function mergeSidebarIcon(staticIcon: any[], IconAccess: any[]) {
const result : any[] = []
const result: any[] = [];
for (const item of staticIcon) {
const matchIcon = IconAccess.find(icon => icon.id === item.id)
const matchIcon = IconAccess.find((icon) => icon.id === item.id);
//console.log('matchIcon', matchIcon);
if (matchIcon) {
result.push({
...item,
})
...item,
});
}
}
return result
return result;
}