From d735824d5717a815256ed69cb5e115717b19e99c Mon Sep 17 00:00:00 2001 From: Fanrouver Date: Wed, 1 Oct 2025 11:28:06 +0700 Subject: [PATCH] akbar first commit --- components/AppBar.vue | 106 +++ components/HakAkses/EditHakAkses.vue | 124 +++ components/ProfilePopup.vue | 163 +++- components/ReorderMenuDialog.vue | 75 ++ components/SideBar.vue | 103 +++ composables/useAuth.ts | 135 +++ layouts/default.vue | 148 +-- layouts/empty.vue | 14 + middleware/auth.ts | 108 +++ middleware/guest.ts | 44 + nuxt.config.ts | 24 +- package-lock.json | 143 ++- package.json | 6 + pages/Dashboard.vue | 476 ++++++++++ pages/FastTrack.vue | 128 --- pages/KlinikAdmin.vue | 390 +++++--- pages/KlinikRuangAdmin.vue | 336 +++++-- pages/LoginPage.vue | 868 ++++++++++++++++++ pages/LoketAdmin.vue | 581 +++++++----- pages/RanapAdmin.vue | 175 ++++ pages/Setting/HakAkses.vue | 571 ++++++++++++ pages/Setting/MasterKlinik.vue | 491 ++++++++++ pages/Setting/MasterKlinikRuang.vue | 376 ++++++++ pages/Setting/UserLogin.vue | 496 ++++++++++ pages/index.vue | 352 ++++--- public/DSC03847-scaled.jpg | Bin 0 -> 463831 bytes ...ah_Sakit_Umum_Daerah_Dr._Saiful_Anwar.webp | Bin 0 -> 31144 bytes server/api/auth/[...].ts.backup | 25 + server/api/auth/keycloak-callback.get.ts | 141 +++ server/api/auth/keycloak-login.ts | 65 ++ server/api/auth/logout.post.ts | 74 ++ server/api/auth/session.get.ts | 95 ++ server/api/datapengunjung.ts | 0 stores/navItems.ts | 82 ++ types/auth.ts | 66 ++ 35 files changed, 6091 insertions(+), 890 deletions(-) create mode 100644 components/AppBar.vue create mode 100644 components/HakAkses/EditHakAkses.vue create mode 100644 components/ReorderMenuDialog.vue create mode 100644 components/SideBar.vue create mode 100644 composables/useAuth.ts create mode 100644 layouts/empty.vue create mode 100644 middleware/auth.ts create mode 100644 middleware/guest.ts create mode 100644 pages/Dashboard.vue delete mode 100644 pages/FastTrack.vue create mode 100644 pages/LoginPage.vue create mode 100644 pages/RanapAdmin.vue create mode 100644 pages/Setting/HakAkses.vue create mode 100644 pages/Setting/MasterKlinik.vue create mode 100644 pages/Setting/MasterKlinikRuang.vue create mode 100644 pages/Setting/UserLogin.vue create mode 100644 public/DSC03847-scaled.jpg create mode 100644 public/Rumah_Sakit_Umum_Daerah_Dr._Saiful_Anwar.webp create mode 100644 server/api/auth/[...].ts.backup create mode 100644 server/api/auth/keycloak-callback.get.ts create mode 100644 server/api/auth/keycloak-login.ts create mode 100644 server/api/auth/logout.post.ts create mode 100644 server/api/auth/session.get.ts delete mode 100644 server/api/datapengunjung.ts create mode 100644 stores/navItems.ts create mode 100644 types/auth.ts diff --git a/components/AppBar.vue b/components/AppBar.vue new file mode 100644 index 0000000..9c2dadd --- /dev/null +++ b/components/AppBar.vue @@ -0,0 +1,106 @@ + + + diff --git a/components/HakAkses/EditHakAkses.vue b/components/HakAkses/EditHakAkses.vue new file mode 100644 index 0000000..9a74e25 --- /dev/null +++ b/components/HakAkses/EditHakAkses.vue @@ -0,0 +1,124 @@ + + + + + \ No newline at end of file diff --git a/components/ProfilePopup.vue b/components/ProfilePopup.vue index 4313b54..2869f6d 100644 --- a/components/ProfilePopup.vue +++ b/components/ProfilePopup.vue @@ -2,43 +2,89 @@ - - - - - mdi-account - - - - Rajal Bayu Nogroho - Super Admin - - + +
+ + + +
+
+ {{ user?.name || user?.preferred_username || 'User' }} +
+
+ {{ user?.email || 'No email' }} +
+
+ ID: {{ user?.id?.substring(0, 8) }}... +
+
+
+ - - + + + Pengaturan Akun + + + + + Mode Gelap + + + + + + Profil Saya + + + + + - Sign out - - + + + {{ isLoggingOut ? 'Logging out...' : 'Keluar' }} + + +
@@ -46,15 +92,64 @@ +.text-red { + color: rgb(244, 67, 54) !important; +} + \ No newline at end of file diff --git a/components/ReorderMenuDialog.vue b/components/ReorderMenuDialog.vue new file mode 100644 index 0000000..4c75f83 --- /dev/null +++ b/components/ReorderMenuDialog.vue @@ -0,0 +1,75 @@ + \ No newline at end of file diff --git a/components/SideBar.vue b/components/SideBar.vue new file mode 100644 index 0000000..5098f72 --- /dev/null +++ b/components/SideBar.vue @@ -0,0 +1,103 @@ + + + + + + \ No newline at end of file diff --git a/composables/useAuth.ts b/composables/useAuth.ts new file mode 100644 index 0000000..42594d9 --- /dev/null +++ b/composables/useAuth.ts @@ -0,0 +1,135 @@ +// composables/useAuth.ts - Enhanced version with better error handling +import type { User, SessionResponse, LoginResponse, LogoutResponse } from '~/types/auth' + +export const useAuth = () => { + const user = ref(null) + const isLoading = ref(false) + const error = ref(null) + const isAuthenticated = computed(() => !!user.value) + + const clearError = () => { + error.value = null + } + + const checkAuth = async (): Promise => { + try { + isLoading.value = true + clearError() + + const response = await $fetch('/api/auth/session') + + if (response.success === false && response.error) { + error.value = response.error + user.value = null + return null + } + + user.value = response.user + return response.user + } catch (fetchError: any) { + console.error('Session check failed:', fetchError) + error.value = 'Failed to check authentication status' + user.value = null + return null + } finally { + isLoading.value = false + } + } + + const login = async (): Promise => { + try { + clearError() + const response = await $fetch('/api/auth/keycloak-login', { + method: 'POST' + }) + + if (response?.success && response?.data?.authUrl) { + console.log('๐Ÿ”— Redirecting to Keycloak login...') + window.location.href = response.data.authUrl + } else { + const errorMsg = response?.error || 'Failed to get authorization URL' + error.value = errorMsg + throw new Error(errorMsg) + } + } catch (loginError: any) { + console.error('โŒ Login error:', loginError) + error.value = loginError.message || 'Login failed' + throw loginError + } + } + + const logout = async (): Promise => { + try { + isLoading.value = true + clearError() + console.log('๐Ÿšช Starting logout process...') + + const response = await $fetch('/api/auth/logout', { + method: 'POST' + }) + + // Clear user immediately regardless of response + user.value = null + + if (response?.success && response?.logoutUrl) { + console.log('๐Ÿ”— Redirecting to Keycloak logout...') + window.location.href = response.logoutUrl + } else { + const warningMsg = response?.error || response?.message || 'No logout URL received' + console.warn('โš ๏ธ', warningMsg) + error.value = warningMsg + await navigateTo('/LoginPage') + } + } catch (logoutError: any) { + console.error('โŒ Logout error:', logoutError) + error.value = logoutError.message || 'Logout failed' + user.value = null + await navigateTo('/LoginPage') + } finally { + isLoading.value = false + } + } + + // Helper function to refresh user data + const refreshUser = async (): Promise => { + const userData = await checkAuth() + return !!userData + } + + // Helper function to check if user has specific role + const hasRole = (role: string): boolean => { + if (!user.value) return false + + // Check in roles array + if (user.value.roles?.includes(role)) return true + + // Check in realm_access.roles + if (user.value.realm_access?.roles?.includes(role)) return true + + return false + } + + // Helper function to check if user has any of the specified roles + const hasAnyRole = (roles: string[]): boolean => { + return roles.some(role => hasRole(role)) + } + + return { + // State + user: readonly(user), + isAuthenticated, + isLoading: readonly(isLoading), + error: readonly(error), + + // Actions + checkAuth, + login, + logout, + refreshUser, + clearError, + + // Utilities + hasRole, + hasAnyRole + } +} \ No newline at end of file diff --git a/layouts/default.vue b/layouts/default.vue index feabeef..93c9e38 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -1,133 +1,37 @@ + - +/* Global styles for layout */ + \ No newline at end of file diff --git a/layouts/empty.vue b/layouts/empty.vue new file mode 100644 index 0000000..5725445 --- /dev/null +++ b/layouts/empty.vue @@ -0,0 +1,14 @@ + + + diff --git a/middleware/auth.ts b/middleware/auth.ts new file mode 100644 index 0000000..a44cb7c --- /dev/null +++ b/middleware/auth.ts @@ -0,0 +1,108 @@ +// export default defineNuxtRouteMiddleware(async (to) => { +// console.log('๐Ÿ›ก๏ธ Auth middleware triggered for:', to.path) + +// // Skip middleware on server-side during build/generation +// if (process.server && process.env.NODE_ENV === 'development') { +// console.log('โญ๏ธ Skipping auth check on server-side during development') +// return +// } + +// // Allow the login page to handle its own checks +// if (to.path === '/LoginPage') { +// console.log('โญ๏ธ Allowing access to LoginPage') +// return +// } + +// // This is the crucial change: check for the authentication signal +// const isAuthRedirect = to.query.authenticated === 'true'; + +// // If this is a redirect from a successful login, we need to let the route load +// if (isAuthRedirect) { +// console.log('โณ Client-side is processing a new login session, allowing the route to load...'); +// // We navigate to a clean URL to remove the query parameter +// return navigateTo({ path: to.path, query: {} }, { replace: true }); +// } + +// try { +// console.log('๐Ÿ” Checking authentication status...') + +// const session = await $fetch<{ user: any } | null>('/api/auth/session').catch(() => null) + +// if (session && session.user) { +// console.log('โœ… User is authenticated:', session.user.name || session.user.email) +// return +// } else { +// console.log('โŒ No valid session found, redirecting to login') +// return navigateTo('/LoginPage') +// } +// } catch (error) { +// console.error('โŒ Auth middleware error:', error) +// console.log('๐Ÿ”„ Redirecting to login due to error') +// return navigateTo('/LoginPage') +// } +// }) + + +import { defineNuxtRouteMiddleware, navigateTo } from '#app'; +import type { RouteLocationNormalized } from 'vue-router'; + +// Define the shape of the user object returned by your authentication API. +// This provides type safety for session.user. +interface User { + name?: string | null; + email: string; + // Add other properties from your user object as needed. +} + +// Define the shape of the full session object returned by the API. +interface Session { + user: User; +} + +export default defineNuxtRouteMiddleware(async (to: RouteLocationNormalized) => { + console.log('๐Ÿ›ก๏ธ Auth middleware triggered for:', to.path); + + // Skip middleware on server-side during development build/generation + if (process.server && process.env.NODE_ENV === 'development') { + console.log('โญ๏ธ Skipping auth check on server-side during development'); + return; + } + + // Allow the login page to handle its own checks without redirection loops + if (to.path === '/LoginPage') { + console.log('โญ๏ธ Allowing access to LoginPage'); + return; + } + + // Check for the authentication signal from a successful login redirect + const isAuthRedirect: boolean = to.query.authenticated === 'true'; + + // If this is a redirect from a successful login, allow the route to load. + // We navigate to a clean URL to remove the query parameter. + if (isAuthRedirect) { + console.log('โณ Client-side is processing a new login session, allowing the route to load...'); + return navigateTo({ path: to.path, query: {} }, { replace: true }); + } + + try { + console.log('๐Ÿ” Checking authentication status...'); + + // Use the defined Session interface to type the fetch response + const session: Session | null = await $fetch('/api/auth/session').catch(() => null); + + // Check if a valid session and user exist using optional chaining + if (session?.user) { + console.log('โœ… User is authenticated:', session.user.name || session.user.email); + return; + } else { + console.log('โŒ No valid session found, redirecting to login'); + return navigateTo('/LoginPage'); + } + } catch (error) { + console.error('โŒ Auth middleware error:', error); + console.log('๐Ÿ”„ Redirecting to login due to error'); + return navigateTo('/LoginPage'); + } +}); + + diff --git a/middleware/guest.ts b/middleware/guest.ts new file mode 100644 index 0000000..ff7284b --- /dev/null +++ b/middleware/guest.ts @@ -0,0 +1,44 @@ +// middleware/guest.ts +export default defineNuxtRouteMiddleware(async (to) => { + console.log('๐Ÿ‘‹ Guest middleware triggered for:', to.path); + + // Skip middleware on server-side during build/generation + if (process.server && process.env.NODE_ENV === 'development') { + console.log('โญ๏ธ Skipping guest check on server-side during development'); + return; + } + + const isAuthRedirect = to.query.authenticated === 'true'; + const isLogoutSuccess = to.query.logout === 'success'; + + // If this is a logout success, allow access to login page + if (isLogoutSuccess) { + console.log('โœ… Logout success detected, allowing access to login page'); + return; + } + + // If this is a redirect from a successful login, we need to wait + if (isAuthRedirect) { + console.log('โณ Client-side is processing a new login session, waiting for session to be established...'); + // We navigate to a clean URL to remove the query parameter from the user's view + return navigateTo({ path: to.path, query: {} }, { replace: true }); + } + + try { + console.log('๐Ÿ” Checking if user is already authenticated...'); + + // The $fetch will automatically send the new user_session cookie + const session = await $fetch<{ user: any } | null>('/api/auth/session').catch(() => null); + + if (session && session.user) { + console.log('โœ… User already authenticated, redirecting to dashboard'); + return navigateTo('/dashboard'); + } else { + console.log('โ„น๏ธ No session found, staying on login page'); + return; + } + } catch (error) { + console.log('โ„น๏ธ Auth check failed (expected for login), staying on login page'); + return; + } +}); \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts index 354ca53..fdd904d 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,4 +1,6 @@ +// nuxt.config.ts import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify' + export default defineNuxtConfig({ compatibilityDate: '2025-05-15', devtools: { enabled: true }, @@ -12,6 +14,8 @@ export default defineNuxtConfig({ '@nuxt/scripts', '@nuxt/test-utils', '@nuxt/ui', + '@pinia/nuxt', + // Remove '@sidebase/nuxt-auth' completely (_options, nuxt) => { nuxt.hooks.hook('vite:extendConfig', (config) => { // @ts-expect-error @@ -19,11 +23,25 @@ export default defineNuxtConfig({ }) }, ], + + // Remove the auth configuration completely + // auth: { ... } <- Remove this entire block + + runtimeConfig: { + authSecret: process.env.NUXT_AUTH_SECRET, + keycloakClientId: process.env.KEYCLOAK_CLIENT_ID, + keycloakClientSecret: process.env.KEYCLOAK_CLIENT_SECRET, + keycloakIssuer: process.env.KEYCLOAK_ISSUER, + public: { + authUrl: process.env.AUTH_ORIGIN || 'http://localhost:3001' || 'http://localhost:3000' + } + }, + build: { - transpile: ['vuetify'] // Important for Nuxt 3 with Vuetify + transpile: ['vuetify'] }, css: [ - 'vuetify/lib/styles/main.sass', // Or 'vuetify/styles' depending on version + 'vuetify/lib/styles/main.sass', '@mdi/font/css/materialdesignicons.min.css', ], vite: { @@ -31,7 +49,7 @@ export default defineNuxtConfig({ noExternal: ['vuetify'] }, plugins: [ - vuetify({ autoImport: true }) // If using vite-plugin-vuetify + vuetify({ autoImport: true }) ] } }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6c17414..2251138 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,18 @@ "@nuxt/scripts": "^0.11.10", "@nuxt/test-utils": "^3.19.2", "@nuxt/ui": "^3.3.0", + "@pinia/nuxt": "^0.11.2", "@unhead/vue": "^2.0.13", "better-sqlite3": "^12.2.0", + "chart.js": "^4.5.0", + "dayjs": "^1.11.18", "eslint": "^9.32.0", "nuxt": "^3.17.7", + "pinia": "^3.0.3", "typescript": "^5.8.3", "vue": "^3.5.18", + "vue-chartjs": "^5.3.2", + "vue-draggable-next": "^2.3.0", "vue-router": "^4.5.1" }, "devDependencies": { @@ -1594,6 +1600,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -4497,6 +4509,21 @@ "node": ">=0.10" } }, + "node_modules/@pinia/nuxt": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@pinia/nuxt/-/nuxt-0.11.2.tgz", + "integrity": "sha512-CgvSWpbktxxWBV7ModhAcsExsQZqpPq6vMYEe9DexmmY6959ev8ukL4iFhr/qov2Nb9cQAWd7niFDnaWkN+FHg==", + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "pinia": "^3.0.3" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -7892,6 +7919,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chart.js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -8604,6 +8643,12 @@ "node": ">= 12" } }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, "node_modules/db0": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/db0/-/db0-0.3.2.tgz", @@ -8953,9 +8998,9 @@ } }, "node_modules/devalue": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz", + "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==", "license": "MIT" }, "node_modules/devlop": { @@ -10286,10 +10331,13 @@ } }, "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -14715,6 +14763,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pinia": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "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", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, "node_modules/pkg-types": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz", @@ -17158,6 +17236,13 @@ } } }, + "node_modules/sortablejs": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", + "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==", + "license": "MIT", + "peer": true + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19042,17 +19127,17 @@ } }, "node_modules/vite": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", - "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", + "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", + "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", - "tinyglobby": "^0.2.14" + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" @@ -19355,6 +19440,22 @@ "vuetify": "^3.0.0" } }, + "node_modules/vite/node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/vitest-environment-nuxt": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vitest-environment-nuxt/-/vitest-environment-nuxt-1.0.1.tgz", @@ -19400,6 +19501,16 @@ "ufo": "^1.6.1" } }, + "node_modules/vue-chartjs": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.2.tgz", + "integrity": "sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "vue": "^3.0.0-0 || ^2.7.0" + } + }, "node_modules/vue-component-type-helpers": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.0.5.tgz", @@ -19412,6 +19523,16 @@ "integrity": "sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==", "license": "MIT" }, + "node_modules/vue-draggable-next": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vue-draggable-next/-/vue-draggable-next-2.3.0.tgz", + "integrity": "sha512-ymbY0UIwfSdg0iDN/iyNNwUrTqZ/6KbPryzsvTNXBLuDCuOBdNijSK8yynNtmiSj6RapTPQfjLGQdJrZkzBd2w==", + "license": "MIT", + "peerDependencies": { + "sortablejs": "^1.14.0", + "vue": "^3.5.17" + } + }, "node_modules/vue-eslint-parser": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz", diff --git a/package.json b/package.json index b44509b..e40cd1a 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,18 @@ "@nuxt/scripts": "^0.11.10", "@nuxt/test-utils": "^3.19.2", "@nuxt/ui": "^3.3.0", + "@pinia/nuxt": "^0.11.2", "@unhead/vue": "^2.0.13", "better-sqlite3": "^12.2.0", + "chart.js": "^4.5.0", + "dayjs": "^1.11.18", "eslint": "^9.32.0", "nuxt": "^3.17.7", + "pinia": "^3.0.3", "typescript": "^5.8.3", "vue": "^3.5.18", + "vue-chartjs": "^5.3.2", + "vue-draggable-next": "^2.3.0", "vue-router": "^4.5.1" }, "devDependencies": { diff --git a/pages/Dashboard.vue b/pages/Dashboard.vue new file mode 100644 index 0000000..58adee8 --- /dev/null +++ b/pages/Dashboard.vue @@ -0,0 +1,476 @@ + + + + + \ No newline at end of file diff --git a/pages/FastTrack.vue b/pages/FastTrack.vue deleted file mode 100644 index 159e35b..0000000 --- a/pages/FastTrack.vue +++ /dev/null @@ -1,128 +0,0 @@ - - - diff --git a/pages/KlinikAdmin.vue b/pages/KlinikAdmin.vue index bf49731..ead0315 100644 --- a/pages/KlinikAdmin.vue +++ b/pages/KlinikAdmin.vue @@ -1,75 +1,93 @@ diff --git a/pages/KlinikRuangAdmin.vue b/pages/KlinikRuangAdmin.vue index c417954..fb46f9c 100644 --- a/pages/KlinikRuangAdmin.vue +++ b/pages/KlinikRuangAdmin.vue @@ -1,83 +1,121 @@ diff --git a/pages/LoginPage.vue b/pages/LoginPage.vue new file mode 100644 index 0000000..37b95fd --- /dev/null +++ b/pages/LoginPage.vue @@ -0,0 +1,868 @@ + + + + + + + \ No newline at end of file diff --git a/pages/LoketAdmin.vue b/pages/LoketAdmin.vue index 95a7921..3e6bf82 100644 --- a/pages/LoketAdmin.vue +++ b/pages/LoketAdmin.vue @@ -1,89 +1,118 @@