update font dan color
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
:root {
|
||||
/* Neutral Colors */
|
||||
--neutral-900: #212121;
|
||||
--neutral-800: #4D4D4D;
|
||||
--neutral-700: #717171;
|
||||
--neutral-600: #89939E;
|
||||
--neutral-500: #ABBED1;
|
||||
--neutral-400: #E5F7FA;
|
||||
--neutral-300: #F5F7FA;
|
||||
--neutral-100: #FFFFFF;
|
||||
|
||||
/* Primary Colors (Orange) */
|
||||
--primary-700: #0053AD;
|
||||
--primary-600: #0663C7;
|
||||
--primary-500: #0671E0;
|
||||
--primary-400: #4196F0;
|
||||
--primary-300: #DBEDFF;
|
||||
--primary-200: #EEF5FC;
|
||||
--primary-100: #EEF5FC;
|
||||
|
||||
/* Secondary Colors (Blue) */
|
||||
--secondary-700: #0053AD;
|
||||
--secondary-600: #0671E0;
|
||||
--secondary-500: #0663C7;
|
||||
--secondary-400: #4196F0;
|
||||
--secondary-300: #DBEDFF;
|
||||
--secondary-200: #EEF5FC;
|
||||
--secondary-100: #0053AD;
|
||||
|
||||
/* Success Colors (Green) */
|
||||
--success-700: #1B6E53;
|
||||
--success-600: #009262;
|
||||
--success-500: #115B43;
|
||||
--success-400: #32C997;
|
||||
--success-300: #84DFC1;
|
||||
--success-200: #F1FBF8;
|
||||
|
||||
/* Danger Colors (Red) */
|
||||
--danger-700: #E01507;
|
||||
--danger-600: #E02B1D;
|
||||
--danger-500: #C33025;
|
||||
--danger-400: #FF5A4F;
|
||||
--danger-300: #F0857D;
|
||||
--danger-200: #FFF1F0;
|
||||
}
|
||||
|
||||
/* Utility Classes untuk Background */
|
||||
.bg-neutral-900 { background-color: var(--neutral-900); }
|
||||
.bg-neutral-800 { background-color: var(--neutral-800); }
|
||||
.bg-neutral-700 { background-color: var(--neutral-700); }
|
||||
.bg-neutral-600 { background-color: var(--neutral-600); }
|
||||
.bg-neutral-500 { background-color: var(--neutral-500); }
|
||||
.bg-neutral-400 { background-color: var(--neutral-400); }
|
||||
.bg-neutral-300 { background-color: var(--neutral-300); }
|
||||
.bg-neutral-100 { background-color: var(--neutral-100); }
|
||||
|
||||
.bg-primary-700 { background-color: var(--primary-700); }
|
||||
.bg-primary-600 { background-color: var(--primary-600); }
|
||||
.bg-primary-500 { background-color: var(--primary-500); }
|
||||
.bg-primary-400 { background-color: var(--primary-400); }
|
||||
.bg-primary-300 { background-color: var(--primary-300); }
|
||||
.bg-primary-200 { background-color: var(--primary-200); }
|
||||
.bg-primary-100 { background-color: var(--primary-100); }
|
||||
|
||||
.bg-secondary-700 { background-color: var(--secondary-700); }
|
||||
.bg-secondary-600 { background-color: var(--secondary-600); }
|
||||
.bg-secondary-500 { background-color: var(--secondary-500); }
|
||||
.bg-secondary-400 { background-color: var(--secondary-400); }
|
||||
.bg-secondary-300 { background-color: var(--secondary-300); }
|
||||
.bg-secondary-200 { background-color: var(--secondary-200); }
|
||||
|
||||
.bg-success-700 { background-color: var(--success-700); }
|
||||
.bg-success-600 { background-color: var(--success-600); }
|
||||
.bg-success-500 { background-color: var(--success-500); }
|
||||
.bg-success-400 { background-color: var(--success-400); }
|
||||
.bg-success-300 { background-color: var(--success-300); }
|
||||
.bg-success-200 { background-color: var(--success-200); }
|
||||
|
||||
.bg-danger-700 { background-color: var(--danger-700); }
|
||||
.bg-danger-600 { background-color: var(--danger-600); }
|
||||
.bg-danger-500 { background-color: var(--danger-500); }
|
||||
.bg-danger-400 { background-color: var(--danger-400); }
|
||||
.bg-danger-300 { background-color: var(--danger-300); }
|
||||
.bg-danger-200 { background-color: var(--danger-200); }
|
||||
|
||||
/* Utility Classes untuk Text Color */
|
||||
.text-neutral-900 { color: var(--neutral-900); }
|
||||
.text-neutral-800 { color: var(--neutral-800); }
|
||||
.text-neutral-700 { color: var(--neutral-700); }
|
||||
.text-neutral-600 { color: var(--neutral-600); }
|
||||
.text-neutral-500 { color: var(--neutral-500); }
|
||||
.text-neutral-100 { color: var(--neutral-100); }
|
||||
|
||||
.text-primary-700 { color: var(--primary-700); }
|
||||
.text-primary-600 { color: var(--primary-600); }
|
||||
.text-primary-500 { color: var(--primary-500); }
|
||||
.text-primary-400 { color: var(--primary-400); }
|
||||
|
||||
.text-secondary-700 { color: var(--secondary-700); }
|
||||
.text-secondary-600 { color: var(--secondary-600); }
|
||||
.text-secondary-500 { color: var(--secondary-500); }
|
||||
|
||||
.text-success-700 { color: var(--success-700); }
|
||||
.text-success-600 { color: var(--success-600); }
|
||||
.text-success-500 { color: var(--success-500); }
|
||||
|
||||
.text-danger-700 { color: var(--danger-700); }
|
||||
.text-danger-600 { color: var(--danger-600); }
|
||||
.text-danger-500 { color: var(--danger-500); }
|
||||
|
||||
/* Utility Classes untuk Border */
|
||||
.border-primary-500 { border-color: var(--primary-500); }
|
||||
.border-secondary-500 { border-color: var(--secondary-500); }
|
||||
.border-success-500 { border-color: var(--success-500); }
|
||||
.border-danger-500 { border-color: var(--danger-500); }
|
||||
.border-neutral-500 { border-color: var(--neutral-500); }
|
||||
@@ -0,0 +1,109 @@
|
||||
/* Base Font */
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/* Headlines - Desktop */
|
||||
.headline-1 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 600; /* Semi Bold */
|
||||
font-size: 64px;
|
||||
line-height: 76px;
|
||||
}
|
||||
|
||||
.headline-2 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 600; /* Semi Bold */
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.headline-3 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 600; /* Semi Bold */
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.headline-4 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 600; /* Semi Bold */
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
/* Body - Desktop */
|
||||
.body-1 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 400; /* Regular */
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.body-2 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 400; /* Regular */
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.body-3 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 400; /* Regular */
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Caption - Desktop */
|
||||
.text-1 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 400; /* Regular */
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.text-2 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 400; /* Regular */
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
/* Mobile Typography */
|
||||
@media (max-width: 768px) {
|
||||
.headline-1 {
|
||||
font-weight: 700; /* Bold */
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.headline-2 {
|
||||
font-weight: 600; /* Semi Bold */
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.headline-3 {
|
||||
font-weight: 600; /* Semi Bold */
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.headline-4 {
|
||||
font-weight: 600; /* Semi Bold */
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.body {
|
||||
font-weight: 500; /* Medium */
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.caption {
|
||||
font-weight: 400; /* Regular */
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
@use 'sass:map';
|
||||
|
||||
// Color Palette Variables
|
||||
$neutral-900: #212121;
|
||||
$neutral-800: #4D4D4D;
|
||||
$neutral-700: #717171;
|
||||
$neutral-600: #89939E;
|
||||
$neutral-500: #ABBED1;
|
||||
$neutral-400: #E5F7FA;
|
||||
$neutral-300: #F5F7FA;
|
||||
$neutral-100: #FFFFFF;
|
||||
|
||||
$primary-700: #0053AD;
|
||||
$primary-600: #0663C7;
|
||||
$primary-500: #0671E0;
|
||||
$primary-400: #4196F0;
|
||||
$primary-300: #DBEDFF;
|
||||
$primary-200: #EEF5FC;
|
||||
$primary-100: #EEF5FC;
|
||||
|
||||
$secondary-700: #0053AD;
|
||||
$secondary-600: #0671E0;
|
||||
$secondary-500: #0663C7;
|
||||
$secondary-400: #4196F0;
|
||||
$secondary-300: #DBEDFF;
|
||||
$secondary-200: #EEF5FC;
|
||||
|
||||
$success-700: #1B6E53;
|
||||
$success-600: #009262;
|
||||
$success-500: #115B43;
|
||||
$success-400: #32C997;
|
||||
$success-300: #84DFC1;
|
||||
$success-200: #F1FBF8;
|
||||
|
||||
$danger-700: #E01507;
|
||||
$danger-600: #E02B1D;
|
||||
$danger-500: #C33025;
|
||||
$danger-400: #FF5A4F;
|
||||
$danger-300: #F0857D;
|
||||
$danger-200: #FFF1F0;
|
||||
|
||||
// CSS Variables untuk dynamic theming
|
||||
:root {
|
||||
// Neutral
|
||||
--color-neutral-900: #{$neutral-900};
|
||||
--color-neutral-800: #{$neutral-800};
|
||||
--color-neutral-700: #{$neutral-700};
|
||||
--color-neutral-600: #{$neutral-600};
|
||||
--color-neutral-500: #{$neutral-500};
|
||||
--color-neutral-400: #{$neutral-400};
|
||||
--color-neutral-300: #{$neutral-300};
|
||||
--color-neutral-100: #{$neutral-100};
|
||||
|
||||
// Primary
|
||||
--color-primary-700: #{$primary-700};
|
||||
--color-primary-600: #{$primary-600};
|
||||
--color-primary-500: #{$primary-500};
|
||||
--color-primary-400: #{$primary-400};
|
||||
--color-primary-300: #{$primary-300};
|
||||
--color-primary-200: #{$primary-200};
|
||||
--color-primary-100: #{$primary-100};
|
||||
|
||||
// Secondary
|
||||
--color-secondary-700: #{$secondary-700};
|
||||
--color-secondary-600: #{$secondary-600};
|
||||
--color-secondary-500: #{$secondary-500};
|
||||
--color-secondary-400: #{$secondary-400};
|
||||
--color-secondary-300: #{$secondary-300};
|
||||
--color-secondary-200: #{$secondary-200};
|
||||
|
||||
// Success
|
||||
--color-success-700: #{$success-700};
|
||||
--color-success-600: #{$success-600};
|
||||
--color-success-500: #{$success-500};
|
||||
--color-success-400: #{$success-400};
|
||||
--color-success-300: #{$success-300};
|
||||
--color-success-200: #{$success-200};
|
||||
|
||||
// Danger
|
||||
--color-danger-700: #{$danger-700};
|
||||
--color-danger-600: #{$danger-600};
|
||||
--color-danger-500: #{$danger-500};
|
||||
--color-danger-400: #{$danger-400};
|
||||
--color-danger-300: #{$danger-300};
|
||||
--color-danger-200: #{$danger-200};
|
||||
|
||||
// Theme colors (akan di-override oleh theme system)
|
||||
--color-primary: #{$primary-500};
|
||||
--color-secondary: #{$secondary-500};
|
||||
--color-success: #{$success-600};
|
||||
--color-danger: #{$danger-600};
|
||||
--color-background: #{$neutral-300};
|
||||
--color-surface: #{$neutral-100};
|
||||
--color-textPrimary: #{$neutral-900};
|
||||
--color-textSecondary: #{$neutral-700};
|
||||
--color-borderColor: #{$neutral-500};
|
||||
}
|
||||
|
||||
// Dark mode base
|
||||
.dark {
|
||||
--color-background: #1a1a1a;
|
||||
--color-surface: #{$neutral-900};
|
||||
--color-textPrimary: #{$neutral-100};
|
||||
--color-textSecondary: #{$neutral-500};
|
||||
--color-borderColor: #{$neutral-800};
|
||||
}
|
||||
|
||||
// Mixin untuk generate color utilities
|
||||
@mixin color-utilities($prefix, $color) {
|
||||
.text-#{$prefix} {
|
||||
color: $color;
|
||||
}
|
||||
|
||||
.bg-#{$prefix} {
|
||||
background-color: $color;
|
||||
}
|
||||
|
||||
.border-#{$prefix} {
|
||||
border-color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate color utilities
|
||||
$colors: (
|
||||
'neutral-900': $neutral-900,
|
||||
'neutral-800': $neutral-800,
|
||||
'neutral-700': $neutral-700,
|
||||
'neutral-600': $neutral-600,
|
||||
'neutral-500': $neutral-500,
|
||||
'neutral-400': $neutral-400,
|
||||
'neutral-300': $neutral-300,
|
||||
'neutral-100': $neutral-100,
|
||||
'primary-700': $primary-700,
|
||||
'primary-600': $primary-600,
|
||||
'primary-500': $primary-500,
|
||||
'primary-400': $primary-400,
|
||||
'primary-300': $primary-300,
|
||||
'primary-200': $primary-200,
|
||||
'secondary-700': $secondary-700,
|
||||
'secondary-600': $secondary-600,
|
||||
'secondary-500': $secondary-500,
|
||||
'success-700': $success-700,
|
||||
'success-600': $success-600,
|
||||
'success-500': $success-500,
|
||||
'danger-700': $danger-700,
|
||||
'danger-600': $danger-600,
|
||||
'danger-500': $danger-500
|
||||
);
|
||||
|
||||
@each $name, $color in $colors {
|
||||
@include color-utilities($name, $color);
|
||||
}
|
||||
|
||||
// Shorthand semantic colors
|
||||
.text-primary { color: $primary-500; }
|
||||
.text-secondary { color: $secondary-500; }
|
||||
.text-success { color: $success-600; }
|
||||
.text-danger { color: $danger-600; }
|
||||
.text-muted { color: $neutral-600; }
|
||||
|
||||
.bg-primary { background-color: $primary-500; }
|
||||
.bg-secondary { background-color: $secondary-500; }
|
||||
.bg-success { background-color: $success-600; }
|
||||
.bg-danger { background-color: $danger-600; }
|
||||
.bg-light { background-color: $neutral-300; }
|
||||
.bg-white { background-color: $neutral-100; }
|
||||
@@ -0,0 +1,121 @@
|
||||
@use 'sass:map';
|
||||
@use './variables' as *;
|
||||
|
||||
// Mixin untuk generate typography classes
|
||||
@mixin typography($name, $size, $line-height, $weight) {
|
||||
.#{$name} {
|
||||
font-family: $font-family-base;
|
||||
font-size: $size;
|
||||
line-height: $line-height;
|
||||
font-weight: $weight;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Base typography
|
||||
* {
|
||||
font-family: $font-family-base;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font-family-base;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: $font-weight-regular;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Generate desktop typography classes - Gunakan map.get
|
||||
@each $name, $props in $font-sizes {
|
||||
@include typography(
|
||||
$name,
|
||||
map.get($props, 'size'),
|
||||
map.get($props, 'line-height'),
|
||||
map.get($props, 'weight')
|
||||
);
|
||||
}
|
||||
|
||||
// Generate mobile typography classes - Gunakan map.get
|
||||
@media (max-width: $breakpoint-mobile) {
|
||||
@each $name, $props in $font-sizes-mobile {
|
||||
@include typography(
|
||||
$name,
|
||||
map.get($props, 'size'),
|
||||
map.get($props, 'line-height'),
|
||||
map.get($props, 'weight')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// HTML semantic tags
|
||||
h1 {
|
||||
@extend .headline-1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@extend .headline-2;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@extend .headline-3;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@extend .headline-4;
|
||||
}
|
||||
|
||||
p {
|
||||
@extend .body-1;
|
||||
}
|
||||
|
||||
small {
|
||||
@extend .caption-2;
|
||||
}
|
||||
|
||||
// Utility classes
|
||||
.text-regular {
|
||||
font-weight: $font-weight-regular !important;
|
||||
}
|
||||
|
||||
.text-medium {
|
||||
font-weight: $font-weight-medium !important;
|
||||
}
|
||||
|
||||
.text-semibold {
|
||||
font-weight: $font-weight-semibold !important;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: $font-weight-bold !important;
|
||||
}
|
||||
|
||||
.text-uppercase {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// Line clamp utilities
|
||||
@for $i from 1 through 5 {
|
||||
.line-clamp-#{$i} {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $i;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
@use 'sass:map';
|
||||
@use 'sass:math';
|
||||
|
||||
// Font Family
|
||||
$font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
|
||||
// Font Weights
|
||||
$font-weight-regular: 400;
|
||||
$font-weight-medium: 500;
|
||||
$font-weight-semibold: 600;
|
||||
$font-weight-bold: 700;
|
||||
|
||||
// Font Sizes & Line Heights - Desktop
|
||||
$font-sizes: (
|
||||
'headline-1': (
|
||||
'size': 64px,
|
||||
'line-height': 76px,
|
||||
'weight': $font-weight-semibold
|
||||
),
|
||||
'headline-2': (
|
||||
'size': 36px,
|
||||
'line-height': 44px,
|
||||
'weight': $font-weight-semibold
|
||||
),
|
||||
'headline-3': (
|
||||
'size': 28px,
|
||||
'line-height': 36px,
|
||||
'weight': $font-weight-semibold
|
||||
),
|
||||
'headline-4': (
|
||||
'size': 20px,
|
||||
'line-height': 28px,
|
||||
'weight': $font-weight-semibold
|
||||
),
|
||||
'body-1': (
|
||||
'size': 18px,
|
||||
'line-height': 28px,
|
||||
'weight': $font-weight-regular
|
||||
),
|
||||
'body-2': (
|
||||
'size': 16px,
|
||||
'line-height': 24px,
|
||||
'weight': $font-weight-regular
|
||||
),
|
||||
'body-3': (
|
||||
'size': 14px,
|
||||
'line-height': 20px,
|
||||
'weight': $font-weight-regular
|
||||
),
|
||||
'caption-1': (
|
||||
'size': 16px,
|
||||
'line-height': 24px,
|
||||
'weight': $font-weight-regular
|
||||
),
|
||||
'caption-2': (
|
||||
'size': 12px,
|
||||
'line-height': 16px,
|
||||
'weight': $font-weight-regular
|
||||
)
|
||||
);
|
||||
|
||||
// Font Sizes - Mobile
|
||||
$font-sizes-mobile: (
|
||||
'headline-1': (
|
||||
'size': 28px,
|
||||
'line-height': 36px,
|
||||
'weight': $font-weight-bold
|
||||
),
|
||||
'headline-2': (
|
||||
'size': 20px,
|
||||
'line-height': 28px,
|
||||
'weight': $font-weight-semibold
|
||||
),
|
||||
'headline-3': (
|
||||
'size': 18px,
|
||||
'line-height': 24px,
|
||||
'weight': $font-weight-semibold
|
||||
),
|
||||
'headline-4': (
|
||||
'size': 18px,
|
||||
'line-height': 24px,
|
||||
'weight': $font-weight-semibold
|
||||
),
|
||||
'body': (
|
||||
'size': 16px,
|
||||
'line-height': 24px,
|
||||
'weight': $font-weight-medium
|
||||
),
|
||||
'caption': (
|
||||
'size': 14px,
|
||||
'line-height': 20px,
|
||||
'weight': $font-weight-regular
|
||||
)
|
||||
);
|
||||
|
||||
// Breakpoints
|
||||
$breakpoint-mobile: 768px;
|
||||
$breakpoint-tablet: 1024px;
|
||||
$breakpoint-desktop: 1280px;
|
||||
|
||||
// Mixins
|
||||
@mixin heading($level) {
|
||||
@if map.has-key($font-sizes, $level) {
|
||||
$props: map.get($font-sizes, $level);
|
||||
font-size: map.get($props, 'size');
|
||||
line-height: map.get($props, 'line-height');
|
||||
font-weight: map.get($props, 'weight');
|
||||
font-family: $font-family-base;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin responsive-text {
|
||||
@media (max-width: $breakpoint-mobile) {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Gunakan @use daripada @import
|
||||
@use './variables' as *;
|
||||
@use './colors' as *;
|
||||
@use './typography' as *;
|
||||
|
||||
// Global styles
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-textPrimary);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
// Selection color
|
||||
::selection {
|
||||
background-color: var(--color-primary-300);
|
||||
color: var(--color-primary-700);
|
||||
}
|
||||
|
||||
// Focus styles
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-primary-500);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
// Responsive images
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Links
|
||||
a {
|
||||
color: var(--color-primary-500);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary-600);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--color-primary-700);
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons base
|
||||
button {
|
||||
font-family: $font-family-base;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
outline: none;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@
|
||||
}
|
||||
|
||||
.hospital-logo {
|
||||
height: 64px;
|
||||
height: 232px;
|
||||
width: auto;
|
||||
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<v-navigation-drawer
|
||||
:model-value="drawer"
|
||||
:rail="rail"
|
||||
permanent
|
||||
app
|
||||
class="bg-white d-flex flex-column"
|
||||
@update:model-value="emit('update:drawer', $event)"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<v-list-item class="py-4 px-2" :class="{ 'text-center': rail }">
|
||||
<div class="d-flex align-center" :class="{ 'justify-center': rail }">
|
||||
<img
|
||||
src="/Antrean Logo.png"
|
||||
alt="Antrean Logo"
|
||||
style="width: 72px; height: 72px; object-fit: contain"
|
||||
/>
|
||||
<v-list-item-title
|
||||
v-if="!rail"
|
||||
class="ml-2 text-h6 font-weight-bold text-blue-grey-darken-4"
|
||||
>
|
||||
<span class="text-orange-darken-2">Antrean</span> RSSA
|
||||
</v-list-item-title>
|
||||
</div>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-list
|
||||
density="compact"
|
||||
nav
|
||||
class="mt-2 flex-grow-1"
|
||||
v-model:opened="openedGroups"
|
||||
>
|
||||
<template v-for="item in items" :key="item.name">
|
||||
<v-list-group v-if="item.children" :value="item.name" :disabled="rail">
|
||||
<template v-slot:activator="{ props: groupProps }">
|
||||
<v-list-item
|
||||
v-bind="groupProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
v-for="child in item.children"
|
||||
:key="child.name"
|
||||
:to="child.path"
|
||||
:title="child.name"
|
||||
:prepend-icon="child.icon"
|
||||
link
|
||||
class="pl-8"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<v-tooltip
|
||||
v-else
|
||||
:disabled="!rail"
|
||||
open-on-hover
|
||||
location="end"
|
||||
:text="item.name"
|
||||
>
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<v-list-item
|
||||
v-bind="tooltipProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
:to="item.path"
|
||||
link
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-list>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-list v-if="user" nav density="compact" class="pa-2 flex-shrink-0">
|
||||
<ProfilePopup :user="user" @logout="handleLogout" :rail="rail" />
|
||||
</v-list>
|
||||
<v-list v-else nav density="compact" class="flex-shrink-0">
|
||||
<v-list-item
|
||||
@click="redirectToLogin"
|
||||
prepend-icon="mdi-login"
|
||||
title="Login"
|
||||
link
|
||||
color="blue-grey-darken-4"
|
||||
></v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits, onMounted, ref, watch } from "vue";
|
||||
import { navigateTo } from "#app";
|
||||
import ProfilePopup from "./ProfilePopup.vue";
|
||||
import { useAuth } from "~/composables/useAuth";
|
||||
|
||||
const { user, logout, checkAuth } = useAuth();
|
||||
|
||||
interface NavItem {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
icon: string;
|
||||
children?: NavItem[];
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
items: {
|
||||
type: Array as () => NavItem[],
|
||||
required: true,
|
||||
},
|
||||
rail: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
drawer: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:drawer", "toggle-rail"]);
|
||||
|
||||
const openedGroups = ref<string[]>([]);
|
||||
|
||||
watch(
|
||||
() => props.rail,
|
||||
(newRailState) => {
|
||||
if (newRailState === true) {
|
||||
openedGroups.value = [];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const isHovering = ref(false);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (props.rail) {
|
||||
isHovering.value = true;
|
||||
emit("toggle-rail");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (isHovering.value) {
|
||||
isHovering.value = false;
|
||||
emit("toggle-rail");
|
||||
}
|
||||
};
|
||||
|
||||
// --- AUTH LOGIC (Kept the same) ---
|
||||
|
||||
const handleLogout = async () => {
|
||||
console.log("🚪 SideBar logout initiated...");
|
||||
try {
|
||||
await logout();
|
||||
} catch (error) {
|
||||
console.error("❌ SideBar logout error:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const redirectToLogin = () => {
|
||||
navigateTo("/LoginPage");
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await checkAuth();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* No specific overrides needed */
|
||||
</style>
|
||||
@@ -1,210 +0,0 @@
|
||||
<template>
|
||||
<v-navigation-drawer
|
||||
:model-value="drawer"
|
||||
:rail="rail"
|
||||
permanent
|
||||
app
|
||||
class="bg-white d-flex flex-column" @update:model-value="emit('update:drawer', $event)"
|
||||
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<v-list-item
|
||||
class="py-4 px-2"
|
||||
:class="{'text-center': rail}"
|
||||
>
|
||||
<div class="d-flex align-center" :class="{'justify-center': rail}">
|
||||
<v-icon color="orange-darken-2" size="36">mdi-hospital-box-outline</v-icon>
|
||||
<v-list-item-title v-if="!rail" class="ml-2 text-h6 font-weight-bold text-blue-grey-darken-4">
|
||||
<span class="text-orange-darken-2">Antrian</span> RSSA
|
||||
</v-list-item-title>
|
||||
</div>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-list density="compact" nav class="mt-2" v-model:opened="openedGroups"> <template v-for="item in items" :key="item.name">
|
||||
|
||||
<v-list-group
|
||||
v-if="item.children"
|
||||
:value="item.name"
|
||||
:disabled="rail"
|
||||
>
|
||||
<template v-slot:activator="{ props: groupProps }">
|
||||
<v-list-item
|
||||
v-bind="groupProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
=======
|
||||
<v-list
|
||||
density="compact"
|
||||
nav
|
||||
class="mt-2 flex-grow-1" v-model:opened="openedGroups"
|
||||
>
|
||||
<template v-for="item in items" :key="item.name">
|
||||
<v-list-group
|
||||
v-if="item.children"
|
||||
:value="item.name"
|
||||
:disabled="rail"
|
||||
>
|
||||
<template v-slot:activator="{ props: groupProps }">
|
||||
<v-list-item
|
||||
v-bind="groupProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
>>>>>>> d7f3240a8226d36d66c5dc319c7a665539d5d8e3
|
||||
|
||||
<v-list-item
|
||||
v-for="child in item.children"
|
||||
:key="child.name"
|
||||
:to="child.path"
|
||||
:title="child.name"
|
||||
:prepend-icon="child.icon"
|
||||
link
|
||||
class="pl-8"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<v-tooltip
|
||||
v-else
|
||||
:disabled="!rail"
|
||||
open-on-hover
|
||||
location="end"
|
||||
:text="item.name"
|
||||
>
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<v-list-item
|
||||
v-bind="tooltipProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
:to="item.path"
|
||||
link
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-list>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-list v-if="user" nav density="compact" class="pa-2 flex-shrink-0">
|
||||
<ProfilePopup
|
||||
:user="user"
|
||||
@logout="handleLogout"
|
||||
:rail="rail"
|
||||
/>
|
||||
</v-list>
|
||||
<v-list v-else nav density="compact" class="flex-shrink-0">
|
||||
<v-list-item
|
||||
@click="redirectToLogin"
|
||||
prepend-icon="mdi-login"
|
||||
title="Login"
|
||||
link
|
||||
color="blue-grey-darken-4"
|
||||
></v-list-item>
|
||||
</v-list>
|
||||
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits, onMounted, ref, watch } from 'vue'; // IMPORT WATCH
|
||||
import { navigateTo } from '#app';
|
||||
import ProfilePopup from './ProfilePopup.vue';
|
||||
import { useAuth } from '~/composables/useAuth';
|
||||
|
||||
const { user, logout, checkAuth } = useAuth();
|
||||
const route = useRoute(); // Access the current route
|
||||
|
||||
interface NavItem {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
icon: string;
|
||||
children?: NavItem[];
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
items: {
|
||||
type: Array as () => NavItem[],
|
||||
required: true,
|
||||
},
|
||||
rail: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
drawer: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:drawer', 'toggle-rail']);
|
||||
|
||||
// --- NEW STATE FOR DROPDOWN GROUPS ---
|
||||
// This holds the names of the currently open list groups.
|
||||
const openedGroups = ref<string[]>([]);
|
||||
|
||||
// --- NEW WATCHER TO CLOSE DROPDOWNS ---
|
||||
watch(() => props.rail, (newRailState) => {
|
||||
if (newRailState === true) {
|
||||
// If the sidebar is collapsing (rail is true), close all groups
|
||||
openedGroups.value = [];
|
||||
}
|
||||
});
|
||||
// ------------------------------------
|
||||
|
||||
|
||||
// Local state for hover-to-expand rail feature
|
||||
const isHovering = ref(false);
|
||||
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (props.rail) {
|
||||
isHovering.value = true;
|
||||
emit('toggle-rail');
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (isHovering.value) {
|
||||
isHovering.value = false;
|
||||
emit('toggle-rail');
|
||||
}
|
||||
};
|
||||
|
||||
// --- AUTH LOGIC ---
|
||||
|
||||
const handleLogout = async () => {
|
||||
console.log('🚪 SideBar logout initiated...');
|
||||
try {
|
||||
// Calls the external logout function (from useAuth.ts)
|
||||
await logout();
|
||||
} catch (error) {
|
||||
console.error('❌ SideBar logout error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const redirectToLogin = () => {
|
||||
navigateTo('/LoginPage');
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// Fetches real user data on mount
|
||||
await checkAuth();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* No specific overrides needed */
|
||||
</style>
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import SideBar from "../components/SideBar1.vue";
|
||||
import SideBar from "../components/SideBar.vue";
|
||||
// Ensure this path matches your store location
|
||||
import { useNavItemsStore } from '~/stores/navItems1';
|
||||
|
||||
|
||||
+49
-34
@@ -1,38 +1,39 @@
|
||||
// nuxt.config.ts
|
||||
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
||||
import vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-05-15',
|
||||
compatibilityDate: "2025-05-15",
|
||||
devtools: {
|
||||
enabled: true,
|
||||
|
||||
timeline: {
|
||||
enabled: true
|
||||
}
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
|
||||
modules: [
|
||||
'@nuxt/content',
|
||||
'@nuxt/eslint',
|
||||
'@nuxt/fonts',
|
||||
'@nuxt/icon',
|
||||
'@nuxt/image',
|
||||
'@nuxt/scripts',
|
||||
'@nuxt/test-utils',
|
||||
'@nuxt/ui',
|
||||
'@pinia/nuxt',
|
||||
|
||||
// Remove '@sidebase/nuxt-auth' completely
|
||||
"@nuxt/content",
|
||||
"@nuxt/eslint",
|
||||
"@nuxt/fonts",
|
||||
"@nuxt/icon",
|
||||
"@nuxt/image",
|
||||
"@nuxt/scripts",
|
||||
"@nuxt/test-utils",
|
||||
"@nuxt/ui",
|
||||
"@pinia/nuxt",
|
||||
"@nuxtjs/google-fonts",
|
||||
(_options, nuxt) => {
|
||||
nuxt.hooks.hook('vite:extendConfig', (config) => {
|
||||
nuxt.hooks.hook("vite:extendConfig", (config) => {
|
||||
// @ts-expect-error
|
||||
config.plugins.push(vuetify({ autoImport: true }))
|
||||
})
|
||||
config.plugins.push(vuetify({ autoImport: true }));
|
||||
});
|
||||
},
|
||||
],
|
||||
|
||||
// Remove the auth configuration completely
|
||||
// auth: { ... } <- Remove this entire block
|
||||
googleFonts: {
|
||||
families: {
|
||||
Inter: [400, 500, 600, 700],
|
||||
},
|
||||
display: "swap",
|
||||
},
|
||||
|
||||
runtimeConfig: {
|
||||
authSecret: process.env.NUXT_AUTH_SECRET,
|
||||
@@ -40,23 +41,37 @@ export default defineNuxtConfig({
|
||||
keycloakClientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
|
||||
keycloakIssuer: process.env.KEYCLOAK_ISSUER,
|
||||
public: {
|
||||
authUrl: process.env.AUTH_ORIGIN || 'http://localhost:3001'
|
||||
}
|
||||
authUrl: process.env.AUTH_ORIGIN || "http://localhost:3001",
|
||||
},
|
||||
},
|
||||
|
||||
build: {
|
||||
transpile: ['vuetify']
|
||||
transpile: ["vuetify"],
|
||||
},
|
||||
|
||||
css: [
|
||||
'vuetify/lib/styles/main.sass',
|
||||
'@mdi/font/css/materialdesignicons.min.css',
|
||||
"vuetify/lib/styles/main.sass",
|
||||
"@mdi/font/css/materialdesignicons.min.css",
|
||||
"~/assets/scss/main.scss",
|
||||
],
|
||||
|
||||
vite: {
|
||||
ssr: {
|
||||
noExternal: ['vuetify']
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler',
|
||||
// ✅ Ini akan membuat variables & colors tersedia di SEMUA component
|
||||
additionalData: `
|
||||
@use "sass:math";
|
||||
@use "sass:map";
|
||||
@use "~/assets/scss/_variables.scss" as *;
|
||||
@use "~/assets/scss/_colors.scss" as *;
|
||||
`
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
vuetify({ autoImport: true })
|
||||
]
|
||||
}
|
||||
})
|
||||
ssr: {
|
||||
noExternal: ["vuetify"],
|
||||
},
|
||||
},
|
||||
});
|
||||
Generated
+336
-4
@@ -23,6 +23,7 @@
|
||||
"nuxt": "^3.17.7",
|
||||
"nuxt-qrcode": "^0.4.8",
|
||||
"pinia": "^3.0.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vue": "^3.5.18",
|
||||
@@ -31,7 +32,9 @@
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/google-fonts": "^3.2.0",
|
||||
"@types/node": "^24.7.2",
|
||||
"sass": "^1.93.3",
|
||||
"sass-embedded": "^1.89.2",
|
||||
"vite-plugin-vuetify": "^2.1.2",
|
||||
"vuetify": "^3.9.3"
|
||||
@@ -3191,6 +3194,18 @@
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nuxtjs/google-fonts": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@nuxtjs/google-fonts/-/google-fonts-3.2.0.tgz",
|
||||
"integrity": "sha512-cGAjDJoeQ2jm6VJCo4AtSmKO6KjsbO9RSLj8q261fD0lMVNMZCxkCxBkg8L0/2Vfgp+5QBHWVXL71p1tiybJFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^3.10.3",
|
||||
"google-fonts-helper": "^3.5.0",
|
||||
"pathe": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxtjs/mdc": {
|
||||
"version": "0.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@nuxtjs/mdc/-/mdc-0.9.5.tgz",
|
||||
@@ -5444,6 +5459,16 @@
|
||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz",
|
||||
"integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-path": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz",
|
||||
@@ -7534,6 +7559,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-api": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
|
||||
@@ -8352,6 +8386,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decode-named-character-reference": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
|
||||
@@ -8536,6 +8579,12 @@
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
@@ -10127,6 +10176,19 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/google-fonts-helper": {
|
||||
"version": "3.7.3",
|
||||
"resolved": "https://registry.npmjs.org/google-fonts-helper/-/google-fonts-helper-3.7.3.tgz",
|
||||
"integrity": "sha512-dENZeK6RMvkCOFoKbLbS+pyoDgpmk4U6BYmfXnztMar2d8SjEG6valxql/Lkyi7bu5F+3FTdE+gAEYSYZ5zzMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deepmerge": "^4.3.1",
|
||||
"hookable": "^5.5.3",
|
||||
"ofetch": "^1.4.1",
|
||||
"ufo": "^1.5.4"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -13524,6 +13586,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
@@ -13811,6 +13882,15 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
@@ -14511,6 +14591,23 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode.vue": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.6.0.tgz",
|
||||
@@ -14520,6 +14617,174 @@
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qrcode/node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
||||
@@ -15027,6 +15292,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/reserved-identifiers": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz",
|
||||
@@ -15289,11 +15560,11 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz",
|
||||
"integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz",
|
||||
"integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
@@ -15368,6 +15639,27 @@
|
||||
"sass": "1.93.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-all-unknown/node_modules/sass": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz",
|
||||
"integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-arm": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.2.tgz",
|
||||
@@ -15608,6 +15900,27 @@
|
||||
"sass": "1.93.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-unknown-all/node_modules/sass": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz",
|
||||
"integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-win32-arm64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.2.tgz",
|
||||
@@ -15764,6 +16077,12 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
@@ -16930,6 +17249,13 @@
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unenv": {
|
||||
"version": "2.0.0-rc.23",
|
||||
"resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.23.tgz",
|
||||
@@ -18313,6 +18639,12 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"nuxt": "^3.17.7",
|
||||
"nuxt-qrcode": "^0.4.8",
|
||||
"pinia": "^3.0.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vue": "^3.5.18",
|
||||
@@ -36,7 +37,9 @@
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/google-fonts": "^3.2.0",
|
||||
"@types/node": "^24.7.2",
|
||||
"sass": "^1.93.3",
|
||||
"sass-embedded": "^1.89.2",
|
||||
"vite-plugin-vuetify": "^2.1.2",
|
||||
"vuetify": "^3.9.3"
|
||||
|
||||
+25
-24
@@ -9,29 +9,28 @@
|
||||
<v-icon size="32" color="white">mdi-hospital-building</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Klinik Admin</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
<h1 class="page-title">Admin Klinik</h1>
|
||||
<!-- <p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<!-- <div class="header-right">
|
||||
<v-chip color="success" variant="flat" class="mr-2">
|
||||
Total {{ totalPasien }} Pasien
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="text-primary">
|
||||
Max 150 Pasien
|
||||
</v-chip>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Combined Control Section -->
|
||||
<!-- Control Section -->
|
||||
<v-row class="mb-4">
|
||||
<!-- Left Side: Current Processing -->
|
||||
<v-col cols="12" lg="6">
|
||||
<!-- Current Patient Processing Card -->
|
||||
<v-card class="current-processing-card" elevation="2">
|
||||
<v-card-text class="pa-4">
|
||||
<!-- State when there's a patient being processed -->
|
||||
<div
|
||||
v-if="currentProcessingPatient"
|
||||
class="d-flex align-center justify-space-between"
|
||||
@@ -61,7 +60,7 @@
|
||||
@click="processPatient(currentProcessingPatient, 'check-in')"
|
||||
>
|
||||
<v-icon start>mdi-check-circle</v-icon>
|
||||
Check In
|
||||
Selesai
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
@@ -95,7 +94,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty state when no patient is being processed -->
|
||||
<!-- Empty state (tidak ada pasien) -->
|
||||
<div v-else class="text-center pa-6">
|
||||
<v-icon size="64" color="grey-lighten-1" class="mb-3">
|
||||
mdi-account-off-outline
|
||||
@@ -104,7 +103,8 @@
|
||||
Tidak Ada Pasien yang Diproses
|
||||
</div>
|
||||
<div class="text-body-2 text-grey">
|
||||
Pasien akan muncul otomatis saat check-in dari loket atau buat antrean klinik baru
|
||||
Pasien akan muncul otomatis saat check-in dari loket atau buat
|
||||
antrean klinik baru
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
@@ -155,7 +155,7 @@
|
||||
<div class="call-buttons">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callNext"
|
||||
:disabled="!nextPatient"
|
||||
@@ -164,7 +164,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(5)"
|
||||
>
|
||||
@@ -172,7 +172,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(10)"
|
||||
>
|
||||
@@ -180,7 +180,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(20)"
|
||||
>
|
||||
@@ -197,18 +197,20 @@
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-center gap-2">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
size="large"
|
||||
color="#FF9B1B"
|
||||
variant="flat"
|
||||
class="text-white"
|
||||
@click="showKlinikDialog = true"
|
||||
>
|
||||
<v-icon start>mdi-hospital-building</v-icon>
|
||||
Buat Antrean Klinik Ruang
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
variant="flat"
|
||||
size="large"
|
||||
color="#32A3FF"
|
||||
variant="flat"
|
||||
class="text-white"
|
||||
@click="openPenunjangDialog()"
|
||||
>
|
||||
<v-icon start>mdi-clipboard-pulse</v-icon>
|
||||
@@ -227,14 +229,14 @@
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
<!-- <v-btn
|
||||
size="small"
|
||||
color="info"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
</v-btn> -->
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
@@ -248,7 +250,7 @@
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Terlambat Patients Table -->
|
||||
<!-- Tabel Pasien Terlambat -->
|
||||
<v-card
|
||||
class="late-table-card mb-4"
|
||||
elevation="2"
|
||||
@@ -274,7 +276,7 @@
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Pending Patients Table -->
|
||||
<!-- Tabel Pasien Pending -->
|
||||
<v-card
|
||||
class="pending-table-card mb-4"
|
||||
elevation="2"
|
||||
@@ -465,7 +467,6 @@ import { ref } from "vue";
|
||||
import TabelData from "../components/TabelData.vue";
|
||||
import { useQueue } from "../composables/useQueue";
|
||||
|
||||
// Use queue composable with 'klinik' admin type
|
||||
const {
|
||||
snackbar,
|
||||
snackbarText,
|
||||
@@ -493,7 +494,7 @@ const {
|
||||
selectPenunjang,
|
||||
openPenunjangDialog,
|
||||
changeKlinik,
|
||||
} = useQueue('klinik');
|
||||
} = useQueue("klinik");
|
||||
|
||||
const diLoketHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
@@ -743,4 +744,4 @@ const pendingHeaders = ref([
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
+51
-29
@@ -9,18 +9,19 @@
|
||||
<v-icon size="32" color="white">mdi-view-dashboard</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Loket Admin</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
<h1 class="page-title">Admin Loket</h1>
|
||||
<p class="page-subtitle">{{ currentDate }}</p>
|
||||
<!-- <p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<!-- <div class="header-right">
|
||||
<v-chip color="success" variant="flat" class="mr-2">
|
||||
Total {{ totalPasien }} Pasien
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="text-primary">
|
||||
Max 150 Pasien
|
||||
</v-chip>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,10 +35,18 @@
|
||||
class="current-processing-card"
|
||||
elevation="2"
|
||||
>
|
||||
<v-card-text class="pa-4">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<v-card-text class="pa-4 d-flex flex-column">
|
||||
<div
|
||||
class="d-flex align-center justify-space-between"
|
||||
|
||||
>
|
||||
<div class="patient-info">
|
||||
<v-chip color="primary" variant="flat" class="mb-2">
|
||||
<v-chip
|
||||
color="primary"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
|
||||
>
|
||||
<v-icon start>mdi-account-clock</v-icon>
|
||||
SEDANG DIPROSES
|
||||
</v-chip>
|
||||
@@ -61,7 +70,7 @@
|
||||
@click="processPatient(currentProcessingPatient, 'check-in')"
|
||||
>
|
||||
<v-icon start>mdi-check-circle</v-icon>
|
||||
Check In
|
||||
Selesai
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
@@ -98,8 +107,12 @@
|
||||
</v-card>
|
||||
|
||||
<!-- Empty State -->
|
||||
<v-card v-else class="current-processing-card" elevation="2">
|
||||
<v-card-text class="pa-6 text-center">
|
||||
<v-card
|
||||
v-else
|
||||
class="current-processing-card d-flex flex-column align-center justify-center"
|
||||
elevation="2"
|
||||
>
|
||||
<v-card-text class="pa-6 text-center" >
|
||||
<v-icon size="64" color="grey-lighten-1" class="mb-3">
|
||||
mdi-account-off-outline
|
||||
</v-icon>
|
||||
@@ -157,7 +170,7 @@
|
||||
<div class="call-buttons">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callNext"
|
||||
:disabled="!nextPatient"
|
||||
@@ -166,7 +179,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(5)"
|
||||
>
|
||||
@@ -174,7 +187,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(10)"
|
||||
>
|
||||
@@ -182,7 +195,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(20)"
|
||||
>
|
||||
@@ -197,20 +210,22 @@
|
||||
|
||||
<v-row class="mb-4">
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-center gap-2">
|
||||
<div class="d-flex justify-center mx-4">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
size="large"
|
||||
color="#FF9B1B"
|
||||
variant="flat"
|
||||
class="text-white"
|
||||
@click="showKlinikDialog = true"
|
||||
>
|
||||
<v-icon start>mdi-hospital-building</v-icon>
|
||||
Buat Antrean Klinik Ruang
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
variant="flat"
|
||||
size="large"
|
||||
color="#32A3FF"
|
||||
variant="flat"
|
||||
class="text-white"
|
||||
@click="openPenunjangDialog()"
|
||||
>
|
||||
<v-icon start>mdi-clipboard-pulse</v-icon>
|
||||
@@ -229,18 +244,19 @@
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
<!-- <v-btn
|
||||
size="small"
|
||||
color="info"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
</v-btn> -->
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
color="#FF9B1B"
|
||||
variant="flat"
|
||||
class="text-white"
|
||||
@click="processPatient(item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
@@ -495,8 +511,15 @@ const {
|
||||
selectPenunjang,
|
||||
openPenunjangDialog,
|
||||
changeKlinik,
|
||||
} = useQueue('loket');
|
||||
|
||||
} = useQueue("loket");
|
||||
const currentDate = ref(
|
||||
new Date().toLocaleDateString("id-ID", {
|
||||
weekday: "long",
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})
|
||||
);
|
||||
// Headers for different tables
|
||||
const diLoketHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
@@ -539,10 +562,10 @@ const pendingHeaders = ref([
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
|
||||
box-shadow: 0 8px 32px rgba(126, 126, 126, 0.3);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -584,8 +607,6 @@ const pendingHeaders = ref([
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.current-processing-card,
|
||||
.quota-call-card,
|
||||
.main-table-card,
|
||||
.late-table-card,
|
||||
.pending-table-card {
|
||||
@@ -596,6 +617,7 @@ const pendingHeaders = ref([
|
||||
.current-processing-card {
|
||||
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
||||
border: 2px solid rgba(255, 152, 0, 0.2);
|
||||
|
||||
}
|
||||
|
||||
.quota-call-card {
|
||||
@@ -746,4 +768,4 @@ const pendingHeaders = ref([
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
+14
-14
@@ -9,18 +9,18 @@
|
||||
<v-icon size="32" color="white">mdi-clipboard-pulse</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Penunjang Admin</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
<h1 class="page-title">Admin Penunjang</h1>
|
||||
<!-- <p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<!-- <div class="header-right">
|
||||
<v-chip color="success" variant="flat" class="mr-2">
|
||||
Total {{ totalPasien }} Pasien
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="text-primary">
|
||||
Max 150 Pasien
|
||||
</v-chip>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<!-- Current Patient Processing Card -->
|
||||
<v-card class="current-processing-card" elevation="2">
|
||||
<v-card-text class="pa-4">
|
||||
<!-- State when there's a patient being processed -->
|
||||
<!-- Empty State (Tidak ada pasien yg diproses) -->
|
||||
<div
|
||||
v-if="currentProcessingPatient"
|
||||
class="d-flex align-center justify-space-between"
|
||||
@@ -61,7 +61,7 @@
|
||||
@click="processPatient(currentProcessingPatient, 'check-in')"
|
||||
>
|
||||
<v-icon start>mdi-check-circle</v-icon>
|
||||
Check In
|
||||
Selesai
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
@@ -155,7 +155,7 @@
|
||||
<div class="call-buttons">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callNext"
|
||||
:disabled="!nextPatient"
|
||||
@@ -164,7 +164,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(5)"
|
||||
>
|
||||
@@ -172,7 +172,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(10)"
|
||||
>
|
||||
@@ -180,7 +180,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(20)"
|
||||
>
|
||||
@@ -197,7 +197,7 @@
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-center">
|
||||
<v-btn
|
||||
color="secondary"
|
||||
color="#32A3FF"
|
||||
variant="flat"
|
||||
size="large"
|
||||
@click="showPenunjangDialog = true"
|
||||
@@ -219,14 +219,14 @@
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
<!-- <v-btn
|
||||
size="small"
|
||||
color="info"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
</v-btn> -->
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
@@ -476,7 +476,7 @@ const pendingHeaders = ref([
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%);
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(156, 39, 176, 0.3);
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
<template>
|
||||
<v-divider class="my-8"></v-divider>
|
||||
<v-main class="bg-grey-lighten-3">
|
||||
<!-- Konten utama dibungkus dalam div yang menyesuaikan padding kiri -->
|
||||
<div :style="contentStyle">
|
||||
<v-container fluid>
|
||||
<h1 class="text-h4">Admin Anjungan</h1>
|
||||
<v-card class="pa-5 mb-5" color="white" flat>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="4">
|
||||
<v-text-field
|
||||
label="Barcode"
|
||||
placeholder="Masukkan Barcode"
|
||||
outlined
|
||||
dense
|
||||
hide-details
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-chip color="#B71C1C" class="text-caption">
|
||||
Tekan Enter. (Barcode depan nomor selalu ada huruf lain, Ex:
|
||||
J20073010005 "Hiraukan huruf 'J' nya")
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<v-btn block color="info">Pendaftaran Online</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
|
||||
<v-divider class="my-5"></v-divider>
|
||||
|
||||
<v-card class="mb-5">
|
||||
<v-toolbar flat color="transparent" dense>
|
||||
<v-toolbar-title class="text-subtitle-1 font-weight-bold red--text">
|
||||
DATA PENGUNJUNG TERLAMBAT
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="searchLate"
|
||||
append-icon="mdi-magnify"
|
||||
label="Search"
|
||||
single-line
|
||||
hide-details
|
||||
dense
|
||||
class="mr-2"
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
<v-select
|
||||
:items="[10, 25, 50, 100]"
|
||||
label="Show"
|
||||
dense
|
||||
single-line
|
||||
hide-details
|
||||
class="shrink"
|
||||
variant="outlined"
|
||||
></v-select>
|
||||
</v-toolbar>
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="lateHeaders"
|
||||
:items="lateVisitors"
|
||||
:search="searchLate"
|
||||
no-data-text="No data available in table"
|
||||
hide-default-footer
|
||||
class="elevation-1"
|
||||
></v-data-table>
|
||||
<div class="d-flex justify-end pt-2">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="10"
|
||||
:total-visible="5"
|
||||
></v-pagination>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-divider class="my-5"></v-divider>
|
||||
|
||||
<v-card>
|
||||
<v-toolbar flat color="transparent" dense>
|
||||
<v-toolbar-title class="text-subtitle-1 font-weight-bold red--text">
|
||||
DATA PENGUNJUNG
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
append-icon="mdi-magnify"
|
||||
label="Search"
|
||||
single-line
|
||||
hide-details
|
||||
dense
|
||||
class="mr-2"
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
<v-select
|
||||
:items="[10, 25, 50, 100]"
|
||||
label="Show"
|
||||
dense
|
||||
single-line
|
||||
hide-details
|
||||
class="shrink"
|
||||
variant="outlined"
|
||||
></v-select>
|
||||
</v-toolbar>
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="visitors"
|
||||
:search="search"
|
||||
no-data-text="No data available in table"
|
||||
class="elevation-1"
|
||||
>
|
||||
<template v-slot:item.aksi="{ item }">
|
||||
<div class="d-flex flex-column">
|
||||
<v-btn small color="success" class="my-1">Tiket</v-btn>
|
||||
<v-btn small color="primary" class="my-1">Tiket Pengantar</v-btn>
|
||||
<v-btn small color="warning" class="my-1">ByPass</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</div>
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
// Definisikan props untuk menerima status 'rail' dari layout induk
|
||||
const props = defineProps({
|
||||
rail: Boolean,
|
||||
});
|
||||
|
||||
// Reactive data
|
||||
const search = ref("");
|
||||
const searchLate = ref("");
|
||||
const page = ref(1);
|
||||
|
||||
// Gaya komputasi untuk menyesuaikan padding
|
||||
const contentStyle = computed(() => {
|
||||
return {
|
||||
paddingLeft: props.rail ? '56px' : '64px',
|
||||
transition: 'padding-left 0.3s ease-in-out',
|
||||
};
|
||||
});
|
||||
|
||||
// Table headers for late visitors
|
||||
const lateHeaders = [
|
||||
{ text: 'No', value: 'no' },
|
||||
{ text: 'Barcode', value: 'barcode' },
|
||||
{ text: 'No Rekamedik', value: 'noRekamedik' },
|
||||
{ text: 'No Antrian', value: 'noAntrian' },
|
||||
{ text: 'No Antrian Klinik', value: 'noAntrianKlinik' },
|
||||
{ text: 'Shift', value: 'shift' },
|
||||
{ text: 'Pembayaran', value: 'pembayaran' },
|
||||
{ text: 'Status', value: 'status' },
|
||||
];
|
||||
|
||||
// Table headers for all visitors
|
||||
const headers = [
|
||||
{ text: 'No', value: 'no' },
|
||||
{ text: 'Barcode', value: 'barcode' },
|
||||
{ text: 'No Rekamedik', value: 'noRekamedik' },
|
||||
{ text: 'No Antrian', value: 'noAntrian' },
|
||||
{ text: 'Shift', value: 'shift' },
|
||||
{ text: 'Ket', value: 'ket' },
|
||||
{ text: 'Fast Track', value: 'fastTrack' },
|
||||
{ text: 'Pembayaran', value: 'pembayaran' },
|
||||
{ text: 'Panggil', value: 'panggil' },
|
||||
{ text: 'Aksi', value: 'aksi' },
|
||||
];
|
||||
|
||||
// Mock data for late visitors
|
||||
const lateVisitors = ref([
|
||||
{ no: 1, barcode: '250813100928', noRekamedik: 'RM001', noAntrian: 'ON1045', noAntrianKlinik: 'K1', shift: 'Shift 1', pembayaran: 'JKN', status: 'Terlambat' },
|
||||
{ no: 2, barcode: '250813100930', noRekamedik: 'RM002', noAntrian: 'GI1018', noAntrianKlinik: 'K2', shift: 'Shift 1', pembayaran: 'JKN', status: 'Terlambat' },
|
||||
{ no: 3, barcode: '250813100937', noRekamedik: 'RM003', noAntrian: 'MT1073', noAntrianKlinik: 'K3', shift: 'Shift 1', pembayaran: 'JKN', status: 'Terlambat' },
|
||||
]);
|
||||
|
||||
// Mock data for all visitors
|
||||
const visitors = ref([
|
||||
{ no: 1, barcode: '250813100928', noRekamedik: 'RM001', noAntrian: 'ON1045', shift: 'Shift 1', ket: '', fastTrack: 'Ya', pembayaran: 'JKN', panggil: 'Ya' },
|
||||
{ no: 2, barcode: '250813100930', noRekamedik: 'RM002', noAntrian: 'GI1018', shift: 'Shift 1', ket: '', fastTrack: 'Tidak', pembayaran: 'JKN', panggil: 'Tidak' },
|
||||
{ no: 3, barcode: '250813100937', noRekamedik: 'RM003', noAntrian: 'MT1073', shift: 'Shift 1', ket: '', fastTrack: 'Tidak', pembayaran: 'JKN', panggil: 'Tidak' },
|
||||
]);
|
||||
</script>
|
||||
+395
-159
@@ -36,7 +36,7 @@
|
||||
:class="{
|
||||
'clinic-card': true,
|
||||
'clinic-available': clinic.available,
|
||||
'clinic-closed': !clinic.available
|
||||
'clinic-closed': !clinic.available,
|
||||
}"
|
||||
:disabled="!clinic.available"
|
||||
@click="selectClinic(clinic)"
|
||||
@@ -50,9 +50,9 @@
|
||||
variant="flat"
|
||||
class="mr-2"
|
||||
>
|
||||
{{ clinic.available ? 'TERSEDIA' : 'TUTUP' }}
|
||||
{{ clinic.available ? "TERSEDIA" : "TUTUP" }}
|
||||
</v-chip>
|
||||
<v-chip
|
||||
<v-chip
|
||||
size="small"
|
||||
:color="clinic.available ? 'info' : 'error'"
|
||||
variant="outlined"
|
||||
@@ -73,12 +73,18 @@
|
||||
{{ clinic.name }}
|
||||
</h3>
|
||||
|
||||
<p v-if="clinic.subtitle" class="text-caption text-grey-darken-1 mb-2">
|
||||
<p
|
||||
v-if="clinic.subtitle"
|
||||
class="text-caption text-grey-darken-1 mb-2"
|
||||
>
|
||||
{{ clinic.subtitle }}
|
||||
</p>
|
||||
|
||||
<div class="shift-info">
|
||||
<span v-if="clinic.schedule" class="text-caption text-grey-darken-1">
|
||||
<span
|
||||
v-if="clinic.schedule"
|
||||
class="text-caption text-grey-darken-1"
|
||||
>
|
||||
{{ clinic.schedule }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -88,9 +94,15 @@
|
||||
</v-row>
|
||||
|
||||
<div v-if="filteredClinics.length === 0" class="text-center py-8">
|
||||
<v-icon size="64" color="grey-lighten-1">mdi-hospital-marker-outline</v-icon>
|
||||
<h3 class="text-h6 mt-4 text-grey-darken-1">Tidak ada klinik yang sesuai filter</h3>
|
||||
<p class="text-body-2 text-grey-darken-1">Coba ubah filter pencarian Anda</p>
|
||||
<v-icon size="64" color="grey-lighten-1"
|
||||
>mdi-hospital-marker-outline</v-icon
|
||||
>
|
||||
<h3 class="text-h6 mt-4 text-grey-darken-1">
|
||||
Tidak ada klinik yang sesuai filter
|
||||
</h3>
|
||||
<p class="text-body-2 text-grey-darken-1">
|
||||
Coba ubah filter pencarian Anda
|
||||
</p>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -101,23 +113,37 @@
|
||||
<v-icon class="mr-2">mdi-check-circle</v-icon>
|
||||
Konfirmasi Pilihan
|
||||
</v-card-title>
|
||||
|
||||
|
||||
<v-card-text class="pa-6" v-if="selectedClinic">
|
||||
<div class="text-center">
|
||||
<div class="mb-3">
|
||||
<v-icon :icon="selectedClinic.icon" size="48" color="primary"></v-icon>
|
||||
<v-icon
|
||||
:icon="selectedClinic.icon"
|
||||
size="48"
|
||||
color="primary"
|
||||
></v-icon>
|
||||
</div>
|
||||
<h3 class="text-h5 font-weight-bold mb-2">{{ selectedClinic.name }}</h3>
|
||||
<p v-if="selectedClinic.subtitle" class="text-body-1 text-grey-darken-1 mb-3">
|
||||
<h3 class="text-h5 font-weight-bold mb-2">
|
||||
{{ selectedClinic.name }}
|
||||
</h3>
|
||||
<p
|
||||
v-if="selectedClinic.subtitle"
|
||||
class="text-body-1 text-grey-darken-1 mb-3"
|
||||
>
|
||||
{{ selectedClinic.subtitle }}
|
||||
</p>
|
||||
|
||||
|
||||
<v-divider class="my-4"></v-divider>
|
||||
|
||||
|
||||
<div class="text-left">
|
||||
<p><strong>Shift:</strong> {{ selectedClinic.shift }}</p>
|
||||
<p v-if="selectedClinic.schedule"><strong>Jadwal:</strong> {{ selectedClinic.schedule }}</p>
|
||||
<p><strong>Status:</strong> <span class="text-success">Tersedia</span></p>
|
||||
<p v-if="selectedClinic.schedule">
|
||||
<strong>Jadwal:</strong> {{ selectedClinic.schedule }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Status:</strong>
|
||||
<span class="text-success">Tersedia</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
@@ -151,7 +177,11 @@
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-center pa-4 pt-0">
|
||||
<v-btn variant="outlined" color="grey-darken-1" @click="showVisitTypeDialog = false">
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
color="grey-darken-1"
|
||||
@click="showVisitTypeDialog = false"
|
||||
>
|
||||
Tutup
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
@@ -185,7 +215,11 @@
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-center pa-4 pt-0">
|
||||
<v-btn variant="outlined" color="grey-darken-1" @click="showPaymentTypeDialog = false">
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
color="grey-darken-1"
|
||||
@click="showPaymentTypeDialog = false"
|
||||
>
|
||||
Tutup
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
@@ -231,7 +265,11 @@
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
<v-btn @click="showBookingFormDialog = false" variant="outlined" color="grey-darken-1">
|
||||
<v-btn
|
||||
@click="showBookingFormDialog = false"
|
||||
variant="outlined"
|
||||
color="grey-darken-1"
|
||||
>
|
||||
Tutup
|
||||
</v-btn>
|
||||
<v-btn color="success" variant="flat" @click="submitBooking">
|
||||
@@ -296,7 +334,11 @@
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
<v-btn @click="showFastTrackFormDialog = false" variant="outlined" color="grey-darken-1">
|
||||
<v-btn
|
||||
@click="showFastTrackFormDialog = false"
|
||||
variant="outlined"
|
||||
color="grey-darken-1"
|
||||
>
|
||||
Tutup
|
||||
</v-btn>
|
||||
<v-btn color="success" variant="flat" @click="submitFastTrack">
|
||||
@@ -323,189 +365,383 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
|
||||
definePageMeta({
|
||||
layout: false, // Disables the layout for this specific page
|
||||
})
|
||||
layout: false,
|
||||
});
|
||||
|
||||
// Reactive data (tambahan untuk dialog baru)
|
||||
const loading = ref(false)
|
||||
const selectedStatus = ref(null)
|
||||
const searchQuery = ref('')
|
||||
const showVisitTypeDialog = ref(false) // Dialog: Pilih Kunjungan
|
||||
const showPaymentTypeDialog = ref(false) // Dialog: Jenis Pembayaran (Sekarang)
|
||||
const showBookingFormDialog = ref(false) // Dialog: Form Pesan
|
||||
const showFastTrackFormDialog = ref(false) // Dialog: Form Fast Track
|
||||
const selectedClinic = ref(null)
|
||||
const selectedVisitType = ref(null)
|
||||
const snackbar = ref(false)
|
||||
const snackbarText = ref('')
|
||||
const snackbarColor = ref('success')
|
||||
const loading = ref(false);
|
||||
const selectedStatus = ref(null);
|
||||
const searchQuery = ref("");
|
||||
const showVisitTypeDialog = ref(false); // Dialog: Pilih Kunjungan
|
||||
const showPaymentTypeDialog = ref(false); // Dialog: Jenis Pembayaran (Sekarang)
|
||||
const showBookingFormDialog = ref(false); // Dialog: Form Pesan
|
||||
const showFastTrackFormDialog = ref(false); // Dialog: Form Fast Track
|
||||
const selectedClinic = ref(null);
|
||||
const selectedVisitType = ref(null);
|
||||
const snackbar = ref(false);
|
||||
const snackbarText = ref("");
|
||||
const snackbarColor = ref("success");
|
||||
|
||||
// Form data models
|
||||
const bookingForm = ref({
|
||||
date: new Date().toISOString().substring(0, 10),
|
||||
shift: 'Shift 1',
|
||||
shift: "Shift 1",
|
||||
payment: null,
|
||||
})
|
||||
});
|
||||
const fastTrackForm = ref({
|
||||
guarantor: '',
|
||||
noRek: '',
|
||||
patientName: '',
|
||||
reason: '',
|
||||
guarantor: "",
|
||||
noRek: "",
|
||||
patientName: "",
|
||||
reason: "",
|
||||
payment: null,
|
||||
})
|
||||
});
|
||||
|
||||
const statusOptions = ["Tersedia", "Tutup"];
|
||||
|
||||
// Options (Tidak Berubah)
|
||||
const statusOptions = ['Tersedia', 'Tutup']
|
||||
|
||||
// Clinic data (Tidak Berubah)
|
||||
const clinics = ref([
|
||||
// ... data klinik ... (dibiarkan seperti semula)
|
||||
{ id: 1, name: 'ANAK', subtitle: '', icon: 'mdi-baby-face', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 2, name: 'ANESTESI', subtitle: '', icon: 'mdi-sleep', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 3, name: 'BEDAH', subtitle: '', icon: 'mdi-medical-bag', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 4, name: 'GERIATRI', subtitle: '', icon: 'mdi-account-supervisor', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 5, name: 'GIGI DAN MULUT', subtitle: '', icon: 'mdi-tooth', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 6, name: 'GIZI', subtitle: '', icon: 'mdi-food-apple', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 7, name: 'HEMATO ONKOLOGI MEDIS', subtitle: '', icon: 'mdi-water', shift: 'TUTUP', schedule: '', available: false, },
|
||||
{ id: 8, name: 'IPD (PENYAKIT DALAM)', subtitle: '', icon: 'mdi-hospital', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 9, name: 'JANTUNG (CARDIOLOGI)', subtitle: '', icon: 'mdi-heart-pulse', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 10, name: 'JIWA', subtitle: '', icon: 'mdi-brain', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 11, name: 'KANDUNGAN', subtitle: '', icon: 'mdi-human-pregnant', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 12, name: 'KEMOTERAPI', subtitle: '', icon: 'mdi-needle', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 13, name: 'KOMPLEMENTER (NYERI)', subtitle: '', icon: 'mdi-leaf', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 14, name: 'KULIT KELAMIN', subtitle: '', icon: 'mdi-hand-back-right', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 15, name: 'MATA', subtitle: '', icon: 'mdi-eye', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 16, name: 'MCU', subtitle: '', icon: 'mdi-clipboard-check', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 17, name: 'ONKOLOGI', subtitle: '', icon: 'mdi-ribbon', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 18, name: 'PARU', subtitle: '', icon: 'mdi-lungs', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 19, name: 'R. TINDAKAN (EMG, ECG, DLL)', subtitle: '', icon: 'mdi-monitor-heart-rate', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 20, name: 'RADIOTERAPI', subtitle: '', icon: 'mdi-radioactive', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 21, name: 'REHAB MEDIK', subtitle: '', icon: 'mdi-human-cane', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 22, name: 'SARAF (NEUROLOGI)', subtitle: '', icon: 'mdi-head-cog', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: false, },
|
||||
{ id: 23, name: 'THT', subtitle: '', icon: 'mdi-ear-hearing', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, }
|
||||
])
|
||||
{
|
||||
id: 1,
|
||||
name: "ANAK",
|
||||
subtitle: "",
|
||||
icon: "mdi-baby-face",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "ANESTESI",
|
||||
subtitle: "",
|
||||
icon: "mdi-sleep",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "BEDAH",
|
||||
subtitle: "",
|
||||
icon: "mdi-medical-bag",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "GERIATRI",
|
||||
subtitle: "",
|
||||
icon: "mdi-account-supervisor",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "GIGI DAN MULUT",
|
||||
subtitle: "",
|
||||
icon: "mdi-tooth",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "GIZI",
|
||||
subtitle: "",
|
||||
icon: "mdi-food-apple",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "HEMATO ONKOLOGI MEDIS",
|
||||
subtitle: "",
|
||||
icon: "mdi-water",
|
||||
shift: "TUTUP",
|
||||
schedule: "",
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "IPD (PENYAKIT DALAM)",
|
||||
subtitle: "",
|
||||
icon: "mdi-hospital",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "JANTUNG (CARDIOLOGI)",
|
||||
subtitle: "",
|
||||
icon: "mdi-heart-pulse",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "JIWA",
|
||||
subtitle: "",
|
||||
icon: "mdi-brain",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: "KANDUNGAN",
|
||||
subtitle: "",
|
||||
icon: "mdi-human-pregnant",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "KEMOTERAPI",
|
||||
subtitle: "",
|
||||
icon: "mdi-needle",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: "KOMPLEMENTER (NYERI)",
|
||||
subtitle: "",
|
||||
icon: "mdi-leaf",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: "KULIT KELAMIN",
|
||||
subtitle: "",
|
||||
icon: "mdi-hand-back-right",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: "MATA",
|
||||
subtitle: "",
|
||||
icon: "mdi-eye",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: "MCU",
|
||||
subtitle: "",
|
||||
icon: "mdi-clipboard-check",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
name: "ONKOLOGI",
|
||||
subtitle: "",
|
||||
icon: "mdi-ribbon",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: "PARU",
|
||||
subtitle: "",
|
||||
icon: "mdi-lungs",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
name: "R. TINDAKAN (EMG, ECG, DLL)",
|
||||
subtitle: "",
|
||||
icon: "mdi-monitor-heart-rate",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: "RADIOTERAPI",
|
||||
subtitle: "",
|
||||
icon: "mdi-radioactive",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
name: "REHAB MEDIK",
|
||||
subtitle: "",
|
||||
icon: "mdi-human-cane",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
name: "SARAF (NEUROLOGI)",
|
||||
subtitle: "",
|
||||
icon: "mdi-head-cog",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
name: "THT",
|
||||
subtitle: "",
|
||||
icon: "mdi-ear-hearing",
|
||||
shift: "SHIFT 1",
|
||||
schedule: "Mulai Pukul 07:00",
|
||||
available: true,
|
||||
},
|
||||
]);
|
||||
|
||||
// Computed properties (Tidak Berubah)
|
||||
const filteredClinics = computed(() => {
|
||||
let filtered = clinics.value
|
||||
let filtered = clinics.value;
|
||||
|
||||
if (selectedStatus.value) {
|
||||
const isAvailable = selectedStatus.value === 'Tersedia'
|
||||
filtered = filtered.filter(clinic => clinic.available === isAvailable)
|
||||
const isAvailable = selectedStatus.value === "Tersedia";
|
||||
filtered = filtered.filter((clinic) => clinic.available === isAvailable);
|
||||
}
|
||||
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
filtered = filtered.filter(clinic =>
|
||||
clinic.name.toLowerCase().includes(query) ||
|
||||
clinic.subtitle.toLowerCase().includes(query)
|
||||
)
|
||||
const query = searchQuery.value.toLowerCase();
|
||||
filtered = filtered.filter(
|
||||
(clinic) =>
|
||||
clinic.name.toLowerCase().includes(query) ||
|
||||
clinic.subtitle.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
return filtered;
|
||||
});
|
||||
|
||||
const totalClinics = computed(() => clinics.value.length)
|
||||
const totalClinics = computed(() => clinics.value.length);
|
||||
|
||||
// Methods
|
||||
const showSnackbar = (text, color = 'success') => {
|
||||
snackbarText.value = text
|
||||
snackbarColor.value = color
|
||||
snackbar.value = true
|
||||
}
|
||||
const showSnackbar = (text, color = "success") => {
|
||||
snackbarText.value = text;
|
||||
snackbarColor.value = color;
|
||||
snackbar.value = true;
|
||||
};
|
||||
|
||||
// 1. Memilih Klinik
|
||||
const selectClinic = (clinic) => {
|
||||
if (clinic.available) {
|
||||
selectedClinic.value = clinic
|
||||
showVisitTypeDialog.value = true // Tampilkan dialog Pilih Kunjungan
|
||||
selectedClinic.value = clinic;
|
||||
showVisitTypeDialog.value = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 2. Memilih Jenis Kunjungan (Sekarang, Pesan, Fast Track)
|
||||
const selectVisitType = (type) => {
|
||||
selectedVisitType.value = type
|
||||
showVisitTypeDialog.value = false // Tutup dialog jenis kunjungan
|
||||
selectedVisitType.value = type;
|
||||
showVisitTypeDialog.value = false;
|
||||
|
||||
if (type === 'SEKARANG') {
|
||||
showPaymentTypeDialog.value = true // Lanjut ke dialog Jenis Pembayaran
|
||||
} else if (type === 'PESAN') {
|
||||
// Reset form booking sebelum ditampilkan
|
||||
if (type === "SEKARANG") {
|
||||
showPaymentTypeDialog.value = true;
|
||||
} else if (type === "PESAN") {
|
||||
bookingForm.value = {
|
||||
date: new Date().toISOString().substring(0, 10),
|
||||
shift: 'Shift 1',
|
||||
shift: "Shift 1",
|
||||
payment: null,
|
||||
}
|
||||
showBookingFormDialog.value = true // Tampilkan form Pesan
|
||||
} else if (type === 'FAST TRACK') {
|
||||
// Reset form fast track sebelum ditampilkan
|
||||
};
|
||||
showBookingFormDialog.value = true;
|
||||
} else if (type === "FAST TRACK") {
|
||||
fastTrackForm.value = {
|
||||
guarantor: '',
|
||||
noRek: '',
|
||||
patientName: '',
|
||||
reason: '',
|
||||
payment: null,
|
||||
}
|
||||
showFastTrackFormDialog.value = true // Tampilkan form Fast Track
|
||||
guarantor: "",
|
||||
noRek: "",
|
||||
patientName: "",
|
||||
reason: "",
|
||||
payment: null,
|
||||
};
|
||||
showFastTrackFormDialog.value = true; // Tampilkan form Fast Track
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 3. Memilih Jenis Pembayaran (untuk SEKARANG)
|
||||
const selectPaymentType = (paymentType) => {
|
||||
showPaymentTypeDialog.value = false
|
||||
const message = `Pendaftaran **${selectedClinic.value.name}** untuk kunjungan **SEKARANG** dengan pembayaran **${paymentType}** berhasil diproses.`
|
||||
showSnackbar(message, 'success')
|
||||
console.log('Proceeding to registration for:', selectedClinic.value.name, 'Type: SEKARANG', 'Payment:', paymentType)
|
||||
selectedClinic.value = null; // Reset
|
||||
}
|
||||
showPaymentTypeDialog.value = false;
|
||||
const message = `Pendaftaran **${selectedClinic.value.name}** untuk kunjungan **SEKARANG** dengan pembayaran **${paymentType}** berhasil diproses.`;
|
||||
showSnackbar(message, "success");
|
||||
console.log(
|
||||
"Proceeding to registration for:",
|
||||
selectedClinic.value.name,
|
||||
"Type: SEKARANG",
|
||||
"Payment:",
|
||||
paymentType
|
||||
);
|
||||
selectedClinic.value = null;
|
||||
};
|
||||
|
||||
// 4. Submit Form Pesan
|
||||
const submitBooking = () => {
|
||||
if (!bookingForm.value.date || !bookingForm.value.shift || !bookingForm.value.payment) {
|
||||
showSnackbar('Mohon lengkapi semua field Pemesanan.', 'error')
|
||||
return
|
||||
if (
|
||||
!bookingForm.value.date ||
|
||||
!bookingForm.value.shift ||
|
||||
!bookingForm.value.payment
|
||||
) {
|
||||
showSnackbar("Mohon lengkapi semua field Pemesanan.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
showBookingFormDialog.value = false
|
||||
const message = `Pemesanan antrian **${selectedClinic.value.name}** pada ${bookingForm.value.date} (**${bookingForm.value.payment}**) berhasil disimpan.`
|
||||
showSnackbar(message, 'success')
|
||||
console.log('Submitted booking for:', selectedClinic.value.name, bookingForm.value)
|
||||
showBookingFormDialog.value = false;
|
||||
const message = `Pemesanan antrian **${selectedClinic.value.name}** pada ${bookingForm.value.date} (**${bookingForm.value.payment}**) berhasil disimpan.`;
|
||||
showSnackbar(message, "success");
|
||||
console.log(
|
||||
"Submitted booking for:",
|
||||
selectedClinic.value.name,
|
||||
bookingForm.value
|
||||
);
|
||||
selectedClinic.value = null; // Reset
|
||||
}
|
||||
};
|
||||
|
||||
// 5. Submit Form Fast Track
|
||||
const submitFastTrack = () => {
|
||||
if (!fastTrackForm.value.guarantor || !fastTrackForm.value.patientName || !fastTrackForm.value.reason || !fastTrackForm.value.payment) {
|
||||
showSnackbar('Mohon lengkapi semua field Fast Track yang wajib diisi.', 'error')
|
||||
return
|
||||
if (
|
||||
!fastTrackForm.value.guarantor ||
|
||||
!fastTrackForm.value.patientName ||
|
||||
!fastTrackForm.value.reason ||
|
||||
!fastTrackForm.value.payment
|
||||
) {
|
||||
showSnackbar(
|
||||
"Mohon lengkapi semua field Fast Track yang wajib diisi.",
|
||||
"error"
|
||||
);
|
||||
return;
|
||||
}
|
||||
showFastTrackFormDialog.value = false
|
||||
const message = `Permintaan Fast Track **${selectedClinic.value.name}** atas nama ${fastTrackForm.value.patientName} (**${fastTrackForm.value.payment}**) telah diajukan.`
|
||||
showSnackbar(message, 'success')
|
||||
console.log('Submitted Fast Track for:', selectedClinic.value.name, fastTrackForm.value)
|
||||
showFastTrackFormDialog.value = false;
|
||||
const message = `Permintaan Fast Track **${selectedClinic.value.name}** atas nama ${fastTrackForm.value.patientName} (**${fastTrackForm.value.payment}**) telah diajukan.`;
|
||||
showSnackbar(message, "success");
|
||||
console.log(
|
||||
"Submitted Fast Track for:",
|
||||
selectedClinic.value.name,
|
||||
fastTrackForm.value
|
||||
);
|
||||
selectedClinic.value = null; // Reset
|
||||
}
|
||||
};
|
||||
|
||||
const refreshData = () => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
showSnackbar('Status klinik berhasil diperbarui', 'success')
|
||||
}, 1000)
|
||||
}
|
||||
loading.value = false;
|
||||
showSnackbar("Status klinik berhasil diperbarui", "success");
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
refreshData()
|
||||
})
|
||||
refreshData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Style yang sudah ada (Tidak Berubah) */
|
||||
.anjungan-container {
|
||||
background: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
@@ -513,7 +749,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
|
||||
@@ -577,13 +813,13 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.clinic-available {
|
||||
border-left: 6px solid #4CAF50;
|
||||
border-left: 6px solid #4caf50;
|
||||
}
|
||||
|
||||
.clinic-closed {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
border-left: 6px solid #F44336;
|
||||
border-left: 6px solid #f44336;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
@@ -613,12 +849,12 @@ onMounted(() => {
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.header-left {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
@@ -628,15 +864,15 @@ onMounted(() => {
|
||||
.anjungan-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
|
||||
.header-content {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
|
||||
.header-icon {
|
||||
padding: 12px;
|
||||
}
|
||||
@@ -655,9 +891,9 @@ onMounted(() => {
|
||||
.clinic-card {
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
+451
-620
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,521 @@
|
||||
<!-- pages/AntrianKlinikRuang.vue -->
|
||||
<template>
|
||||
<div class="antrian-display-container">
|
||||
<!-- Header -->
|
||||
<div class="display-header">
|
||||
<div class="header-left">
|
||||
<div class="logo-circle">
|
||||
<v-icon size="48" color="white">mdi-door-open</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="hospital-name">ANTRIAN KLINIK</h1>
|
||||
<p class="display-subtitle">RSUD dr. Saiful Anwar Provinsi Jawa Timur</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="datetime-display">
|
||||
<div class="time-large">{{ currentTime }}</div>
|
||||
<div class="date-small">{{ currentDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hero Call Section -->
|
||||
<div v-if="currentCalledQueue" class="hero-call-section">
|
||||
<div class="call-label">
|
||||
<v-icon size="32" class="mr-2">mdi-bullhorn</v-icon>
|
||||
NOMOR ANTRIAN YANG DIPANGGIL
|
||||
</div>
|
||||
<div class="call-number">{{ currentCalledQueue.noAntrian.split(' |')[0] }}</div>
|
||||
<div class="call-clinic">{{ currentCalledQueue.klinik }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Grid - 3x3 Clinics -->
|
||||
<div class="clinics-grid">
|
||||
<div
|
||||
v-for="clinic in displayedClinics"
|
||||
:key="clinic.name"
|
||||
class="clinic-box"
|
||||
>
|
||||
<!-- Clinic Header -->
|
||||
<div class="clinic-header-bar" style="background: linear-gradient(135deg, #FF9800 0%, #F57C00 100%);">
|
||||
<span class="clinic-title">{{ clinic.name }}</span>
|
||||
<v-chip
|
||||
:color="clinic.totalQueues > 0 ? '#FF9800' : '#89939E'"
|
||||
size="small"
|
||||
class="clinic-count"
|
||||
>
|
||||
<v-icon size="16" class="mr-1">mdi-account-multiple</v-icon>
|
||||
{{ clinic.totalQueues }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- Queue Content -->
|
||||
<div class="clinic-content">
|
||||
<!-- Current Queue -->
|
||||
<div v-if="clinic.currentQueue" class="current-section">
|
||||
<div class="current-label">SEKARANG</div>
|
||||
<div class="current-number">
|
||||
{{ clinic.currentQueue.noAntrian.split(' |')[0] }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Next Queues -->
|
||||
<div v-if="clinic.nextQueues.length > 0" class="next-section">
|
||||
<div class="next-label">SELANJUTNYA</div>
|
||||
<div class="next-numbers">
|
||||
<div
|
||||
v-for="queue in clinic.nextQueues.slice(0, 3)"
|
||||
:key="queue.no"
|
||||
class="next-item"
|
||||
>
|
||||
{{ queue.noAntrian.split(' |')[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="!clinic.currentQueue && clinic.nextQueues.length === 0" class="empty-state">
|
||||
<v-icon size="56" color="#F5F7FA">mdi-clock-outline</v-icon>
|
||||
<p class="empty-text">Tidak Ada Antrian</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Stats -->
|
||||
<div class="footer-stats-bar">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon" style="background: #FFF3E0;">
|
||||
<v-icon size="32" color="#FF9800">mdi-format-list-numbered</v-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.total }}</div>
|
||||
<div class="stat-label">Total Antrian</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-divider"></div>
|
||||
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon" style="background: #FFF3E0;">
|
||||
<v-icon size="32" color="#FF9800">mdi-clock-alert-outline</v-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.waiting }}</div>
|
||||
<div class="stat-label">Menunggu</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-divider"></div>
|
||||
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon" style="background: #E8F5E9;">
|
||||
<v-icon size="32" color="#18B653">mdi-account-check</v-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.active }}</div>
|
||||
<div class="stat-label">Sedang Dilayani</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-message">
|
||||
<v-icon size="24" color="#FF9800" class="mr-2">mdi-information</v-icon>
|
||||
<span>Harap perhatikan nomor antrian Anda di layar</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useQueueStore } from '../stores/queueStore'
|
||||
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
});
|
||||
|
||||
const queueStore = useQueueStore()
|
||||
const currentTime = ref('')
|
||||
const currentDate = ref('')
|
||||
let timeInterval = null
|
||||
|
||||
const klinikPatients = computed(() => {
|
||||
return queueStore.getPatientsByStage('klinik').value.all
|
||||
})
|
||||
|
||||
const displayedClinics = computed(() => {
|
||||
const allKliniks = queueStore.kliniks.slice(0, 9)
|
||||
|
||||
return allKliniks.map(klinik => {
|
||||
const queues = klinikPatients.value
|
||||
.filter(p => p.klinik === klinik.name)
|
||||
.sort((a, b) => {
|
||||
const statusPriority = {
|
||||
'di-loket': 1,
|
||||
'waiting': 2,
|
||||
'terlambat': 3,
|
||||
'pending': 4
|
||||
}
|
||||
|
||||
const priorityDiff = (statusPriority[a.status] || 99) - (statusPriority[b.status] || 99)
|
||||
if (priorityDiff !== 0) return priorityDiff
|
||||
|
||||
const timeA = a.jamPanggil.split(':').map(Number)
|
||||
const timeB = b.jamPanggil.split(':').map(Number)
|
||||
return timeA[0] * 60 + timeA[1] - (timeB[0] * 60 + timeB[1])
|
||||
})
|
||||
|
||||
const currentQueue = queues.find(q => q.status === 'di-loket') ||
|
||||
(queues.find(q => q.status === 'waiting') || null)
|
||||
|
||||
const nextQueues = queues.filter(q =>
|
||||
q.no !== currentQueue?.no &&
|
||||
(q.status === 'waiting' || q.status === 'di-loket')
|
||||
)
|
||||
|
||||
return {
|
||||
name: klinik.name,
|
||||
currentQueue: currentQueue,
|
||||
nextQueues: nextQueues,
|
||||
totalQueues: queues.length
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const currentCalledQueue = computed(() => {
|
||||
const calledQueues = klinikPatients.value
|
||||
.filter(p => p.status === 'di-loket')
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
||||
|
||||
return calledQueues[0] || null
|
||||
})
|
||||
|
||||
const statistics = computed(() => {
|
||||
const all = klinikPatients.value
|
||||
return {
|
||||
total: all.length,
|
||||
waiting: all.filter(p => p.status === 'waiting').length,
|
||||
active: all.filter(p => p.status === 'di-loket').length
|
||||
}
|
||||
})
|
||||
|
||||
const updateTime = () => {
|
||||
const now = new Date()
|
||||
currentTime.value = now.toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
currentDate.value = now.toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timeInterval = setInterval(updateTime, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) clearInterval(timeInterval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.antrian-display-container {
|
||||
background: #FFFFFF;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.display-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #FF9800 0%, #F57C00 100%);
|
||||
border-radius: 20px;
|
||||
padding: 24px 40px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 24px rgba(255, 152, 0, 0.2);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.logo-circle {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.hospital-name {
|
||||
font-size: 42px;
|
||||
font-weight: 900;
|
||||
color: #FFFFFF;
|
||||
margin: 0;
|
||||
letter-spacing: 1px;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.display-subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
color: #FFFFFF;
|
||||
margin: 0;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.time-large {
|
||||
font-size: 56px;
|
||||
font-weight: 800;
|
||||
color: #FFFFFF;
|
||||
line-height: 1;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.date-small {
|
||||
font-size: 18px;
|
||||
color: #FFFFFF;
|
||||
margin-top: 8px;
|
||||
font-weight: 400;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.hero-call-section {
|
||||
background: linear-gradient(135deg, #E01507 0%, #E02B1D 100%);
|
||||
border-radius: 20px;
|
||||
padding: 32px;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
box-shadow: 0 12px 32px rgba(224, 21, 7, 0.25);
|
||||
animation: pulse-shadow 2s infinite;
|
||||
}
|
||||
|
||||
.call-label {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.call-number {
|
||||
font-size: 110px;
|
||||
font-weight: 900;
|
||||
color: #FFFFFF;
|
||||
line-height: 1;
|
||||
margin: 16px 0;
|
||||
letter-spacing: 4px;
|
||||
text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.call-clinic {
|
||||
font-size: 38px;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
@keyframes pulse-shadow {
|
||||
0%, 100% {
|
||||
box-shadow: 0 12px 32px rgba(224, 21, 7, 0.25);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 16px 48px rgba(224, 21, 7, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.clinics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.clinic-box {
|
||||
background: #FFFFFF;
|
||||
border: 2px solid #FFF3E0;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 16px rgba(255, 152, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
min-height: 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.clinic-box:hover {
|
||||
box-shadow: 0 8px 24px rgba(255, 152, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.clinic-header-bar {
|
||||
background: linear-gradient(135deg, #FF9800 0%, #F57C00 100%);
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.clinic-title {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.clinic-count {
|
||||
background: rgba(255, 255, 255, 0.2) !important;
|
||||
color: #FFFFFF !important;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.clinic-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
background: #FAFBFC;
|
||||
}
|
||||
|
||||
.current-section {
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.current-label {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #18B653;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.current-number {
|
||||
font-size: 68px;
|
||||
font-weight: 900;
|
||||
color: #212121;
|
||||
line-height: 1;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.next-section {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.next-label {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #717171;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.next-numbers {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.next-item {
|
||||
background: linear-gradient(135deg, #FF9800 0%, #F57C00 100%);
|
||||
color: #FFFFFF;
|
||||
padding: 8px 18px;
|
||||
border-radius: 10px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 4px 12px rgba(255, 152, 0, 0.2);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
color: #89939E;
|
||||
margin-top: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.footer-stats-bar {
|
||||
background: #FFFFFF;
|
||||
border: 2px solid #FFF3E0;
|
||||
border-radius: 20px;
|
||||
padding: 24px 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
box-shadow: 0 4px 16px rgba(255, 152, 0, 0.08);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 42px;
|
||||
font-weight: 900;
|
||||
color: #212121;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 15px;
|
||||
color: #717171;
|
||||
font-weight: 600;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 2px;
|
||||
height: 64px;
|
||||
background: #FFF3E0;
|
||||
}
|
||||
|
||||
.footer-message {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #4D4D4D;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,520 @@
|
||||
<!-- pages/AntrianPenunjang.vue -->
|
||||
<template>
|
||||
<div class="antrian-display-container">
|
||||
<!-- Header -->
|
||||
<div class="display-header">
|
||||
<div class="header-left">
|
||||
<div class="logo-circle">
|
||||
<v-icon size="48" color="white">mdi-clipboard-pulse</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="hospital-name">RUMAH SAKIT</h1>
|
||||
<p class="display-subtitle">Antrian Penunjang</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="datetime-display">
|
||||
<div class="time-large">{{ currentTime }}</div>
|
||||
<div class="date-small">{{ currentDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hero Call Section -->
|
||||
<div v-if="currentCalledQueue" class="hero-call-section">
|
||||
<div class="call-label">
|
||||
<v-icon size="32" class="mr-2">mdi-bullhorn</v-icon>
|
||||
NOMOR ANTRIAN YANG DIPANGGIL
|
||||
</div>
|
||||
<div class="call-number">{{ currentCalledQueue.noAntrian.split(' |')[0] }}</div>
|
||||
<div class="call-clinic">{{ currentCalledQueue.klinik }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Grid - 3x3 Penunjang -->
|
||||
<div class="clinics-grid">
|
||||
<div
|
||||
v-for="penunjang in displayedPenunjangs"
|
||||
:key="penunjang.name"
|
||||
class="clinic-box"
|
||||
>
|
||||
<!-- Penunjang Header -->
|
||||
<div class="clinic-header-bar" style="background: linear-gradient(135deg, #9C27B0 0%, #7B1FA2 100%);">
|
||||
<span class="clinic-title">{{ penunjang.name }}</span>
|
||||
<v-chip
|
||||
:color="penunjang.totalQueues > 0 ? '#9C27B0' : '#89939E'"
|
||||
size="small"
|
||||
class="clinic-count"
|
||||
>
|
||||
<v-icon size="16" class="mr-1">mdi-account-multiple</v-icon>
|
||||
{{ penunjang.totalQueues }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- Queue Content -->
|
||||
<div class="clinic-content">
|
||||
<!-- Current Queue -->
|
||||
<div v-if="penunjang.currentQueue" class="current-section">
|
||||
<div class="current-label">SEKARANG</div>
|
||||
<div class="current-number">
|
||||
{{ penunjang.currentQueue.noAntrian.split(' |')[0] }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Next Queues -->
|
||||
<div v-if="penunjang.nextQueues.length > 0" class="next-section">
|
||||
<div class="next-label">SELANJUTNYA</div>
|
||||
<div class="next-numbers">
|
||||
<div
|
||||
v-for="queue in penunjang.nextQueues.slice(0, 3)"
|
||||
:key="queue.no"
|
||||
class="next-item"
|
||||
style="background: linear-gradient(135deg, #9C27B0 0%, #7B1FA2 100%);"
|
||||
>
|
||||
{{ queue.noAntrian.split(' |')[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="!penunjang.currentQueue && penunjang.nextQueues.length === 0" class="empty-state">
|
||||
<v-icon size="56" color="#F5F7FA">mdi-clock-outline</v-icon>
|
||||
<p class="empty-text">Tidak Ada Antrian</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Stats -->
|
||||
<div class="footer-stats-bar">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon" style="background: #F3E5F5;">
|
||||
<v-icon size="32" color="#9C27B0">mdi-format-list-numbered</v-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.total }}</div>
|
||||
<div class="stat-label">Total Antrian</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-divider"></div>
|
||||
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon" style="background: #F3E5F5;">
|
||||
<v-icon size="32" color="#9C27B0">mdi-clock-alert-outline</v-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.waiting }}</div>
|
||||
<div class="stat-label">Menunggu</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-divider"></div>
|
||||
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon" style="background: #E8F5E9;">
|
||||
<v-icon size="32" color="#18B653">mdi-account-check</v-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.active }}</div>
|
||||
<div class="stat-label">Sedang Dilayani</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-message">
|
||||
<v-icon size="24" color="#9C27B0" class="mr-2">mdi-information</v-icon>
|
||||
<span>Harap perhatikan nomor antrian Anda di layar</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useQueueStore } from '../stores/queueStore'
|
||||
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
});
|
||||
|
||||
const queueStore = useQueueStore()
|
||||
const currentTime = ref('')
|
||||
const currentDate = ref('')
|
||||
let timeInterval = null
|
||||
|
||||
const penunjangPatients = computed(() => {
|
||||
return queueStore.getPatientsByStage('penunjang').value.all
|
||||
})
|
||||
|
||||
const displayedPenunjangs = computed(() => {
|
||||
const allPenunjangs = queueStore.penunjangs.slice(0, 9)
|
||||
|
||||
return allPenunjangs.map(penunjang => {
|
||||
const queues = penunjangPatients.value
|
||||
.filter(p => p.klinik === penunjang.name)
|
||||
.sort((a, b) => {
|
||||
const statusPriority = {
|
||||
'di-loket': 1,
|
||||
'waiting': 2,
|
||||
'terlambat': 3,
|
||||
'pending': 4
|
||||
}
|
||||
|
||||
const priorityDiff = (statusPriority[a.status] || 99) - (statusPriority[b.status] || 99)
|
||||
if (priorityDiff !== 0) return priorityDiff
|
||||
|
||||
const timeA = a.jamPanggil.split(':').map(Number)
|
||||
const timeB = b.jamPanggil.split(':').map(Number)
|
||||
return timeA[0] * 60 + timeA[1] - (timeB[0] * 60 + timeB[1])
|
||||
})
|
||||
|
||||
const currentQueue = queues.find(q => q.status === 'di-loket') ||
|
||||
(queues.find(q => q.status === 'waiting') || null)
|
||||
|
||||
const nextQueues = queues.filter(q =>
|
||||
q.no !== currentQueue?.no &&
|
||||
(q.status === 'waiting' || q.status === 'di-loket')
|
||||
)
|
||||
|
||||
return {
|
||||
name: penunjang.name,
|
||||
currentQueue: currentQueue,
|
||||
nextQueues: nextQueues,
|
||||
totalQueues: queues.length
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const currentCalledQueue = computed(() => {
|
||||
const calledQueues = penunjangPatients.value
|
||||
.filter(p => p.status === 'di-loket')
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
||||
|
||||
return calledQueues[0] || null
|
||||
})
|
||||
|
||||
const statistics = computed(() => {
|
||||
const all = penunjangPatients.value
|
||||
return {
|
||||
total: all.length,
|
||||
waiting: all.filter(p => p.status === 'waiting').length,
|
||||
active: all.filter(p => p.status === 'di-loket').length
|
||||
}
|
||||
})
|
||||
|
||||
const updateTime = () => {
|
||||
const now = new Date()
|
||||
currentTime.value = now.toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
currentDate.value = now.toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timeInterval = setInterval(updateTime, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) clearInterval(timeInterval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.antrian-display-container {
|
||||
background: #FFFFFF;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.display-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
padding: 24px 40px;
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 24px rgba(255, 252, 255, 0.2);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.logo-circle {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.hospital-name {
|
||||
font-size: 42px;
|
||||
font-weight: 900;
|
||||
color: #FFFFFF;
|
||||
margin: 0;
|
||||
letter-spacing: 1px;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.display-subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
color: #FFFFFF;
|
||||
margin: 0;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.time-large {
|
||||
font-size: 56px;
|
||||
font-weight: 800;
|
||||
color: #FFFFFF;
|
||||
line-height: 1;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.date-small {
|
||||
font-size: 18px;
|
||||
color: #FFFFFF;
|
||||
margin-top: 8px;
|
||||
font-weight: 400;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.hero-call-section {
|
||||
background: linear-gradient(135deg, #E01507 0%, #E02B1D 100%);
|
||||
border-radius: 20px;
|
||||
padding: 32px;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
box-shadow: 0 12px 32px rgba(224, 21, 7, 0.25);
|
||||
animation: pulse-shadow 2s infinite;
|
||||
}
|
||||
|
||||
.call-label {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.call-number {
|
||||
font-size: 110px;
|
||||
font-weight: 900;
|
||||
color: #FFFFFF;
|
||||
line-height: 1;
|
||||
margin: 16px 0;
|
||||
letter-spacing: 4px;
|
||||
text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.call-clinic {
|
||||
font-size: 38px;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
@keyframes pulse-shadow {
|
||||
0%, 100% {
|
||||
box-shadow: 0 12px 32px rgba(224, 21, 7, 0.25);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 16px 48px rgba(224, 21, 7, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.clinics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.clinic-box {
|
||||
background: #FFFFFF;
|
||||
border: 2px solid #F3E5F5;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 16px rgba(156, 39, 176, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
min-height: 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.clinic-box:hover {
|
||||
box-shadow: 0 8px 24px rgba(156, 39, 176, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.clinic-header-bar {
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.clinic-title {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.clinic-count {
|
||||
background: rgba(255, 255, 255, 0.2) !important;
|
||||
color: #FFFFFF !important;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.clinic-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
background: #FAFBFC;
|
||||
}
|
||||
|
||||
.current-section {
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.current-label {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #18B653;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.current-number {
|
||||
font-size: 68px;
|
||||
font-weight: 900;
|
||||
color: #212121;
|
||||
line-height: 1;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.next-section {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.next-label {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #717171;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.next-numbers {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.next-item {
|
||||
color: #FFFFFF;
|
||||
padding: 8px 18px;
|
||||
border-radius: 10px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 4px 12px rgba(156, 39, 176, 0.2);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
color: #89939E;
|
||||
margin-top: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.footer-stats-bar {
|
||||
background: #FFFFFF;
|
||||
border: 2px solid #F3E5F5;
|
||||
border-radius: 20px;
|
||||
padding: 24px 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
box-shadow: 0 4px 16px rgba(156, 39, 176, 0.08);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 42px;
|
||||
font-weight: 900;
|
||||
color: #212121;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 15px;
|
||||
color: #717171;
|
||||
font-weight: 600;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 2px;
|
||||
height: 64px;
|
||||
background: #F3E5F5;
|
||||
}
|
||||
|
||||
.footer-message {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #4D4D4D;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,543 @@
|
||||
<!-- pages/AntrianKlinik.vue -->
|
||||
<template>
|
||||
<div class="tv-display-container">
|
||||
<!-- Header -->
|
||||
<div class="tv-header">
|
||||
<div class="tv-header-left">
|
||||
<div class="hospital-logo">
|
||||
<v-icon size="64" color="white">mdi-hospital-box</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="hospital-name">ANTRIAN KLINIK</h1>
|
||||
<p class="display-title">RSUD Dr. Saiful Anwar Provinsi Jawa Timur</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tv-header-right">
|
||||
<div class="current-time">{{ currentTime }}</div>
|
||||
<div class="current-date">{{ currentDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Called Queue - Super Prominent -->
|
||||
<div v-if="currentCalledQueue" class="hero-queue">
|
||||
<div class="hero-label">SEDANG DIPANGGIL</div>
|
||||
<div class="hero-number">{{ currentCalledQueue.noAntrian.split(' |')[0] }}</div>
|
||||
<div class="hero-clinic">{{ currentCalledQueue.klinik }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Clinic Grid - 3x3 Layout -->
|
||||
<div class="clinic-grid">
|
||||
<div
|
||||
v-for="clinic in displayedClinics"
|
||||
:key="clinic.name"
|
||||
class="clinic-card"
|
||||
>
|
||||
<div class="clinic-name">{{ clinic.name }}</div>
|
||||
|
||||
<div class="queue-display">
|
||||
<!-- Show current queue number -->
|
||||
<div v-if="clinic.currentQueue" class="current-queue-large">
|
||||
<div class="queue-label">SEKARANG</div>
|
||||
<div class="queue-number-huge">
|
||||
{{ clinic.currentQueue.noAntrian.split(' |')[0] }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Show next queues -->
|
||||
<div v-if="clinic.nextQueues.length > 0" class="next-queues">
|
||||
<div class="queue-label-small">SELANJUTNYA</div>
|
||||
<div class="next-queue-numbers">
|
||||
<span
|
||||
v-for="(queue, index) in clinic.nextQueues.slice(0, 3)"
|
||||
:key="queue.no"
|
||||
class="next-number"
|
||||
>
|
||||
{{ queue.noAntrian.split(' |')[0] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-if="!clinic.currentQueue && clinic.nextQueues.length === 0" class="empty-queue">
|
||||
<v-icon size="48" color="grey-lighten-2">mdi-checkbox-blank-circle-outline</v-icon>
|
||||
<div class="empty-text">Tidak Ada Antrian</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Queue count badge -->
|
||||
<div class="queue-count">
|
||||
<v-icon size="20" class="mr-1">mdi-account-multiple</v-icon>
|
||||
{{ clinic.totalQueues }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<div class="tv-footer">
|
||||
<div class="footer-stats">
|
||||
<div class="stat-box">
|
||||
<div class="stat-value">{{ statistics.total }}</div>
|
||||
<div class="stat-label">Total Antrian</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-value">{{ statistics.waiting }}</div>
|
||||
<div class="stat-label">Menunggu</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-value">{{ statistics.active }}</div>
|
||||
<div class="stat-label">Sedang Dilayani</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-message">
|
||||
<v-icon size="24" class="mr-2">mdi-information</v-icon>
|
||||
Harap perhatikan nomor antrian Anda di layar
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useQueueStore } from '../stores/queueStore'
|
||||
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
});
|
||||
|
||||
// Store
|
||||
const queueStore = useQueueStore()
|
||||
|
||||
// Reactive data
|
||||
const currentTime = ref('')
|
||||
const currentDate = ref('')
|
||||
let timeInterval = null
|
||||
let refreshInterval = null
|
||||
|
||||
// Computed - Get all klinik patients (stage: klinik)
|
||||
const klinikPatients = computed(() => {
|
||||
return queueStore.getPatientsByStage('klinik').value.all
|
||||
})
|
||||
|
||||
// Get clinics with their queues organized
|
||||
const displayedClinics = computed(() => {
|
||||
const allKliniks = queueStore.kliniks.slice(0, 9) // Max 9 clinics for 3x3 grid
|
||||
|
||||
return allKliniks.map(klinik => {
|
||||
const queues = klinikPatients.value
|
||||
.filter(p => p.klinik === klinik.name)
|
||||
.sort((a, b) => {
|
||||
// Sort by status priority then time
|
||||
const statusPriority = {
|
||||
'di-loket': 1,
|
||||
'waiting': 2,
|
||||
'terlambat': 3,
|
||||
'pending': 4
|
||||
}
|
||||
|
||||
const priorityDiff = (statusPriority[a.status] || 99) - (statusPriority[b.status] || 99)
|
||||
if (priorityDiff !== 0) return priorityDiff
|
||||
|
||||
// Sort by time if same status
|
||||
const timeA = a.jamPanggil.split(':').map(Number)
|
||||
const timeB = b.jamPanggil.split(':').map(Number)
|
||||
return timeA[0] * 60 + timeA[1] - (timeB[0] * 60 + timeB[1])
|
||||
})
|
||||
|
||||
// Get current (first di-loket or first waiting)
|
||||
const currentQueue = queues.find(q => q.status === 'di-loket') ||
|
||||
(queues.find(q => q.status === 'waiting') || null)
|
||||
|
||||
// Get next queues (excluding current)
|
||||
const nextQueues = queues.filter(q =>
|
||||
q.no !== currentQueue?.no &&
|
||||
(q.status === 'waiting' || q.status === 'di-loket')
|
||||
)
|
||||
|
||||
return {
|
||||
name: klinik.name,
|
||||
currentQueue: currentQueue,
|
||||
nextQueues: nextQueues,
|
||||
totalQueues: queues.length
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Current queue being called (most recent di-loket)
|
||||
const currentCalledQueue = computed(() => {
|
||||
const calledQueues = klinikPatients.value
|
||||
.filter(p => p.status === 'di-loket')
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
||||
|
||||
return calledQueues[0] || null
|
||||
})
|
||||
|
||||
// Statistics
|
||||
const statistics = computed(() => {
|
||||
const all = klinikPatients.value
|
||||
return {
|
||||
total: all.length,
|
||||
waiting: all.filter(p => p.status === 'waiting').length,
|
||||
active: all.filter(p => p.status === 'di-loket').length
|
||||
}
|
||||
})
|
||||
|
||||
// Methods
|
||||
const updateTime = () => {
|
||||
const now = new Date()
|
||||
currentTime.value = now.toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
currentDate.value = now.toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timeInterval = setInterval(updateTime, 1000)
|
||||
|
||||
// Auto refresh every 5 seconds
|
||||
refreshInterval = setInterval(() => {
|
||||
// Data will auto-update due to reactive store
|
||||
}, 5000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) clearInterval(timeInterval)
|
||||
if (refreshInterval) clearInterval(refreshInterval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tv-display-container {
|
||||
background: linear-gradient(135deg, #e7e7e7 0%, #FFDCAF 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.tv-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #FAFAFA;
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 20px 40px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.tv-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hospital-logo {
|
||||
background: #1e3c72;
|
||||
border-radius: 50%;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hospital-name {
|
||||
font-size: 36px;
|
||||
margin: 0;
|
||||
letter-spacing: 2px;
|
||||
color: #1e3c72;
|
||||
line-height: 1;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.display-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: #1e3c72;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.tv-header-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.current-time {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #1e3c72;
|
||||
text-shadow: 2px 2px 4px rgba(124, 124, 124, 0.3);
|
||||
}
|
||||
|
||||
.current-date {
|
||||
font-size: 18px;
|
||||
opacity: 0.9;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Hero Queue - Sedang Dipanggil */
|
||||
.hero-queue {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
|
||||
border-radius: 24px;
|
||||
padding: 30px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 12px 48px rgba(255, 107, 107, 0.4);
|
||||
animation: pulse-glow 2s infinite;
|
||||
}
|
||||
|
||||
.hero-label {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 3px;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hero-number {
|
||||
font-size: 120px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
margin: 10px 0;
|
||||
text-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3);
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
.hero-clinic {
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% {
|
||||
box-shadow: 0 12px 48px rgba(255, 107, 107, 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 16px 64px rgba(255, 107, 107, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Clinic Grid - 3x3 */
|
||||
.clinic-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.clinic-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.clinic-name {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: #1e3c72;
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 3px solid #2a5298;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.queue-display {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Current Queue Display */
|
||||
.current-queue-large {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.queue-label {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #4caf50;
|
||||
margin-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.queue-number-huge {
|
||||
font-size: 72px;
|
||||
font-weight: 900;
|
||||
color: #1e3c72;
|
||||
line-height: 1;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Next Queues */
|
||||
.next-queues {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.queue-label-small {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.next-queue-numbers {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.next-number {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 12px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-queue {
|
||||
text-align: center;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
color: #999;
|
||||
margin-top: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Queue Count Badge */
|
||||
.queue-count {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.tv-footer {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 20px 40px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.footer-stats {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 42px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
color:#1e3c72;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
margin-top: 5px;
|
||||
color:#1e3c72;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.footer-message {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.95;
|
||||
color:#1e3c72;
|
||||
}
|
||||
|
||||
/* Responsive for different TV sizes */
|
||||
@media (max-width: 1920px) {
|
||||
.hero-number {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
.queue-number-huge {
|
||||
font-size: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1366px) {
|
||||
.clinic-grid {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.clinic-card {
|
||||
padding: 15px;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.clinic-name {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.queue-number-huge {
|
||||
font-size: 56px;
|
||||
}
|
||||
|
||||
.hero-number {
|
||||
font-size: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation for smooth transitions */
|
||||
.clinic-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.queue-number-huge,
|
||||
.hero-number {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,698 @@
|
||||
<!-- pages/BuatAntrean.vue -->
|
||||
<template>
|
||||
<v-container fluid class="buat-antrean-container">
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-ticket-account</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Buat Antrean</h1>
|
||||
<p class="page-subtitle">{{ currentDate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<v-chip color="white" variant="flat" class="mr-2">
|
||||
<v-icon start size="16">mdi-account-multiple</v-icon>
|
||||
{{ totalPasien }} Pasien
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search & Filter -->
|
||||
<v-card class="filter-card mb-4" elevation="2">
|
||||
<v-card-text>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="Pencarian"
|
||||
placeholder="Cari nama pasien, No. RM, atau alamat..."
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-select
|
||||
v-model="statusFilter"
|
||||
:items="statusOptions"
|
||||
label="Status"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
block
|
||||
size="large"
|
||||
@click="refreshData"
|
||||
:loading="loading"
|
||||
>
|
||||
<v-icon start>mdi-refresh</v-icon>
|
||||
Refresh Data
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Patient Table -->
|
||||
<v-card elevation="2">
|
||||
<v-card-title class="d-flex align-center pa-4 bg-grey-lighten-4">
|
||||
<v-icon class="mr-2">mdi-account-group</v-icon>
|
||||
<span>Daftar Pasien - {{ filteredPasien.length }} pasien</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-0">
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="filteredPasien"
|
||||
:search="search"
|
||||
:items-per-page="10"
|
||||
class="elevation-0"
|
||||
>
|
||||
<template v-slot:item.namaPasien="{ item }">
|
||||
<div class="patient-info">
|
||||
<div class="patient-name">{{ item.namaPasien }}</div>
|
||||
<div class="patient-meta">{{ item.noRM }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.status="{ item }">
|
||||
<v-chip
|
||||
:color="getStatusColor(item.status)"
|
||||
size="small"
|
||||
variant="flat"
|
||||
>
|
||||
{{ item.status }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.buatAntrean="{ item }">
|
||||
<div class="d-flex ga-2 flex-nowrap">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#0053AD"
|
||||
variant="outlined"
|
||||
@click="openKlinikDialog(item)"
|
||||
>
|
||||
<v-icon size="16" start>mdi-hospital-building</v-icon>
|
||||
Klinik
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#FF9B1B"
|
||||
variant="outlined"
|
||||
@click="openKlinikRuangDialog(item)"
|
||||
>
|
||||
<v-icon size="16" start>mdi-door-open</v-icon>
|
||||
Klinik Ruang
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#9C27B0"
|
||||
variant="outlined"
|
||||
@click="openPenunjangDialog(item)"
|
||||
>
|
||||
<v-icon size="16" start>mdi-clipboard-pulse</v-icon>
|
||||
Penunjang
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Dialog Klinik -->
|
||||
<v-dialog v-model="showKlinikDialog" max-width="600px">
|
||||
<v-card>
|
||||
<v-card-title class="dialog-header bg-primary">
|
||||
<v-icon class="mr-2">mdi-hospital-building</v-icon>
|
||||
<span>Pilih Klinik</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="showKlinikDialog = false" size="small">
|
||||
<v-icon color="white">mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<div v-if="selectedPatient" class="patient-card mb-4">
|
||||
<h4 class="mb-2">Informasi Pasien</h4>
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<div class="info-label">Nama</div>
|
||||
<div class="info-value">{{ selectedPatient.namaPasien }}</div>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<div class="info-label">No. RM</div>
|
||||
<div class="info-value">{{ selectedPatient.noRM }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-text-field
|
||||
v-model="klinikSearch"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="Cari Klinik"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="klinik in filteredKliniks"
|
||||
:key="klinik.id"
|
||||
cols="6"
|
||||
>
|
||||
<v-card
|
||||
class="klinik-option"
|
||||
@click="buatAntreanKlinik(klinik)"
|
||||
elevation="1"
|
||||
hover
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<v-icon size="32" color="primary" class="mb-2">
|
||||
mdi-hospital-building
|
||||
</v-icon>
|
||||
<div class="option-title">{{ klinik.name }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Dialog Klinik Ruang -->
|
||||
<v-dialog v-model="showKlinikRuangDialog" max-width="800px">
|
||||
<v-card>
|
||||
<v-card-title class="dialog-header" style="background: linear-gradient(135deg, #FF9800 0%, #F57C00 100%);">
|
||||
<v-icon class="mr-2">mdi-door-open</v-icon>
|
||||
<span>Pilih Klinik Ruang</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="showKlinikRuangDialog = false" size="small">
|
||||
<v-icon color="white">mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<div v-if="selectedPatient" class="patient-card mb-4">
|
||||
<h4 class="mb-2">Informasi Pasien</h4>
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<div class="info-label">Nama</div>
|
||||
<div class="info-value">{{ selectedPatient.namaPasien }}</div>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<div class="info-label">No. RM</div>
|
||||
<div class="info-value">{{ selectedPatient.noRM }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-text-field
|
||||
v-model="klinikRuangSearch"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="Cari Klinik Ruang"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<div class="ruang-list">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel
|
||||
v-for="klinikRuang in filteredKlinikRuang"
|
||||
:key="klinikRuang.id"
|
||||
>
|
||||
<v-expansion-panel-title>
|
||||
<div class="d-flex align-center">
|
||||
<v-chip size="small" color="primary" class="mr-2">
|
||||
{{ klinikRuang.kodeKlinik }}
|
||||
</v-chip>
|
||||
<strong>{{ klinikRuang.namaKlinik }}</strong>
|
||||
<v-spacer></v-spacer>
|
||||
<v-chip size="small" variant="outlined">
|
||||
{{ klinikRuang.ruangList.length }} Ruang
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
v-for="ruang in klinikRuang.ruangList"
|
||||
:key="ruang.nomorRuang"
|
||||
@click="buatAntreanKlinikRuang(klinikRuang, ruang)"
|
||||
class="ruang-item"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="orange">mdi-door</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
<strong>Ruang {{ ruang.nomorRuang }}</strong> - {{ ruang.namaRuang }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
Screen: {{ ruang.nomorScreen }}
|
||||
</v-list-item-subtitle>
|
||||
<template v-slot:append>
|
||||
<v-icon>mdi-chevron-right</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Dialog Penunjang -->
|
||||
<v-dialog v-model="showPenunjangDialog" max-width="600px">
|
||||
<v-card>
|
||||
<v-card-title class="dialog-header" style="background: linear-gradient(135deg, #9C27B0 0%, #7B1FA2 100%);">
|
||||
<v-icon class="mr-2">mdi-clipboard-pulse</v-icon>
|
||||
<span>Pilih Penunjang</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="showPenunjangDialog = false" size="small">
|
||||
<v-icon color="white">mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<div v-if="selectedPatient" class="patient-card mb-4">
|
||||
<h4 class="mb-2">Informasi Pasien</h4>
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<div class="info-label">Nama</div>
|
||||
<div class="info-value">{{ selectedPatient.namaPasien }}</div>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<div class="info-label">No. RM</div>
|
||||
<div class="info-value">{{ selectedPatient.noRM }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-text-field
|
||||
v-model="penunjangSearch"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="Cari Penunjang"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="penunjang in filteredPenunjangs"
|
||||
:key="penunjang.id"
|
||||
cols="6"
|
||||
>
|
||||
<v-card
|
||||
class="klinik-option"
|
||||
@click="buatAntreanPenunjang(penunjang)"
|
||||
elevation="1"
|
||||
hover
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<v-icon size="32" color="purple" class="mb-2">
|
||||
mdi-clipboard-pulse
|
||||
</v-icon>
|
||||
<div class="option-title">{{ penunjang.name }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="3000"
|
||||
location="top right"
|
||||
>
|
||||
{{ snackbarText }}
|
||||
<template v-slot:actions>
|
||||
<v-btn icon @click="snackbar = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useQueueStore } from '../stores/queueStore'
|
||||
|
||||
// Store
|
||||
const queueStore = useQueueStore()
|
||||
|
||||
// State
|
||||
const loading = ref(false)
|
||||
const search = ref('')
|
||||
const statusFilter = ref(null)
|
||||
const snackbar = ref(false)
|
||||
const snackbarText = ref('')
|
||||
const snackbarColor = ref('success')
|
||||
const currentDate = ref(new Date().toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
}))
|
||||
|
||||
// Dialog states
|
||||
const showKlinikDialog = ref(false)
|
||||
const showKlinikRuangDialog = ref(false)
|
||||
const showPenunjangDialog = ref(false)
|
||||
const selectedPatient = ref(null)
|
||||
|
||||
// Search states
|
||||
const klinikSearch = ref('')
|
||||
const klinikRuangSearch = ref('')
|
||||
const penunjangSearch = ref('')
|
||||
|
||||
// Status options
|
||||
const statusOptions = ref(['Terdaftar', 'Belum Terdaftar', 'Proses'])
|
||||
|
||||
// Headers
|
||||
const headers = ref([
|
||||
{ title: 'Nama Pasien', value: 'namaPasien', sortable: true },
|
||||
{ title: 'No. RM', value: 'noRM', sortable: true },
|
||||
{ title: 'Alamat', value: 'alamat', sortable: true },
|
||||
{ title: 'Status', value: 'status', sortable: true },
|
||||
{ title: 'Buat Antrean', value: 'buatAntrean', sortable: false, width: '300px' },
|
||||
])
|
||||
|
||||
// Mock patient data
|
||||
const pasienList = ref([
|
||||
{
|
||||
id: 1,
|
||||
namaPasien: 'Ahmad Fauzi',
|
||||
noRM: '250811100163',
|
||||
alamat: 'Jl. Merdeka No. 123',
|
||||
status: 'Terdaftar'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
namaPasien: 'Siti Aminah',
|
||||
noRM: '250811100155',
|
||||
alamat: 'Jl. Sudirman No. 45',
|
||||
status: 'Terdaftar'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
namaPasien: 'Budi Santoso',
|
||||
noRM: '250811100200',
|
||||
alamat: 'Jl. Gatot Subroto No. 78',
|
||||
status: 'Belum Terdaftar'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
namaPasien: 'Dewi Lestari',
|
||||
noRM: '250811100210',
|
||||
alamat: 'Jl. Ahmad Yani No. 90',
|
||||
status: 'Terdaftar'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
namaPasien: 'Eko Prasetyo',
|
||||
noRM: '250811100220',
|
||||
alamat: 'Jl. Diponegoro No. 15',
|
||||
status: 'Proses'
|
||||
},
|
||||
])
|
||||
|
||||
// Mock klinik ruang data (dari Master Klinik Ruang)
|
||||
const klinikRuangList = ref([
|
||||
{
|
||||
id: 1,
|
||||
kodeKlinik: 'AN',
|
||||
namaKlinik: 'ANAK',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'R. TINDAKAN', nomorScreen: '101' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
kodeKlinik: 'GI',
|
||||
namaKlinik: 'GIGI DAN MULUT',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '501' },
|
||||
{ nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '502' },
|
||||
{ nomorRuang: '3', namaRuang: 'Ruang 3', nomorScreen: '503' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
kodeKlinik: 'BD',
|
||||
namaKlinik: 'BEDAH',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang Konsultasi', nomorScreen: '301' }
|
||||
]
|
||||
},
|
||||
])
|
||||
|
||||
// Computed
|
||||
const totalPasien = computed(() => pasienList.value.length)
|
||||
|
||||
const filteredPasien = computed(() => {
|
||||
let filtered = pasienList.value
|
||||
|
||||
if (statusFilter.value) {
|
||||
filtered = filtered.filter(p => p.status === statusFilter.value)
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
const filteredKliniks = computed(() => {
|
||||
if (!klinikSearch.value) return queueStore.kliniks
|
||||
return queueStore.kliniks.filter(k =>
|
||||
k.name.toLowerCase().includes(klinikSearch.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
const filteredKlinikRuang = computed(() => {
|
||||
if (!klinikRuangSearch.value) return klinikRuangList.value
|
||||
return klinikRuangList.value.filter(k =>
|
||||
k.namaKlinik.toLowerCase().includes(klinikRuangSearch.value.toLowerCase()) ||
|
||||
k.kodeKlinik.toLowerCase().includes(klinikRuangSearch.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
const filteredPenunjangs = computed(() => {
|
||||
if (!penunjangSearch.value) return queueStore.penunjangs
|
||||
return queueStore.penunjangs.filter(p =>
|
||||
p.name.toLowerCase().includes(penunjangSearch.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
// Methods
|
||||
const showSnackbar = (text, color = 'success') => {
|
||||
snackbarText.value = text
|
||||
snackbarColor.value = color
|
||||
snackbar.value = true
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'Terdaftar': 'success',
|
||||
'Belum Terdaftar': 'warning',
|
||||
'Proses': 'info'
|
||||
}
|
||||
return colors[status] || 'grey'
|
||||
}
|
||||
|
||||
const refreshData = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
showSnackbar('Data berhasil diperbarui', 'success')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const openKlinikDialog = (patient) => {
|
||||
selectedPatient.value = patient
|
||||
showKlinikDialog.value = true
|
||||
}
|
||||
|
||||
const openKlinikRuangDialog = (patient) => {
|
||||
selectedPatient.value = patient
|
||||
showKlinikRuangDialog.value = true
|
||||
}
|
||||
|
||||
const openPenunjangDialog = (patient) => {
|
||||
selectedPatient.value = patient
|
||||
showPenunjangDialog.value = true
|
||||
}
|
||||
|
||||
const buatAntreanKlinik = (klinik) => {
|
||||
const result = queueStore.createAntreanKlinik(klinik, 'loket')
|
||||
showSnackbar(`Antrean klinik ${klinik.name} untuk ${selectedPatient.value.namaPasien} berhasil dibuat`, 'success')
|
||||
showKlinikDialog.value = false
|
||||
selectedPatient.value = null
|
||||
}
|
||||
|
||||
const buatAntreanKlinikRuang = (klinikRuang, ruang) => {
|
||||
// Create queue for klinik ruang with room info
|
||||
const klinikInfo = {
|
||||
name: `${klinikRuang.namaKlinik} - ${ruang.namaRuang}`,
|
||||
kode: klinikRuang.kodeKlinik,
|
||||
ruang: ruang
|
||||
}
|
||||
|
||||
const result = queueStore.createAntreanKlinik(klinikInfo, 'loket')
|
||||
showSnackbar(`Antrean ${klinikRuang.namaKlinik} Ruang ${ruang.nomorRuang} untuk ${selectedPatient.value.namaPasien} berhasil dibuat`, 'success')
|
||||
showKlinikRuangDialog.value = false
|
||||
selectedPatient.value = null
|
||||
}
|
||||
|
||||
const buatAntreanPenunjang = (penunjang) => {
|
||||
const result = queueStore.createAntreanPenunjang(penunjang, selectedPatient.value, 'loket')
|
||||
showSnackbar(`Antrean penunjang ${penunjang.name} untuk ${selectedPatient.value.namaPasien} berhasil dibuat`, 'success')
|
||||
showPenunjangDialog.value = false
|
||||
selectedPatient.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.buat-antrean-container {
|
||||
background: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 24px rgba(0, 83, 173, 0.2);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-right: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.filter-card,
|
||||
.patient-card {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.patient-info {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.patient-name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
.patient-meta {
|
||||
font-size: 12px;
|
||||
color: #717171;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
color: white;
|
||||
padding: 20px 24px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.patient-card {
|
||||
background: #f5f7fa;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 12px;
|
||||
color: #717171;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
.klinik-option {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.klinik-option:hover {
|
||||
border-color: #0053AD;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.option-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
.ruang-item {
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.ruang-item:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
</style>
|
||||
+95
-97
@@ -101,14 +101,11 @@
|
||||
|
||||
<v-row class="mb-4">
|
||||
<v-col cols="12" md="6">
|
||||
<v-card class="pa-4 rounded-xl elevation-6">
|
||||
<v-card-title class="d-flex justify-space-between align-center">
|
||||
<span class="text-h6 font-weight-bold">Grafik Jumlah Antrean</span>
|
||||
<v-btn-toggle v-model="queueTimePeriod" mandatory divided variant="outlined" color="primary" class="text-caption">
|
||||
<v-btn size="small" value="day">Hari</v-btn>
|
||||
<v-btn size="small" value="week">Minggu</v-btn>
|
||||
<v-btn size="small" value="month">Bulan</v-btn>
|
||||
<v-btn size="small" value="year">Tahun</v-btn>
|
||||
<v-card class="pa-6 rounded-xl elevation-3 card-style chart-card">
|
||||
<v-card-title class="d-flex justify-space-between align-center px-0 pt-0 mb-4">
|
||||
<span class="text-h6 font-weight-bold primary-text">Registration Trend</span>
|
||||
<v-btn-toggle v-model="queueTimePeriod" mandatory variant="outlined" color="#D17A4A" class="text-caption rounded-lg">
|
||||
<v-btn size="small" value="day">Day</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-card-title>
|
||||
<v-card-text class="pa-0">
|
||||
@@ -159,9 +156,15 @@
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-card class="pa-4 rounded-xl elevation-6">
|
||||
<v-card-title class="text-h6 font-weight-bold">Realtime Ticket Queue</v-card-title>
|
||||
<v-card-text>
|
||||
<v-card class="pa-6 rounded-xl elevation-3 card-style chart-card">
|
||||
<v-card-title class="text-h6 font-weight-bold primary-text px-0 pt-0 mb-4">
|
||||
Realtime Check-in Velocity
|
||||
<v-chip size="small" color="success" class="ml-2">
|
||||
<v-icon start size="small">mdi-circle</v-icon>
|
||||
Live
|
||||
</v-chip>
|
||||
</v-card-title>
|
||||
<v-card-text class="pa-0">
|
||||
<Line
|
||||
ref="realtimeChart"
|
||||
:data="realtimeLineData"
|
||||
@@ -217,18 +220,33 @@ import {
|
||||
LineElement,
|
||||
} from 'chart.js';
|
||||
|
||||
// Ensure this middleware setup is correct for your framework (e.g., Nuxt)
|
||||
definePageMeta({
|
||||
middleware:['auth']
|
||||
})
|
||||
|
||||
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, ArcElement, PointElement, LineElement);
|
||||
|
||||
// Use the auth composable
|
||||
const { user, isLoading, checkAuth, logout } = useAuth()
|
||||
const isLoggingOut = ref(false)
|
||||
// --- KEYCLOAK/AUTH INTEGRATION ---
|
||||
// const { user, isLoading, checkAuth, logout } = useAuth()
|
||||
// const navigateTo = (path) => { console.log('Navigating to', path); };
|
||||
// Mock user for demo purposes - replace with your actual auth
|
||||
const user = ref({ name: 'Admin User', preferred_username: 'admin' });
|
||||
const isLoading = ref(false);
|
||||
const checkAuth = async () => user.value;
|
||||
const navigateTo = (path) => console.log('Navigate to:', path);
|
||||
// ---------------------------------
|
||||
|
||||
// Dashboard data
|
||||
// --- EXPORT STATE ---
|
||||
const exportType = ref('JSON');
|
||||
const exportOptions = ref([
|
||||
{ title: 'JSON (.json)', type: 'json', icon: 'mdi-code-json' },
|
||||
{ title: 'CSV (.csv)', type: 'csv', icon: 'mdi-file-delimited' },
|
||||
{ title: 'Excel (.xlsx) - Server Only', type: 'excel', icon: 'mdi-file-excel' },
|
||||
{ title: 'PDF (.pdf) - Placeholder', type: 'pdf', icon: 'mdi-file-pdf' },
|
||||
]);
|
||||
// --------------------
|
||||
|
||||
// Dashboard state
|
||||
const currentDate = ref('');
|
||||
const activeYear = ref('2025');
|
||||
const queueTimePeriod = ref('day');
|
||||
@@ -243,74 +261,69 @@ const mockQueueData = ref([
|
||||
{ date: '2025-10-18', registrants: 435, attendees: 380 },
|
||||
]);
|
||||
|
||||
// --- REFACTORED REALTIME LOGIC ---
|
||||
const realtimeChart = ref(null); // Ref untuk mengakses komponen Line
|
||||
// --- REALTIME CHART LOGIC - FIXED ---
|
||||
const realtimeChart = ref(null);
|
||||
const maxDataPoints = 10;
|
||||
let realtimeInterval = null;
|
||||
|
||||
// Initial data structure (non-reactive for update function)
|
||||
const initialLineData = {
|
||||
labels: Array.from({ length: maxDataPoints }, (_, i) =>
|
||||
dayjs().subtract((maxDataPoints - 1 - i) * 5, 'second').format('HH:mm:ss')
|
||||
),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Tickets Processed',
|
||||
backgroundColor: '#FF5722',
|
||||
borderColor: '#FF5722',
|
||||
data: Array.from({ length: maxDataPoints }, () => Math.floor(Math.random() * 50) + 100),
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
},
|
||||
],
|
||||
};
|
||||
const realtimeLineData = ref({
|
||||
labels: Array.from({ length: maxDataPoints }, (_, i) =>
|
||||
dayjs().subtract((maxDataPoints - 1 - i) * 5, 'second').format('HH:mm:ss')
|
||||
),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Check-ins/min',
|
||||
backgroundColor: 'rgba(74, 95, 122, 0.15)',
|
||||
borderColor: '#5FB7FF',
|
||||
data: Array.from({ length: maxDataPoints }, () => Math.floor(Math.random() * 20) + 40),
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
borderWidth: 3,
|
||||
pointRadius: 4,
|
||||
pointHoverRadius: 6,
|
||||
pointBackgroundColor: '#4A5F7A',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const lineOptions = ref({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
duration: 0 // Crucial: Disable animation for smooth realtime scrolling
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: { duration: 300 },
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
title: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
suggestedMax: 80,
|
||||
title: { display: true, text: 'Check-ins per Minute' },
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' }
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
title: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
suggestedMax: 200,
|
||||
title: { display: true, text: 'Count' },
|
||||
grid: { display: true }
|
||||
},
|
||||
x: {
|
||||
title: { display: true, text: 'Time (HH:MM:SS)' },
|
||||
grid: { display: false }
|
||||
}
|
||||
x: {
|
||||
title: { display: true, text: 'Time (HH:MM:SS)' },
|
||||
grid: { display: false }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const updateRealtimeData = () => {
|
||||
const chart = realtimeChart.value?.chart;
|
||||
if (!chart) return;
|
||||
|
||||
// 1. Get the current data arrays
|
||||
const dataArray = chart.data.datasets[0].data;
|
||||
const labelArray = chart.data.labels;
|
||||
|
||||
// 2. Shift (remove) the oldest data point and label
|
||||
dataArray.shift();
|
||||
labelArray.shift();
|
||||
|
||||
// 3. Generate new data point and time label
|
||||
const newDataPoint = Math.floor(Math.random() * 50) + 100;
|
||||
const newTimeLabel = dayjs().format('HH:mm:ss');
|
||||
|
||||
// 4. Push the new data and label
|
||||
dataArray.push(newDataPoint);
|
||||
labelArray.push(newTimeLabel);
|
||||
|
||||
// 5. CRUCIAL: Tell Chart.js to redraw itself without destroying the instance
|
||||
chart.update();
|
||||
const data = realtimeLineData.value.datasets[0].data;
|
||||
const labels = realtimeLineData.value.labels;
|
||||
|
||||
// Remove first data point
|
||||
data.shift();
|
||||
labels.shift();
|
||||
|
||||
// Add new data point
|
||||
const newDataPoint = Math.floor(Math.random() * 20) + 40;
|
||||
const newTimeLabel = dayjs().format('HH:mm:ss');
|
||||
|
||||
data.push(newDataPoint);
|
||||
labels.push(newTimeLabel);
|
||||
|
||||
console.log('Realtime chart updated:', newTimeLabel, newDataPoint);
|
||||
};
|
||||
// --- END REALTIME CHART LOGIC ---
|
||||
|
||||
@@ -318,13 +331,13 @@ onMounted(async () => {
|
||||
try {
|
||||
const sessionUser = await checkAuth()
|
||||
if (sessionUser) {
|
||||
// Set current date
|
||||
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
|
||||
currentDate.value = new Date().toLocaleDateString('id-ID', options);
|
||||
|
||||
// Start realtime data update interval
|
||||
// Pastikan chart instance sudah siap sebelum memulai interval
|
||||
realtimeInterval = setInterval(updateRealtimeData, 5000);
|
||||
// Set date with Indonesian locale
|
||||
currentDate.value = dayjs().format('dddd, DD MMMM YYYY');
|
||||
console.log('Current date set:', currentDate.value);
|
||||
|
||||
// Start realtime updates every 5 seconds
|
||||
realtimeInterval = setInterval(updateRealtimeData, 5000);
|
||||
console.log('Realtime interval started');
|
||||
} else {
|
||||
await navigateTo('/LoginPage');
|
||||
}
|
||||
@@ -337,25 +350,10 @@ onMounted(async () => {
|
||||
onUnmounted(() => {
|
||||
if (realtimeInterval) {
|
||||
clearInterval(realtimeInterval);
|
||||
console.log('Realtime interval cleared');
|
||||
}
|
||||
});
|
||||
|
||||
// Updated logout handler (Tetap)
|
||||
const handleLogout = async () => {
|
||||
if (isLoggingOut.value) return
|
||||
|
||||
try {
|
||||
isLoggingOut.value = true
|
||||
console.log('🚪 Dashboard logout initiated...')
|
||||
await logout()
|
||||
} catch (error) {
|
||||
console.error('❌ Dashboard logout error:', error)
|
||||
} finally {
|
||||
isLoggingOut.value = false
|
||||
}
|
||||
};
|
||||
|
||||
// Function to change the active year (Tetap)
|
||||
const changeYear = (year) => {
|
||||
activeYear.value = year;
|
||||
};
|
||||
|
||||
@@ -1,635 +0,0 @@
|
||||
<template>
|
||||
<div class="pasien-container">
|
||||
<!-- Header Section -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-account-group</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">List Pasien</h1>
|
||||
<p class="page-subtitle">Senin, 15 September 2025 - Data Master Pasien</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<v-chip
|
||||
color="success"
|
||||
variant="flat"
|
||||
class="mr-2"
|
||||
>
|
||||
Total {{ pasienData.length }} Pasien
|
||||
</v-chip>
|
||||
<v-chip
|
||||
color="white"
|
||||
variant="flat"
|
||||
class="text-primary"
|
||||
>
|
||||
Status: Aktif
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Controls
|
||||
<v-card class="filter-controls-card mb-4" elevation="2">
|
||||
<v-card-text class="py-4">
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="8">
|
||||
<div class="d-flex align-center flex-wrap gap-10">
|
||||
<span class="text-subtitle-1 font-weight-medium">Filter Cepat:</span>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
size="default"
|
||||
class="px-4"
|
||||
@click="filterByStatus('Tunggu Daftar')"
|
||||
>
|
||||
<v-icon start size="16">mdi-clock-outline</v-icon>
|
||||
Tunggu Daftar
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
size="default"
|
||||
class="px-4"
|
||||
@click="filterByStatus('Selesai')"
|
||||
>
|
||||
<v-icon start size="16">mdi-check-circle</v-icon>
|
||||
Selesai
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
size="default"
|
||||
class="px-4"
|
||||
@click="filterByKlinik('JKN')"
|
||||
>
|
||||
<v-icon start size="16">mdi-card-account-details</v-icon>
|
||||
JKN
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
size="default"
|
||||
class="px-4"
|
||||
@click="resetFilter()"
|
||||
>
|
||||
<v-icon start size="16">mdi-refresh</v-icon>
|
||||
Reset
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<div class="d-flex justify-end gap-2">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="handleExportLaporan"
|
||||
:loading="loading"
|
||||
>
|
||||
<v-icon start>mdi-file-excel</v-icon>
|
||||
Export Laporan
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
@click="handleExportLaporanPerKlinik"
|
||||
:loading="loading"
|
||||
>
|
||||
<v-icon start>mdi-hospital-building</v-icon>
|
||||
Export Per Klinik
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card> -->
|
||||
|
||||
<!-- Main Patients Table -->
|
||||
<v-card class="main-table-card mb-4" elevation="2">
|
||||
<v-card-title class="d-flex align-center justify-space-between pa-6">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon color="primary" class="mr-2">mdi-table</v-icon>
|
||||
<span class="text-h6 font-weight-bold">DATA PASIEN</span>
|
||||
</div>
|
||||
<v-chip color="info" variant="flat">
|
||||
{{ filteredData.length }} dari {{ pasienData.length }} pasien
|
||||
</v-chip>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<TabelListPasien
|
||||
:items="filteredData"
|
||||
@search="handleSearch"
|
||||
@export-laporan="handleExportLaporan"
|
||||
@export-laporan-per-klinik="handleExportLaporanPerKlinik"
|
||||
/>
|
||||
</v-card>
|
||||
|
||||
Statistics Cards
|
||||
<v-row class="mb-4">
|
||||
<v-col cols="12" md="3">
|
||||
<v-card class="stats-card" elevation="2">
|
||||
<v-card-text class="text-center pa-4">
|
||||
<v-icon size="40" color="success" class="mb-2">mdi-account-check</v-icon>
|
||||
<div class="text-h4 font-weight-bold text-success">{{ getStatsByStatus('Tunggu Daftar') }}</div>
|
||||
<div class="text-body-2 text-grey-darken-1">Tunggu Daftar</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-card class="stats-card" elevation="2">
|
||||
<v-card-text class="text-center pa-4">
|
||||
<v-icon size="40" color="info" class="mb-2">mdi-barcode</v-icon>
|
||||
<div class="text-h4 font-weight-bold text-info">{{ getStatsByStatus('Barcode') }}</div>
|
||||
<div class="text-body-2 text-grey-darken-1">Barcode</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-card class="stats-card" elevation="2">
|
||||
<v-card-text class="text-center pa-4">
|
||||
<v-icon size="40" color="warning" class="mb-2">mdi-wifi</v-icon>
|
||||
<div class="text-h4 font-weight-bold text-warning">{{ getStatsByKeterangan('Online') }}</div>
|
||||
<div class="text-body-2 text-grey-darken-1">Online</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-card class="stats-card" elevation="2">
|
||||
<v-card-text class="text-center pa-4">
|
||||
<v-icon size="40" color="error" class="mb-2">mdi-wifi-off</v-icon>
|
||||
<div class="text-h4 font-weight-bold text-error">{{ getStatsByKeterangan('Offline') }}</div>
|
||||
<div class="text-body-2 text-grey-darken-1">Offline</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<v-overlay v-model="loading" class="align-center justify-center">
|
||||
<v-progress-circular
|
||||
color="primary"
|
||||
indeterminate
|
||||
size="64"
|
||||
/>
|
||||
</v-overlay>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
:timeout="3000"
|
||||
location="top right"
|
||||
>
|
||||
{{ snackbar.message }}
|
||||
<template v-slot:actions>
|
||||
<v-btn
|
||||
icon
|
||||
@click="snackbar.show = false"
|
||||
>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import TabelListPasien from '~/components/TabelListPasien.vue'
|
||||
|
||||
// Meta untuk SEO
|
||||
definePageMeta({
|
||||
title: 'List Pasien',
|
||||
layout: 'default'
|
||||
})
|
||||
|
||||
// Reactive states
|
||||
const loading = ref(false)
|
||||
const currentFilter = ref('')
|
||||
const snackbar = ref({
|
||||
show: false,
|
||||
message: '',
|
||||
color: 'success'
|
||||
})
|
||||
|
||||
// Sample data
|
||||
const pasienData = ref([
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '3507264104730004',
|
||||
rm: '11412584',
|
||||
barcode: '250627100001',
|
||||
noAntrian: 'HQ1001',
|
||||
klinik: 'HOM',
|
||||
fullName: 'Binti Almatul',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Online',
|
||||
status: 'Tunggu Daftar'
|
||||
},
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '3504096063630001',
|
||||
rm: '',
|
||||
barcode: '250627100002',
|
||||
noAntrian: 'QB1001',
|
||||
klinik: 'KANDUNGAN',
|
||||
fullName: 'maret kumalal',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Online',
|
||||
status: 'Tunggu Daftar'
|
||||
},
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '3507114102250002',
|
||||
rm: '11555560',
|
||||
barcode: '250627100003',
|
||||
noAntrian: 'QB1002',
|
||||
klinik: 'KANDUNGAN',
|
||||
fullName: 'ayu rafti lelu amanda',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Online',
|
||||
status: 'Tunggu Daftar'
|
||||
},
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '3508185040150002',
|
||||
rm: '11333655',
|
||||
barcode: '250627100004',
|
||||
noAntrian: 'AN1001',
|
||||
klinik: 'ANAK',
|
||||
fullName: 'Erin Wahyuni',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Online',
|
||||
status: 'Tunggu Daftar'
|
||||
},
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '3515085040110004',
|
||||
rm: '11585554',
|
||||
barcode: '250627100005',
|
||||
noAntrian: 'IP1001',
|
||||
klinik: 'IPD',
|
||||
fullName: 'Yohana Karina Pusplta Sari',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Online',
|
||||
status: 'Tunggu Daftar'
|
||||
},
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '3506246105750002',
|
||||
rm: '11527608',
|
||||
barcode: '250627100006',
|
||||
noAntrian: 'IP1001',
|
||||
klinik: 'IPD',
|
||||
fullName: 'Elok Suharsti',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Online',
|
||||
status: 'Tunggu Daftar'
|
||||
},
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '',
|
||||
rm: '',
|
||||
barcode: '250627100007',
|
||||
noAntrian: 'HQ1002',
|
||||
klinik: 'HOM',
|
||||
fullName: '',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Offline',
|
||||
status: 'Barcode'
|
||||
},
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '',
|
||||
rm: '',
|
||||
barcode: '250627100008',
|
||||
noAntrian: 'IP1002',
|
||||
klinik: 'IPD',
|
||||
fullName: '',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Offline',
|
||||
status: 'Barcode'
|
||||
},
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '',
|
||||
rm: '',
|
||||
barcode: '250627100009',
|
||||
noAntrian: 'IP1001',
|
||||
klinik: 'IPD',
|
||||
fullName: '',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Offline',
|
||||
status: 'Barcode'
|
||||
},
|
||||
{
|
||||
tglPeriksa: '27/08/2025',
|
||||
nik: '',
|
||||
rm: '',
|
||||
barcode: '250627100010',
|
||||
noAntrian: 'IP1001',
|
||||
klinik: 'IPD',
|
||||
fullName: '',
|
||||
shift: 'Shift 1',
|
||||
pembayaran: 'JKN',
|
||||
keterangan: 'Offline',
|
||||
status: 'Barcode'
|
||||
}
|
||||
])
|
||||
|
||||
// Computed properties
|
||||
const filteredData = computed(() => {
|
||||
if (!currentFilter.value) {
|
||||
return pasienData.value
|
||||
}
|
||||
|
||||
return pasienData.value.filter(item => {
|
||||
return item.status === currentFilter.value ||
|
||||
item.pembayaran === currentFilter.value ||
|
||||
item.keterangan === currentFilter.value
|
||||
})
|
||||
})
|
||||
|
||||
// Methods
|
||||
const showSnackbar = (message, color = 'success') => {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message,
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
const filterByStatus = (status) => {
|
||||
currentFilter.value = status
|
||||
showSnackbar(`Filter diterapkan: ${status}`, 'info')
|
||||
}
|
||||
|
||||
const filterByKlinik = (pembayaran) => {
|
||||
currentFilter.value = pembayaran
|
||||
showSnackbar(`Filter diterapkan: ${pembayaran}`, 'info')
|
||||
}
|
||||
|
||||
const resetFilter = () => {
|
||||
currentFilter.value = ''
|
||||
showSnackbar('Filter direset', 'success')
|
||||
}
|
||||
|
||||
const getStatsByStatus = (status) => {
|
||||
return pasienData.value.filter(item => item.status === status).length
|
||||
}
|
||||
|
||||
const getStatsByKeterangan = (keterangan) => {
|
||||
return pasienData.value.filter(item => item.keterangan === keterangan).length
|
||||
}
|
||||
|
||||
const handleSearch = async (filters) => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
console.log('Search filters:', filters)
|
||||
showSnackbar('Data berhasil difilter', 'success')
|
||||
} catch (error) {
|
||||
console.error('Error searching data:', error)
|
||||
showSnackbar('Gagal memfilter data', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleExportLaporan = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
console.log('Exporting laporan pasien...')
|
||||
showSnackbar('Laporan pasien berhasil diexport', 'success')
|
||||
} catch (error) {
|
||||
console.error('Error exporting laporan:', error)
|
||||
showSnackbar('Gagal export laporan pasien', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleExportLaporanPerKlinik = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
console.log('Exporting laporan pasien per klinik...')
|
||||
showSnackbar('Laporan pasien per klinik berhasil diexport', 'success')
|
||||
} catch (error) {
|
||||
console.error('Error exporting laporan per klinik:', error)
|
||||
showSnackbar('Gagal export laporan pasien per klinik', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const fetchPasienData = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
console.log('Patient data loaded successfully')
|
||||
} catch (error) {
|
||||
console.error('Error fetching patient data:', error)
|
||||
showSnackbar('Gagal memuat data pasien', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
fetchPasienData()
|
||||
})
|
||||
|
||||
// Head untuk SEO
|
||||
useHead({
|
||||
title: 'List Pasien - Antrean RSSA',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Daftar master data seluruh pasien rumah sakit'
|
||||
}
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pasien-container {
|
||||
background: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-right: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* .filter-controls-card, */
|
||||
.main-table-card,
|
||||
.stats-card {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.stats-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Enhanced table styling */
|
||||
.main-table-card :deep(.v-data-table th) {
|
||||
background: #fafbfc;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.main-table-card :deep(.v-data-table tbody tr:hover) {
|
||||
background: #f8fafc !important;
|
||||
}
|
||||
|
||||
.main-table-card :deep(.v-data-table tbody td) {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
/* Button styling */
|
||||
.v-btn {
|
||||
text-transform: none !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.v-btn--size-default {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 1024px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pasien-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* .filter-controls-card .d-flex {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
} */
|
||||
|
||||
.filter-controls-card .v-col:last-child .d-flex {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.filter-buttons-container {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
margin-right: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header-right .v-chip {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,491 +0,0 @@
|
||||
<template>
|
||||
<div class="master-klinik-page pa-4">
|
||||
<!-- Breadcrumbs -->
|
||||
<v-breadcrumbs :items="breadcrumbs" class="pl-0">
|
||||
<template v-slot:divider>
|
||||
<v-icon icon="mdi-chevron-right"></v-icon>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
|
||||
<!-- Tampilan Formulir Tambah/Edit/View -->
|
||||
<v-card v-if="showForm" class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-title class="text-h5 font-weight-bold mb-4">
|
||||
{{ isEditMode ? 'Edit Klinik' : readOnly ? 'Detail Klinik' : 'Tambah Klinik' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Kode</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.kode"
|
||||
placeholder="Masukkan Kode Klinik"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Nama</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.namaKlinik"
|
||||
placeholder="Masukkan Nama Klinik"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Shift</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.shift"
|
||||
placeholder="Jumlah Shift"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Kuota Shift</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.kuotaShift"
|
||||
placeholder="Kuota Per Shift"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="d-flex align-center">
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Jam Buka Shift</v-label>
|
||||
<div class="d-flex align-center">
|
||||
<v-text-field
|
||||
v-model="editedItem.jamBuka"
|
||||
placeholder="Jam Buka Shift 1"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mr-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
<span class="text-h6 font-weight-bold mr-2">:</span>
|
||||
<v-text-field
|
||||
v-model="editedItem.menitBuka"
|
||||
placeholder="Menit Buka Shift 1"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-switch
|
||||
v-model="editedItem.autoShift"
|
||||
inset
|
||||
color="primary"
|
||||
label="Auto Shift"
|
||||
hide-details
|
||||
:readonly="readOnly"
|
||||
></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-label class="font-weight-bold">Kuota Bangku</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.kuotaBangku"
|
||||
placeholder="Masukkan Kuota Bangku Klinik"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-label class="font-weight-bold">Jadwal Klinik</v-label>
|
||||
<v-table class="rounded-lg elevation-1 mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left text-uppercase font-weight-bold">#</th>
|
||||
<th class="text-left text-uppercase font-weight-bold">Hari</th>
|
||||
<th class="text-left text-uppercase font-weight-bold">Pilih</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(day, index) in days" :key="index">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ day.name }}</td>
|
||||
<td>
|
||||
<v-checkbox
|
||||
v-model="day.selected"
|
||||
color="primary"
|
||||
hide-details
|
||||
:readonly="readOnly"
|
||||
></v-checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions class="d-flex justify-start pa-4">
|
||||
<v-btn
|
||||
color="grey-darken-1"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg mr-2"
|
||||
@click="cancelForm"
|
||||
>
|
||||
{{ readOnly ? 'Tutup' : 'Batal' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!readOnly"
|
||||
color="orange-darken-2"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg"
|
||||
@click="saveItem"
|
||||
>
|
||||
Submit
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
||||
<!-- Tampilan Tabel Data -->
|
||||
<div v-else>
|
||||
<!-- Banner biru sebagai pengganti header h1 -->
|
||||
<v-card class="d-flex justify-space-between align-center pa-4 mb-4 rounded-lg bg-blue-darken-2 text-white elevation-2">
|
||||
<v-card-title class="d-flex align-center text-h5 font-weight-bold pa-0">
|
||||
<v-icon icon="mdi-hospital" class="mr-2" size="40"></v-icon>
|
||||
<span>Master Klinik</span>
|
||||
</v-card-title>
|
||||
<v-btn
|
||||
color="success"
|
||||
prepend-icon="mdi-plus"
|
||||
rounded
|
||||
class="text-capitalize"
|
||||
@click="showForm = true"
|
||||
>
|
||||
Tambah Baru
|
||||
</v-btn>
|
||||
</v-card>
|
||||
|
||||
<v-card class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-text>
|
||||
<!-- Table controls -->
|
||||
<div class="d-flex flex-wrap align-center justify-space-between mb-4">
|
||||
<div class="d-flex align-center">
|
||||
<span class="mr-2 text-subtitle-1">Show</span>
|
||||
<v-select
|
||||
v-model="itemsPerPage"
|
||||
:items="[10, 25, 50]"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 80px;"
|
||||
rounded
|
||||
class="mr-2"
|
||||
></v-select>
|
||||
<span class="text-subtitle-1">entries</span>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
rounded
|
||||
clearable
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table dengan paginasi kustom -->
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="allKlinikData"
|
||||
:search="search"
|
||||
:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
class="rounded-lg elevation-0 custom-table"
|
||||
>
|
||||
<!-- Slot untuk aksi di setiap baris -->
|
||||
<template v-slot:[`item.actions`]="{ item }">
|
||||
<!-- Tombol "View" yang hanya menampilkan detail -->
|
||||
<v-btn icon color="blue" size="small" class="mr-2" @click="viewItem(item)">
|
||||
<v-icon>mdi-eye</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="orange" size="small" class="mr-2" @click="editItem(item)">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="red" size="small" @click="deleteItem(item)">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<template v-slot:no-data>
|
||||
<v-alert :value="true" color="grey-lighten-3" icon="mdi-information">
|
||||
Tidak ada data yang tersedia.
|
||||
</v-alert>
|
||||
</template>
|
||||
|
||||
<!-- Slot kustom untuk footer tabel (paginasi) -->
|
||||
<template v-slot:bottom>
|
||||
<v-row class="ma-2">
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-start text-caption text-grey">
|
||||
{{ showingEntriesText }}
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-end">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageCount"
|
||||
rounded="circle"
|
||||
:total-visible="5"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Dialog -->
|
||||
<v-dialog v-model="showDeleteDialog" max-width="400px">
|
||||
<v-card class="pa-4 rounded-lg">
|
||||
<v-card-title class="text-h6 font-weight-bold">Hapus Data</v-card-title>
|
||||
<v-card-text>Apakah Anda yakin ingin menghapus data ini?</v-card-text>
|
||||
<v-card-actions class="d-flex justify-end">
|
||||
<v-btn color="grey-darken-1" variant="text" @click="closeDeleteDialog">Batal</v-btn>
|
||||
<v-btn color="red" variant="text" @click="confirmDelete">Hapus</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
definePageMeta({
|
||||
middleware:['auth']
|
||||
})
|
||||
|
||||
// State untuk menampilkan/menyembunyikan formulir
|
||||
const showForm = ref(false);
|
||||
const readOnly = ref(false); // State untuk mode "view"
|
||||
|
||||
// Data dummy untuk Master Klinik
|
||||
const allKlinikData = ref([
|
||||
{ id: 1, kode: 'AN', namaKlinik: 'ANAK', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 2, kode: 'AS', namaKlinik: 'ANESTESI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 3, kode: 'BD', namaKlinik: 'BEDAH', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 4, kode: 'GR', namaKlinik: 'GERIATRI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 5, kode: 'GI', namaKlinik: 'GIGI DAN MULUT', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 6, kode: 'GZ', namaKlinik: 'GIZI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 7, kode: 'HO', namaKlinik: 'HOM', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 8, kode: 'IP', namaKlinik: 'IPD', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 9, kode: 'JT', namaKlinik: 'JANTUNG', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 10, kode: 'JW', namaKlinik: 'JIWA', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 11, kode: 'OB', namaKlinik: 'KANDUNGAN', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 12, kode: 'KT', namaKlinik: 'KEMOTERAPI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 13, kode: 'KN', namaKlinik: 'KOMPLEMENTER', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 14, kode: 'KL', namaKlinik: 'KUL KEL', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 15, kode: 'MT', namaKlinik: 'MATA', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 16, kode: 'MC', namaKlinik: 'MCU', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 17, kode: 'ON', namaKlinik: 'ONKOLOGI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 18, kode: 'PR', namaKlinik: 'PARU', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 19, kode: 'RT', namaKlinik: 'R. TINDAKAN', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 20, kode: 'RD', namaKlinik: 'RADIOTERAPI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 21, kode: 'RM', namaKlinik: 'REHAB MEDIK', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 22, kode: 'NU', namaKlinik: 'SARAF', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 23, kode: 'TH', namaKlinik: 'THT', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
]);
|
||||
|
||||
const headers = ref([
|
||||
{ title: 'No', key: 'id' },
|
||||
{ title: 'Kode', key: 'kode', sortable: true },
|
||||
{ title: 'Nama Klinik', key: 'namaKlinik', sortable: true },
|
||||
{ title: 'Shift', key: 'shift', sortable: true },
|
||||
{ title: 'Kuota Shift', key: 'kuotaShift', sortable: true },
|
||||
{ title: 'Aksi', key: 'actions', sortable: false },
|
||||
]);
|
||||
|
||||
// Breadcrumbs
|
||||
const breadcrumbs = ref([
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik', disabled: false, href: '/setting/masterklinik' },
|
||||
]);
|
||||
|
||||
const days = ref([
|
||||
{ name: 'Senin', selected: false },
|
||||
{ name: 'Selasa', selected: false },
|
||||
{ name: 'Rabu', selected: false },
|
||||
{ name: 'Kamis', selected: false },
|
||||
{ name: 'Jum`at', selected: false },
|
||||
]);
|
||||
|
||||
// State untuk tabel dan paginasi
|
||||
const itemsPerPage = ref(10);
|
||||
const search = ref('');
|
||||
const page = ref(1);
|
||||
|
||||
// Computed properties untuk paginasi kustom
|
||||
const pageCount = computed(() => {
|
||||
return Math.ceil(allKlinikData.value.length / itemsPerPage.value);
|
||||
});
|
||||
|
||||
const showingEntriesText = computed(() => {
|
||||
const start = (page.value - 1) * itemsPerPage.value + 1;
|
||||
const end = Math.min(page.value * itemsPerPage.value, allKlinikData.value.length);
|
||||
const total = allKlinikData.value.length;
|
||||
return `Showing ${start} to ${end} of ${total} entries`;
|
||||
});
|
||||
|
||||
// State untuk dialog
|
||||
const showDeleteDialog = ref(false);
|
||||
const isEditMode = ref(false);
|
||||
const editedIndex = ref(-1);
|
||||
const editedItem = ref({
|
||||
id: 0,
|
||||
kode: '',
|
||||
namaKlinik: '',
|
||||
shift: 1,
|
||||
kuotaShift: 0,
|
||||
kuotaBangku: 0,
|
||||
jamBuka: '',
|
||||
menitBuka: '',
|
||||
autoShift: false,
|
||||
});
|
||||
|
||||
|
||||
// Fungsi untuk tombol aksi
|
||||
// Fungsi untuk tombol View yang hanya menampilkan data tanpa bisa diedit
|
||||
const viewItem = (item) => {
|
||||
editedItem.value = { ...item };
|
||||
readOnly.value = true;
|
||||
isEditMode.value = false;
|
||||
showForm.value = true;
|
||||
// Update breadcrumbs untuk mode view
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik', disabled: false, href: '/setting/masterklinik' },
|
||||
{ title: 'Detail Klinik', disabled: true, href: '/setting/viewklinik' },
|
||||
];
|
||||
};
|
||||
|
||||
const editItem = (item) => {
|
||||
editedIndex.value = allKlinikData.value.findIndex(d => d.id === item.id);
|
||||
editedItem.value = { ...item };
|
||||
isEditMode.value = true;
|
||||
readOnly.value = false;
|
||||
showForm.value = true;
|
||||
// Update breadcrumbs untuk mode edit
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik', disabled: false, href: '/setting/masterklinik' },
|
||||
{ title: 'Edit Klinik', disabled: true, href: '/setting/editklinik' },
|
||||
];
|
||||
};
|
||||
|
||||
const deleteItem = (item) => {
|
||||
editedIndex.value = allKlinikData.value.findIndex(d => d.id === item.id);
|
||||
showDeleteDialog.value = true;
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (editedIndex.value > -1) {
|
||||
allKlinikData.value.splice(editedIndex.value, 1);
|
||||
}
|
||||
closeDeleteDialog();
|
||||
};
|
||||
|
||||
const cancelForm = () => {
|
||||
showForm.value = false;
|
||||
isEditMode.value = false;
|
||||
readOnly.value = false;
|
||||
editedItem.value = {
|
||||
id: 0,
|
||||
kode: '',
|
||||
namaKlinik: '',
|
||||
shift: 1,
|
||||
kuotaShift: 0,
|
||||
kuotaBangku: 0,
|
||||
jamBuka: '',
|
||||
menitBuka: '',
|
||||
autoShift: false,
|
||||
};
|
||||
editedIndex.value = -1;
|
||||
// Reset breadcrumbs ke mode tabel
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik', disabled: false, href: '/setting/masterklinik' },
|
||||
];
|
||||
};
|
||||
|
||||
const closeDeleteDialog = () => {
|
||||
showDeleteDialog.value = false;
|
||||
editedIndex.value = -1;
|
||||
};
|
||||
|
||||
const saveItem = () => {
|
||||
if (isEditMode.value) {
|
||||
Object.assign(allKlinikData.value[editedIndex.value], editedItem.value);
|
||||
} else {
|
||||
// Generate new ID
|
||||
const newId = allKlinikData.value.length > 0 ? Math.max(...allKlinikData.value.map(item => item.id)) + 1 : 1;
|
||||
editedItem.value.id = newId;
|
||||
allKlinikData.value.push(editedItem.value);
|
||||
}
|
||||
cancelForm(); // Kembali ke tampilan tabel setelah menyimpan
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.master-klinik-page {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.custom-table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.v-data-table :deep(th) {
|
||||
font-weight: bold !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
.v-data-table :deep(td) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.v-btn--icon {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.v-select :deep(.v-field__input) {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,376 +0,0 @@
|
||||
<template>
|
||||
<div class="master-klinik-ruang-page pa-4">
|
||||
<!-- Breadcrumbs -->
|
||||
<v-breadcrumbs :items="breadcrumbs" class="pl-0">
|
||||
<template v-slot:divider>
|
||||
<v-icon icon="mdi-chevron-right"></v-icon>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
|
||||
<!-- Tampilan Formulir Tambah/Edit/View -->
|
||||
<v-card v-if="showForm" class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-title class="text-h5 font-weight-bold mb-4">
|
||||
{{ isEditMode ? 'Edit Ruang Klinik' : readOnly ? 'Detail Ruang Klinik' : 'Tambah Ruang Klinik' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Nama Klinik</v-label>
|
||||
<v-select
|
||||
v-model="editedItem.namaKlinik"
|
||||
:items="['ANAK', 'ANESTESI', 'BEDAH', 'GERIATRI', 'GIGI DAN MULUT', 'GIZI', 'HOM', 'IPD', 'JANTUNG', 'JIWA', 'KANDUNGAN', 'KEMOTERAPI', 'KOMPLEMENTER', 'KUL KEL', 'MATA', 'MCU', 'ONKOLOGI', 'PARU', 'R. TINDAKAN', 'RADIOTERAPI', 'REHAB MEDIK', 'SARAF', 'THT']"
|
||||
placeholder="Pilih Nama Klinik"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Kode Ruang</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.kode"
|
||||
placeholder="Masukkan Kode Ruang"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-label class="font-weight-bold">Nama Ruang</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.namaRuang"
|
||||
placeholder="Masukkan Nama Ruang"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions class="d-flex justify-start pa-4">
|
||||
<v-btn
|
||||
color="grey-darken-1"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg mr-2"
|
||||
@click="cancelForm"
|
||||
>
|
||||
{{ readOnly ? 'Tutup' : 'Batal' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!readOnly"
|
||||
color="orange-darken-2"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg"
|
||||
@click="saveItem"
|
||||
>
|
||||
Submit
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
||||
<!-- Tampilan Tabel Data -->
|
||||
<div v-else>
|
||||
<!-- Banner biru sebagai pengganti header h1 -->
|
||||
<v-card class="d-flex justify-space-between align-center pa-4 mb-4 rounded-lg bg-blue-darken-2 text-white elevation-2">
|
||||
<v-card-title class="d-flex align-center text-h5 font-weight-bold pa-0">
|
||||
<v-icon icon="mdi-hospital-box-outline" class="mr-2" size="40"></v-icon>
|
||||
<span>Master Klinik Ruang</span>
|
||||
</v-card-title>
|
||||
<v-btn
|
||||
color="success"
|
||||
prepend-icon="mdi-plus"
|
||||
rounded
|
||||
class="text-capitalize"
|
||||
@click="showForm = true"
|
||||
>
|
||||
Tambah Baru
|
||||
</v-btn>
|
||||
</v-card>
|
||||
|
||||
<v-card class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-text>
|
||||
<!-- Table controls -->
|
||||
<div class="d-flex flex-wrap align-center justify-space-between mb-4">
|
||||
<div class="d-flex align-center">
|
||||
<span class="mr-2 text-subtitle-1">Show</span>
|
||||
<v-select
|
||||
v-model="itemsPerPage"
|
||||
:items="[10, 25, 50]"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 80px;"
|
||||
rounded
|
||||
class="mr-2"
|
||||
></v-select>
|
||||
<span class="text-subtitle-1">entries</span>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
rounded
|
||||
clearable
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table dengan paginasi kustom -->
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="allRuangData"
|
||||
:search="search"
|
||||
:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
class="rounded-lg elevation-0 custom-table"
|
||||
>
|
||||
<!-- Slot untuk aksi di setiap baris -->
|
||||
<template v-slot:[`item.actions`]="{ item }">
|
||||
<v-btn icon color="blue" size="small" class="mr-2" @click="viewItem(item)">
|
||||
<v-icon>mdi-eye</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="orange" size="small" class="mr-2" @click="editItem(item)">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="red" size="small" @click="deleteItem(item)">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<template v-slot:no-data>
|
||||
<v-alert :value="true" color="grey-lighten-3" icon="mdi-information">
|
||||
Tidak ada data yang tersedia.
|
||||
</v-alert>
|
||||
</template>
|
||||
|
||||
<!-- Slot kustom untuk footer tabel (paginasi) -->
|
||||
<template v-slot:bottom>
|
||||
<v-row class="ma-2">
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-start text-caption text-grey">
|
||||
{{ showingEntriesText }}
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-end">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageCount"
|
||||
rounded="circle"
|
||||
:total-visible="5"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Dialog -->
|
||||
<v-dialog v-model="showDeleteDialog" max-width="400px">
|
||||
<v-card class="pa-4 rounded-lg">
|
||||
<v-card-title class="text-h6 font-weight-bold">Hapus Data</v-card-title>
|
||||
<v-card-text>Apakah Anda yakin ingin menghapus data ini?</v-card-text>
|
||||
<v-card-actions class="d-flex justify-end">
|
||||
<v-btn color="grey-darken-1" variant="text" @click="closeDeleteDialog">Batal</v-btn>
|
||||
<v-btn color="red" variant="text" @click="confirmDelete">Hapus</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
definePageMeta({
|
||||
middleware:['auth']
|
||||
})
|
||||
|
||||
// State untuk menampilkan/menyembunyikan formulir
|
||||
const showForm = ref(false);
|
||||
const readOnly = ref(false);
|
||||
|
||||
// Data dummy untuk Master Klinik Ruang
|
||||
const allRuangData = ref([
|
||||
{ id: 1, namaKlinik: 'ANAK', kode: 'AN-01', namaRuang: 'RUANG ANAK 1' },
|
||||
{ id: 2, namaKlinik: 'ANAK', kode: 'AN-02', namaRuang: 'RUANG ANAK 2' },
|
||||
{ id: 3, namaKlinik: 'ANESTESI', kode: 'AS-01', namaRuang: 'RUANG ANESTESI 1' },
|
||||
{ id: 4, namaKlinik: 'BEDAH', kode: 'BD-01', namaRuang: 'RUANG BEDAH 1' },
|
||||
{ id: 5, namaKlinik: 'GIGI DAN MULUT', kode: 'GI-01', namaRuang: 'RUANG GIGI DAN MULUT 1' },
|
||||
{ id: 6, namaKlinik: 'GERIATRI', kode: 'GR-01', namaRuang: 'RUANG GERIATRI 1' },
|
||||
{ id: 7, namaKlinik: 'GIZI', kode: 'GZ-01', namaRuang: 'RUANG GIZI 1' },
|
||||
{ id: 8, namaKlinik: 'HOM', kode: 'HO-01', namaRuang: 'RUANG HOM 1' },
|
||||
{ id: 9, namaKlinik: 'IPD', kode: 'IP-01', namaRuang: 'RUANG IPD 1' },
|
||||
{ id: 10, namaKlinik: 'JANTUNG', kode: 'JT-01', namaRuang: 'RUANG JANTUNG 1' },
|
||||
{ id: 11, namaKlinik: 'JIWA', kode: 'JW-01', namaRuang: 'RUANG JIWA 1' },
|
||||
{ id: 12, namaKlinik: 'KANDUNGAN', kode: 'OB-01', namaRuang: 'RUANG KANDUNGAN 1' },
|
||||
{ id: 13, namaKlinik: 'KANDUNGAN', kode: 'OB-02', namaRuang: 'RUANG KANDUNGAN 2' },
|
||||
{ id: 14, namaKlinik: 'KEMOTERAPI', kode: 'KT-01', namaRuang: 'RUANG KEMOTERAPI 1' },
|
||||
{ id: 15, namaKlinik: 'KOMPLEMENTER', kode: 'KN-01', namaRuang: 'RUANG KOMPLEMENTER 1' },
|
||||
{ id: 16, namaKlinik: 'KUL KEL', kode: 'KL-01', namaRuang: 'RUANG KUL KEL 1' },
|
||||
{ id: 17, namaKlinik: 'MATA', kode: 'MT-01', namaRuang: 'RUANG MATA 1' },
|
||||
{ id: 18, namaKlinik: 'MCU', kode: 'MC-01', namaRuang: 'RUANG MCU 1' },
|
||||
{ id: 19, namaKlinik: 'ONKOLOGI', kode: 'ON-01', namaRuang: 'RUANG ONKOLOGI 1' },
|
||||
{ id: 20, namaKlinik: 'PARU', kode: 'PR-01', namaRuang: 'RUANG PARU 1' },
|
||||
{ id: 21, namaKlinik: 'R. TINDAKAN', kode: 'RT-01', namaRuang: 'RUANG R. TINDAKAN 1' },
|
||||
{ id: 22, namaKlinik: 'RADIOTERAPI', kode: 'RD-01', namaRuang: 'RUANG RADIOTERAPI 1' },
|
||||
{ id: 23, namaKlinik: 'REHAB MEDIK', kode: 'RM-01', namaRuang: 'RUANG REHAB MEDIK 1' },
|
||||
{ id: 24, namaKlinik: 'SARAF', kode: 'NU-01', namaRuang: 'RUANG SARAF 1' },
|
||||
{ id: 25, namaKlinik: 'THT', kode: 'TH-01', namaRuang: 'RUANG THT 1' },
|
||||
]);
|
||||
|
||||
const headers = ref([
|
||||
{ title: 'No', key: 'id' },
|
||||
{ title: 'Nama Klinik', key: 'namaKlinik', sortable: true },
|
||||
{ title: 'Kode Ruang', key: 'kode', sortable: true },
|
||||
{ title: 'Nama Ruang', key: 'namaRuang', sortable: true },
|
||||
{ title: 'Aksi', key: 'actions', sortable: false },
|
||||
]);
|
||||
|
||||
// Breadcrumbs
|
||||
const breadcrumbs = ref([
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
]);
|
||||
|
||||
// State untuk tabel dan paginasi
|
||||
const itemsPerPage = ref(10);
|
||||
const search = ref('');
|
||||
const page = ref(1);
|
||||
|
||||
// Computed properties untuk paginasi kustom
|
||||
const pageCount = computed(() => {
|
||||
return Math.ceil(allRuangData.value.length / itemsPerPage.value);
|
||||
});
|
||||
|
||||
const showingEntriesText = computed(() => {
|
||||
const start = (page.value - 1) * itemsPerPage.value + 1;
|
||||
const end = Math.min(page.value * itemsPerPage.value, allRuangData.value.length);
|
||||
const total = allRuangData.value.length;
|
||||
return `Showing ${start} to ${end} of ${total} entries`;
|
||||
});
|
||||
|
||||
// State untuk dialog
|
||||
const showDeleteDialog = ref(false);
|
||||
const isEditMode = ref(false);
|
||||
const editedIndex = ref(-1);
|
||||
const editedItem = ref({
|
||||
id: 0,
|
||||
namaKlinik: '',
|
||||
kode: '',
|
||||
namaRuang: '',
|
||||
});
|
||||
|
||||
// Fungsi untuk tombol aksi
|
||||
const viewItem = (item) => {
|
||||
editedItem.value = { ...item };
|
||||
readOnly.value = true;
|
||||
isEditMode.value = false;
|
||||
showForm.value = true;
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
{ title: 'Detail Ruang Klinik', disabled: true, href: '/setting/viewruang' },
|
||||
];
|
||||
};
|
||||
|
||||
const editItem = (item) => {
|
||||
editedIndex.value = allRuangData.value.findIndex(d => d.id === item.id);
|
||||
editedItem.value = { ...item };
|
||||
isEditMode.value = true;
|
||||
readOnly.value = false;
|
||||
showForm.value = true;
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
{ title: 'Edit Ruang Klinik', disabled: true, href: '/setting/editruang' },
|
||||
];
|
||||
};
|
||||
|
||||
const deleteItem = (item) => {
|
||||
editedIndex.value = allRuangData.value.findIndex(d => d.id === item.id);
|
||||
showDeleteDialog.value = true;
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (editedIndex.value > -1) {
|
||||
allRuangData.value.splice(editedIndex.value, 1);
|
||||
}
|
||||
closeDeleteDialog();
|
||||
};
|
||||
|
||||
const cancelForm = () => {
|
||||
showForm.value = false;
|
||||
isEditMode.value = false;
|
||||
readOnly.value = false;
|
||||
editedItem.value = {
|
||||
id: 0,
|
||||
namaKlinik: '',
|
||||
kode: '',
|
||||
namaRuang: '',
|
||||
};
|
||||
editedIndex.value = -1;
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
];
|
||||
};
|
||||
|
||||
const closeDeleteDialog = () => {
|
||||
showDeleteDialog.value = false;
|
||||
editedIndex.value = -1;
|
||||
};
|
||||
|
||||
const saveItem = () => {
|
||||
if (isEditMode.value) {
|
||||
Object.assign(allRuangData.value[editedIndex.value], editedItem.value);
|
||||
} else {
|
||||
const newId = allRuangData.value.length > 0 ? Math.max(...allRuangData.value.map(item => item.id)) + 1 : 1;
|
||||
editedItem.value.id = newId;
|
||||
allRuangData.value.push(editedItem.value);
|
||||
}
|
||||
cancelForm();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.master-klinik-ruang-page {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.custom-table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.v-data-table :deep(th) {
|
||||
font-weight: bold !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
.v-data-table :deep(td) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.v-btn--icon {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.v-select :deep(.v-field__input) {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,156 +0,0 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- <v-card-title class="d-flex justify-space-between align-center">
|
||||
<span>Master Loket</span>
|
||||
<v-btn color=#ff9248 @click="tambahLoket" style="color:white;">Tambah Baru</v-btn>
|
||||
</v-card-title> -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-view-dashboard</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Master Loket</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TabelData
|
||||
:headers="loketHeaders"
|
||||
:items="loketData"
|
||||
title="Master Loket"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<v-btn
|
||||
small
|
||||
color="#ff9248"
|
||||
@click="editLoket(item)"
|
||||
class="mr-2"
|
||||
style="color: white"
|
||||
>Edit</v-btn
|
||||
>
|
||||
<v-btn small color="grey-lighten-4" @click="deleteLoket(item)"
|
||||
>Delete</v-btn
|
||||
>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import TabelData from "../../components/TabelData.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const loketHeaders = ref([
|
||||
{ title: "No", value: "no" },
|
||||
{ title: "Nama Loket", value: "namaLoket" },
|
||||
{ title: "Kuota", value: "kuota" },
|
||||
{ title: "Pelayanan", value: "pelayanan" },
|
||||
{ title: "Pembayaran", value: "pembayaran" },
|
||||
{ title: "Keterangan", value: "keterangan" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false }, // 'value' harus 'actions'
|
||||
]);
|
||||
|
||||
// Master-Loket.vue
|
||||
const loketData = ref([
|
||||
{
|
||||
id: 1,
|
||||
no: 1,
|
||||
namaLoket: "Loket 1",
|
||||
kuota: 500,
|
||||
pelayanan: ["RADIOTERAPI", "REHAB MEDIK", "TINDAKAN"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
no: 2,
|
||||
namaLoket: "Loket 2",
|
||||
kuota: 666,
|
||||
pelayanan: ["JIWA", "SARAF"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
no: 3,
|
||||
namaLoket: "Loket 3",
|
||||
kuota: 666,
|
||||
pelayanan: ["ANESTESI", "JANTUNG"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
no: 4,
|
||||
namaLoket: "Loket 4",
|
||||
kuota: 3676,
|
||||
pelayanan: ["KULIT KELAMIN", "PARU"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
},
|
||||
]);
|
||||
|
||||
const editLoket = (item) => {
|
||||
router.push({ path: `/Setting/Edit-Loket/${item.id}` });
|
||||
};
|
||||
|
||||
const deleteLoket = (item) => {
|
||||
const index = loketData.value.findIndex((loket) => loket.id === item.id);
|
||||
if (index !== -1) {
|
||||
loketData.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const tambahLoket = () => {
|
||||
router.push({ path: "/Setting/TambahLoket" });
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
|
||||
}
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-right: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -46,9 +46,9 @@
|
||||
<template v-slot:item.aksi="{ item }">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#1976d2"
|
||||
color="#"
|
||||
@click="openEditDialog(item)"
|
||||
class="mr-2"
|
||||
class="mr-2 text-white"
|
||||
variant="flat"
|
||||
>
|
||||
<v-icon size="16" left>mdi-pencil</v-icon>
|
||||
@@ -56,9 +56,9 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#f44336"
|
||||
color="#E51A08"
|
||||
@click="deleteKlinik(item)"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
>
|
||||
<v-icon size="16" left>mdi-delete</v-icon>
|
||||
Delete
|
||||
@@ -501,7 +501,7 @@ const deleteKlinik = (item) => {
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
box-shadow: 0 4px 16px rgba(25, 118, 210, 0.2);
|
||||
}
|
||||
@@ -550,7 +550,7 @@ const deleteKlinik = (item) => {
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
color: white;
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
|
||||
+125
-126
@@ -1,3 +1,4 @@
|
||||
<!-- pages/MasterKlinikRuang.vue -->
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
@@ -10,18 +11,30 @@
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Master Klinik Ruang</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Manajemen Ruangan</p>
|
||||
<p class="page-subtitle">
|
||||
{{ currentDate }} - Manajemen Ruangan
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Ruang
|
||||
</v-btn>
|
||||
<div class="header-stats">
|
||||
<v-chip color="white" variant="flat" class="mr-2">
|
||||
<v-icon start size="16">mdi-hospital-building</v-icon>
|
||||
{{ totalKlinikRuang }} Klinik
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="mr-2">
|
||||
<v-icon start size="16">mdi-door</v-icon>
|
||||
{{ totalRuangan }} Ruang
|
||||
</v-chip>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Ruang
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +42,7 @@
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="ruangData"
|
||||
:items="klinikRuangList"
|
||||
:items-per-page="10"
|
||||
class="elevation-0"
|
||||
>
|
||||
@@ -43,9 +56,9 @@
|
||||
<template v-slot:item.aksi="{ item }">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#1976d2"
|
||||
color="#FF9B1B"
|
||||
@click="openEditDialog(item)"
|
||||
class="mr-2"
|
||||
class="mr-2 text-white"
|
||||
variant="flat"
|
||||
>
|
||||
<v-icon size="16" left>mdi-pencil</v-icon>
|
||||
@@ -53,9 +66,9 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#f44336"
|
||||
color="#E51A08"
|
||||
@click="deleteRuang(item)"
|
||||
variant="flat"
|
||||
variant="outlined"
|
||||
>
|
||||
<v-icon size="16" left>mdi-delete</v-icon>
|
||||
Delete
|
||||
@@ -92,7 +105,7 @@
|
||||
<v-autocomplete
|
||||
label="Kode Klinik"
|
||||
v-model="formData.kodeKlinik"
|
||||
:items="klinikList"
|
||||
:items="masterKlinikList"
|
||||
item-title="kode"
|
||||
item-value="kode"
|
||||
variant="outlined"
|
||||
@@ -196,7 +209,7 @@
|
||||
placeholder="Screen Klinik Ruang"
|
||||
prepend-inner-icon="mdi-monitor"
|
||||
></v-text-field>
|
||||
<small class="text-grey">Masukan Nomor Screen Ruang</small>
|
||||
<!-- <small class="text-grey">Masukan Nomor Screen Ruang</small> -->
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="1" class="d-flex justify-center">
|
||||
@@ -245,6 +258,7 @@
|
||||
color="warning"
|
||||
@click="submitForm"
|
||||
class="action-btn"
|
||||
:loading="loading"
|
||||
>
|
||||
<v-icon left size="18">mdi-content-save</v-icon>
|
||||
Submit
|
||||
@@ -252,16 +266,54 @@
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="3000"
|
||||
location="top right"
|
||||
>
|
||||
{{ snackbarText }}
|
||||
<template v-slot:actions>
|
||||
<v-btn icon @click="snackbar = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useKlinikRuangStore } from '../stores/klinikRuangStore';
|
||||
|
||||
// Store
|
||||
const klinikRuangStore = useKlinikRuangStore();
|
||||
|
||||
// Reactive data
|
||||
const dialog = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const formRef = ref(null);
|
||||
const loading = ref(false);
|
||||
const snackbar = ref(false);
|
||||
const snackbarText = ref('');
|
||||
const snackbarColor = ref('success');
|
||||
|
||||
const currentDate = ref(new Date().toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
}));
|
||||
|
||||
// Computed from store
|
||||
const masterKlinikList = computed(() => klinikRuangStore.masterKlinikList);
|
||||
const klinikRuangList = computed(() => klinikRuangStore.klinikRuangList);
|
||||
const totalKlinikRuang = computed(() => klinikRuangStore.totalKlinikRuang);
|
||||
const totalRuangan = computed(() => klinikRuangStore.totalRuangan);
|
||||
|
||||
// Headers
|
||||
const headers = ref([
|
||||
{ title: 'No', value: 'no', sortable: true },
|
||||
{ title: 'Nama Klinik', value: 'namaKlinik', sortable: true },
|
||||
@@ -270,81 +322,7 @@ const headers = ref([
|
||||
{ title: 'Aksi', value: 'aksi', sortable: false },
|
||||
]);
|
||||
|
||||
// List klinik dari Master Klinik
|
||||
const klinikList = ref([
|
||||
{ kode: 'AN', nama: 'ANAK' },
|
||||
{ kode: 'AS', nama: 'ANESTESI' },
|
||||
{ kode: 'BD', nama: 'BEDAH' },
|
||||
{ kode: 'GR', nama: 'GERIATRI' },
|
||||
{ kode: 'GI', nama: 'GIGI DAN MULUT' },
|
||||
{ kode: 'GZ', nama: 'GIZI' },
|
||||
{ kode: 'HO', nama: 'HOM' },
|
||||
{ kode: 'IP', nama: 'IPD' },
|
||||
{ kode: 'JT', nama: 'JANTUNG' },
|
||||
{ kode: 'JW', nama: 'JIWA' },
|
||||
{ kode: 'KK', nama: 'KULIT KELAMIN' },
|
||||
{ kode: 'MT', nama: 'MATA' },
|
||||
{ kode: 'OB', nama: 'OBGYN' },
|
||||
{ kode: 'PR', nama: 'PARU' },
|
||||
{ kode: 'RT', nama: 'RADIOTERAPI' },
|
||||
]);
|
||||
|
||||
const ruangData = ref([
|
||||
{
|
||||
id: 1,
|
||||
no: 1,
|
||||
kodeKlinik: 'AN',
|
||||
namaKlinik: 'ANAK',
|
||||
namaRuang: 'R. TINDAKAN',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'R. TINDAKAN', nomorScreen: '101' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
no: 2,
|
||||
kodeKlinik: 'AS',
|
||||
namaKlinik: 'ANESTESI',
|
||||
namaRuang: 'Ruang 1, Ruang 2',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '201' },
|
||||
{ nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '202' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
no: 3,
|
||||
kodeKlinik: 'BD',
|
||||
namaKlinik: 'BEDAH',
|
||||
namaRuang: 'Ruang Konsultasi',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang Konsultasi', nomorScreen: '301' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
no: 4,
|
||||
kodeKlinik: 'GR',
|
||||
namaKlinik: 'GERIATRI',
|
||||
namaRuang: 'Ruang Pemeriksaan',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang Pemeriksaan', nomorScreen: '401' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
no: 5,
|
||||
kodeKlinik: 'GI',
|
||||
namaKlinik: 'GIGI DAN MULUT',
|
||||
namaRuang: 'Ruang 1, Ruang 2, Ruang 3',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '501' },
|
||||
{ nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '502' },
|
||||
{ nomorRuang: '3', namaRuang: 'Ruang 3', nomorScreen: '503' }
|
||||
]
|
||||
},
|
||||
]);
|
||||
|
||||
// Form data
|
||||
const formData = ref({
|
||||
id: null,
|
||||
kodeKlinik: '',
|
||||
@@ -354,8 +332,15 @@ const formData = ref({
|
||||
],
|
||||
});
|
||||
|
||||
// Methods
|
||||
const showSnackbar = (text, color = 'success') => {
|
||||
snackbarText.value = text;
|
||||
snackbarColor.value = color;
|
||||
snackbar.value = true;
|
||||
};
|
||||
|
||||
const onKlinikChange = (kode) => {
|
||||
const selectedKlinik = klinikList.value.find(k => k.kode === kode);
|
||||
const selectedKlinik = masterKlinikList.value.find(k => k.kode === kode);
|
||||
if (selectedKlinik) {
|
||||
formData.value.namaKlinik = selectedKlinik.nama;
|
||||
}
|
||||
@@ -415,51 +400,46 @@ const submitForm = async () => {
|
||||
const { valid } = await formRef.value.validate();
|
||||
|
||||
if (!valid) {
|
||||
showSnackbar('Mohon lengkapi form dengan benar', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.value.ruangList.length === 0) {
|
||||
alert('Tambahkan minimal 1 ruangan');
|
||||
showSnackbar('Tambahkan minimal 1 ruangan', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate nama ruang untuk display di table
|
||||
const namaRuangDisplay = formData.value.ruangList
|
||||
.map(r => r.namaRuang)
|
||||
.filter(n => n)
|
||||
.join(', ');
|
||||
loading.value = true;
|
||||
|
||||
if (isEdit.value) {
|
||||
const index = ruangData.value.findIndex(r => r.id === formData.value.id);
|
||||
if (index !== -1) {
|
||||
ruangData.value[index] = {
|
||||
...ruangData.value[index],
|
||||
...formData.value,
|
||||
namaRuang: namaRuangDisplay,
|
||||
};
|
||||
try {
|
||||
let result;
|
||||
if (isEdit.value) {
|
||||
result = klinikRuangStore.updateKlinikRuang(formData.value.id, formData.value);
|
||||
} else {
|
||||
result = klinikRuangStore.createKlinikRuang(formData.value);
|
||||
}
|
||||
} else {
|
||||
const newId = Math.max(...ruangData.value.map(r => r.id)) + 1;
|
||||
const newNo = ruangData.value.length + 1;
|
||||
ruangData.value.push({
|
||||
id: newId,
|
||||
no: newNo,
|
||||
...formData.value,
|
||||
namaRuang: namaRuangDisplay,
|
||||
});
|
||||
}
|
||||
|
||||
closeDialog();
|
||||
if (result.success) {
|
||||
showSnackbar(result.message, 'success');
|
||||
closeDialog();
|
||||
} else {
|
||||
showSnackbar(result.message, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showSnackbar('Terjadi kesalahan', 'error');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteRuang = (item) => {
|
||||
if (confirm(`Hapus ruangan ${item.namaRuang} dari klinik ${item.namaKlinik}?`)) {
|
||||
const index = ruangData.value.findIndex(r => r.id === item.id);
|
||||
if (index !== -1) {
|
||||
ruangData.value.splice(index, 1);
|
||||
ruangData.value.forEach((r, idx) => {
|
||||
r.no = idx + 1;
|
||||
});
|
||||
const result = klinikRuangStore.deleteKlinikRuang(item.id);
|
||||
|
||||
if (result.success) {
|
||||
showSnackbar(result.message, 'success');
|
||||
} else {
|
||||
showSnackbar(result.message, 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -467,7 +447,8 @@ const deleteRuang = (item) => {
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
|
||||
border-radius: 16px 16px 0 0;
|
||||
box-shadow: 0 4px 16px rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
@@ -506,6 +487,12 @@ const deleteRuang = (item) => {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
font-weight: 600;
|
||||
text-transform: none;
|
||||
@@ -594,4 +581,16 @@ const deleteRuang = (item) => {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header-stats {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+361
-42
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
@@ -14,7 +15,7 @@
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="tambahLoket"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
@@ -23,40 +24,220 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<TabelData
|
||||
:headers="loketHeaders"
|
||||
:items="loketData"
|
||||
title="Master Loket"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<v-btn
|
||||
small
|
||||
color="#ff9248"
|
||||
@click="editLoket(item)"
|
||||
class="mr-2"
|
||||
style="color: white"
|
||||
>
|
||||
Edit
|
||||
</v-btn>
|
||||
<v-btn
|
||||
small
|
||||
color="grey-lighten-4"
|
||||
@click="deleteLoket(item)"
|
||||
>
|
||||
Delete
|
||||
</v-btn>
|
||||
</template>
|
||||
</TabelData>
|
||||
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="loketHeaders"
|
||||
:items="loketData"
|
||||
:items-per-page="10"
|
||||
class="elevation-0"
|
||||
>
|
||||
<template v-slot:item.pelayanan="{ item }">
|
||||
<v-chip
|
||||
v-for="(service, idx) in item.pelayanan.slice(0, 2)"
|
||||
:key="idx"
|
||||
size="small"
|
||||
class="mr-1 mb-1"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
>
|
||||
{{ service }}
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="item.pelayanan.length > 2"
|
||||
size="small"
|
||||
color="grey"
|
||||
variant="flat"
|
||||
>
|
||||
+{{ item.pelayanan.length - 2 }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.aksi="{ item }">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#FF9B1B"
|
||||
@click="openEditDialog(item)"
|
||||
variant="flat"
|
||||
class="mr-2 text-white"
|
||||
>
|
||||
<v-icon size="16" left>mdi-pencil</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#E51A08"
|
||||
variant="outlined"
|
||||
@click="deleteLoket(item)"
|
||||
>
|
||||
<v-icon size="16" left>mdi-delete</v-icon>
|
||||
Delete
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Dialog Tambah/Edit -->
|
||||
<v-dialog v-model="dialog" max-width="700px" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="dialog-header">
|
||||
<span class="dialog-title">{{ isEdit ? 'Edit Loket' : 'Tambah Loket Baru' }}</span>
|
||||
<v-btn icon variant="text" @click="closeDialog" size="small">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-text class="dialog-content">
|
||||
<v-form ref="formRef">
|
||||
<!-- Informasi Loket -->
|
||||
<div class="field-group">
|
||||
<div class="group-label">Informasi Loket</div>
|
||||
|
||||
<v-text-field
|
||||
label="Nama Loket"
|
||||
v-model="formData.namaLoket"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:rules="[v => !!v || 'Nama loket harus diisi']"
|
||||
hide-details="auto"
|
||||
class="mb-3"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
label="Kuota Bangku"
|
||||
v-model="formData.kuota"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:rules="[v => !!v || 'Kuota harus diisi', v => v > 0 || 'Kuota minimal 1']"
|
||||
hide-details="auto"
|
||||
class="mb-3"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-4"></v-divider>
|
||||
|
||||
<!-- Konfigurasi -->
|
||||
<div class="field-group">
|
||||
<div class="group-label">Konfigurasi</div>
|
||||
|
||||
<v-select
|
||||
label="Status Pelayanan"
|
||||
:items="['RAWAT JALAN', 'RAWAT INAP']"
|
||||
v-model="formData.statusPelayanan"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:rules="[v => !!v || 'Status pelayanan harus dipilih']"
|
||||
hide-details="auto"
|
||||
class="mb-3"
|
||||
></v-select>
|
||||
|
||||
<v-row dense>
|
||||
<v-col cols="6">
|
||||
<v-select
|
||||
label="Pembayaran"
|
||||
:items="['JKN', 'UMUM', 'SPM', 'JKMM', 'JAMPERSAL', 'T4', 'KARYAWAN']"
|
||||
v-model="formData.pembayaran"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:rules="[v => !!v || 'Pembayaran harus dipilih']"
|
||||
hide-details="auto"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<v-select
|
||||
label="Keterangan"
|
||||
:items="['ONLINE', 'OFFLINE']"
|
||||
v-model="formData.keterangan"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:rules="[v => !!v || 'Keterangan harus dipilih']"
|
||||
hide-details="auto"
|
||||
></v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-4"></v-divider>
|
||||
|
||||
<!-- Pelayanan -->
|
||||
<div class="field-group">
|
||||
<div class="group-label">Pelayanan</div>
|
||||
|
||||
<v-select
|
||||
label="Pilih Pelayanan"
|
||||
:items="availableServices"
|
||||
v-model="formData.pelayanan"
|
||||
item-title="nama"
|
||||
item-value="id"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
multiple
|
||||
chips
|
||||
closable-chips
|
||||
:rules="[v => v.length > 0 || 'Pilih minimal 1 pelayanan']"
|
||||
hide-details="auto"
|
||||
>
|
||||
<template v-slot:chip="{ props, item }">
|
||||
<v-chip
|
||||
v-bind="props"
|
||||
closable
|
||||
size="small"
|
||||
color="primary"
|
||||
>
|
||||
{{ item.raw.nama }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<template v-slot:item="{ props, item }">
|
||||
<v-list-item v-bind="props">
|
||||
<template v-slot:prepend>
|
||||
<v-chip size="x-small" color="grey-lighten-2">{{ item.raw.id }}</v-chip>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.raw.nama }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-select>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-actions class="dialog-actions">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
@click="closeDialog"
|
||||
class="action-btn"
|
||||
>
|
||||
<v-icon left size="18">mdi-close</v-icon>
|
||||
Batal
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="#FF9B1B"
|
||||
variant="flat"
|
||||
@click="submitForm"
|
||||
class="action-btn"
|
||||
>
|
||||
<v-icon left size="18">mdi-content-save</v-icon>
|
||||
Simpan
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import TabelData from "../../components/TabelData.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ref } from 'vue';
|
||||
|
||||
const router = useRouter();
|
||||
const dialog = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const formRef = ref(null);
|
||||
|
||||
const loketHeaders = ref([
|
||||
{ title: "No", value: "no" },
|
||||
@@ -107,28 +288,124 @@ const loketData = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
const editLoket = (item) => {
|
||||
router.push({ path: `/Setting/Edit-Loket/${item.id}` });
|
||||
const availableServices = ref([
|
||||
{ id: 'AN', nama: 'ANAK' },
|
||||
{ id: 'AS', nama: 'ANESTESI' },
|
||||
{ id: 'BD', nama: 'BEDAH' },
|
||||
{ id: 'GR', nama: 'GERIATRI' },
|
||||
{ id: 'GI', nama: 'GIGI DAN MULUT' },
|
||||
{ id: 'GZ', nama: 'GIZI' },
|
||||
{ id: 'HO', nama: 'HOM' },
|
||||
{ id: 'IP', nama: 'IPD' },
|
||||
{ id: 'JT', nama: 'JANTUNG' },
|
||||
{ id: 'JW', nama: 'JIWA' },
|
||||
{ id: 'KK', nama: 'KULIT KELAMIN' },
|
||||
{ id: 'PR', nama: 'PARU' },
|
||||
{ id: 'RT', nama: 'RADIOTERAPI' },
|
||||
{ id: 'RM', nama: 'REHAB MEDIK' },
|
||||
{ id: 'SR', nama: 'SARAF' },
|
||||
{ id: 'TD', nama: 'TINDAKAN' },
|
||||
]);
|
||||
|
||||
const formData = ref({
|
||||
id: null,
|
||||
namaLoket: '',
|
||||
kuota: null,
|
||||
statusPelayanan: '',
|
||||
pembayaran: '',
|
||||
keterangan: '',
|
||||
pelayanan: [],
|
||||
});
|
||||
|
||||
const openTambahDialog = () => {
|
||||
isEdit.value = false;
|
||||
resetForm();
|
||||
dialog.value = true;
|
||||
};
|
||||
|
||||
const deleteLoket = (item) => {
|
||||
const index = loketData.value.findIndex((loket) => loket.id === item.id);
|
||||
if (index !== -1) {
|
||||
loketData.value.splice(index, 1);
|
||||
const openEditDialog = (item) => {
|
||||
isEdit.value = true;
|
||||
formData.value = {
|
||||
id: item.id,
|
||||
namaLoket: item.namaLoket,
|
||||
kuota: item.kuota,
|
||||
statusPelayanan: item.statusPelayanan || 'RAWAT JALAN',
|
||||
pembayaran: item.pembayaran,
|
||||
keterangan: item.keterangan,
|
||||
pelayanan: [...item.pelayanan],
|
||||
};
|
||||
dialog.value = true;
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
dialog.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: null,
|
||||
namaLoket: '',
|
||||
kuota: null,
|
||||
statusPelayanan: '',
|
||||
pembayaran: '',
|
||||
keterangan: '',
|
||||
pelayanan: [],
|
||||
};
|
||||
if (formRef.value) {
|
||||
formRef.value.reset();
|
||||
}
|
||||
};
|
||||
|
||||
const tambahLoket = () => {
|
||||
router.push({ path: "/Setting/TambahLoket" });
|
||||
const submitForm = async () => {
|
||||
const { valid } = await formRef.value.validate();
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
// Update existing
|
||||
const index = loketData.value.findIndex(l => l.id === formData.value.id);
|
||||
if (index !== -1) {
|
||||
loketData.value[index] = {
|
||||
...loketData.value[index],
|
||||
...formData.value,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Add new
|
||||
const newId = Math.max(...loketData.value.map(l => l.id)) + 1;
|
||||
const newNo = loketData.value.length + 1;
|
||||
loketData.value.push({
|
||||
id: newId,
|
||||
no: newNo,
|
||||
...formData.value,
|
||||
});
|
||||
}
|
||||
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const deleteLoket = (item) => {
|
||||
if (confirm(`Hapus loket ${item.namaLoket}?`)) {
|
||||
const index = loketData.value.findIndex(l => l.id === item.id);
|
||||
if (index !== -1) {
|
||||
loketData.value.splice(index, 1);
|
||||
// Reorder no
|
||||
loketData.value.forEach((l, idx) => {
|
||||
l.no = idx + 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
box-shadow: 0 4px 16px rgba(245, 124, 0, 0.2);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -171,6 +448,48 @@ const tambahLoket = () => {
|
||||
letter-spacing: 0.5px;
|
||||
padding: 0 24px !important;
|
||||
height: 44px !important;
|
||||
color: #1976d2 !important;
|
||||
color: #f57c00 !important;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
color: white;
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
padding: 24px !important;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
padding: 16px 24px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
text-transform: none;
|
||||
font-weight: 600;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.field-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.group-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #f57c00;
|
||||
margin-bottom: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
</style>
|
||||
@@ -89,12 +89,14 @@
|
||||
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
color="#ff9248"
|
||||
@click="editScreen(item)"
|
||||
style="color: white"
|
||||
></v-btn>
|
||||
size="small"
|
||||
color="#FF9B1B"
|
||||
@click="editScreen(item)"
|
||||
variant="flat"
|
||||
class="mr-2 text-white"
|
||||
><v-icon size="16" left>mdi-pencil</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,283 @@
|
||||
// stores/klinikRuangStore.js
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
export const useKlinikRuangStore = defineStore('klinikRuang', () => {
|
||||
// State - List Master Klinik
|
||||
const masterKlinikList = ref([
|
||||
{ kode: 'AN', nama: 'ANAK' },
|
||||
{ kode: 'AS', nama: 'ANESTESI' },
|
||||
{ kode: 'BD', nama: 'BEDAH' },
|
||||
{ kode: 'GR', nama: 'GERIATRI' },
|
||||
{ kode: 'GI', nama: 'GIGI DAN MULUT' },
|
||||
{ kode: 'GZ', nama: 'GIZI' },
|
||||
{ kode: 'HO', nama: 'HOM' },
|
||||
{ kode: 'IP', nama: 'IPD' },
|
||||
{ kode: 'JT', nama: 'JANTUNG' },
|
||||
{ kode: 'JW', nama: 'JIWA' },
|
||||
{ kode: 'KK', nama: 'KULIT KELAMIN' },
|
||||
{ kode: 'MT', nama: 'MATA' },
|
||||
{ kode: 'OB', nama: 'OBGYN' },
|
||||
{ kode: 'PR', nama: 'PARU' },
|
||||
{ kode: 'RT', nama: 'RADIOTERAPI' },
|
||||
]);
|
||||
|
||||
// State - Klinik Ruang Data
|
||||
const klinikRuangList = ref([
|
||||
{
|
||||
id: 1,
|
||||
no: 1,
|
||||
kodeKlinik: 'AN',
|
||||
namaKlinik: 'ANAK',
|
||||
namaRuang: 'R. TINDAKAN',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'R. TINDAKAN', nomorScreen: '101' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
no: 2,
|
||||
kodeKlinik: 'AS',
|
||||
namaKlinik: 'ANESTESI',
|
||||
namaRuang: 'Ruang 1, Ruang 2',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '201' },
|
||||
{ nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '202' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
no: 3,
|
||||
kodeKlinik: 'BD',
|
||||
namaKlinik: 'BEDAH',
|
||||
namaRuang: 'Ruang Konsultasi',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang Konsultasi', nomorScreen: '301' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
no: 4,
|
||||
kodeKlinik: 'GR',
|
||||
namaKlinik: 'GERIATRI',
|
||||
namaRuang: 'Ruang Pemeriksaan',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang Pemeriksaan', nomorScreen: '401' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
no: 5,
|
||||
kodeKlinik: 'GI',
|
||||
namaKlinik: 'GIGI DAN MULUT',
|
||||
namaRuang: 'Ruang 1, Ruang 2, Ruang 3',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '501' },
|
||||
{ nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '502' },
|
||||
{ nomorRuang: '3', namaRuang: 'Ruang 3', nomorScreen: '503' }
|
||||
]
|
||||
},
|
||||
]);
|
||||
|
||||
// Computed
|
||||
const totalKlinikRuang = computed(() => klinikRuangList.value.length);
|
||||
|
||||
const totalRuangan = computed(() => {
|
||||
return klinikRuangList.value.reduce((total, klinik) => {
|
||||
return total + klinik.ruangList.length;
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Get klinik by code
|
||||
const getKlinikByCode = (kode) => {
|
||||
return klinikRuangList.value.find(k => k.kodeKlinik === kode);
|
||||
};
|
||||
|
||||
// Get all ruang for a specific klinik
|
||||
const getRuangByKlinik = (kodeKlinik) => {
|
||||
const klinik = klinikRuangList.value.find(k => k.kodeKlinik === kodeKlinik);
|
||||
return klinik ? klinik.ruangList : [];
|
||||
};
|
||||
|
||||
// Actions - CRUD Operations
|
||||
|
||||
// Create new Klinik Ruang
|
||||
const createKlinikRuang = (data) => {
|
||||
const newId = Math.max(...klinikRuangList.value.map(r => r.id), 0) + 1;
|
||||
const newNo = klinikRuangList.value.length + 1;
|
||||
|
||||
// Generate nama ruang untuk display
|
||||
const namaRuangDisplay = data.ruangList
|
||||
.map(r => r.namaRuang)
|
||||
.filter(n => n)
|
||||
.join(', ');
|
||||
|
||||
const newKlinikRuang = {
|
||||
id: newId,
|
||||
no: newNo,
|
||||
kodeKlinik: data.kodeKlinik,
|
||||
namaKlinik: data.namaKlinik,
|
||||
namaRuang: namaRuangDisplay,
|
||||
ruangList: data.ruangList
|
||||
};
|
||||
|
||||
klinikRuangList.value.push(newKlinikRuang);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Klinik Ruang ${data.namaKlinik} berhasil ditambahkan`,
|
||||
data: newKlinikRuang
|
||||
};
|
||||
};
|
||||
|
||||
// Update existing Klinik Ruang
|
||||
const updateKlinikRuang = (id, data) => {
|
||||
const index = klinikRuangList.value.findIndex(r => r.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Klinik Ruang tidak ditemukan'
|
||||
};
|
||||
}
|
||||
|
||||
// Generate nama ruang untuk display
|
||||
const namaRuangDisplay = data.ruangList
|
||||
.map(r => r.namaRuang)
|
||||
.filter(n => n)
|
||||
.join(', ');
|
||||
|
||||
klinikRuangList.value[index] = {
|
||||
...klinikRuangList.value[index],
|
||||
kodeKlinik: data.kodeKlinik,
|
||||
namaKlinik: data.namaKlinik,
|
||||
namaRuang: namaRuangDisplay,
|
||||
ruangList: data.ruangList
|
||||
};
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Klinik Ruang ${data.namaKlinik} berhasil diupdate`,
|
||||
data: klinikRuangList.value[index]
|
||||
};
|
||||
};
|
||||
|
||||
// Delete Klinik Ruang
|
||||
const deleteKlinikRuang = (id) => {
|
||||
const index = klinikRuangList.value.findIndex(r => r.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Klinik Ruang tidak ditemukan'
|
||||
};
|
||||
}
|
||||
|
||||
const deletedKlinik = klinikRuangList.value[index];
|
||||
klinikRuangList.value.splice(index, 1);
|
||||
|
||||
// Reorder numbers
|
||||
klinikRuangList.value.forEach((r, idx) => {
|
||||
r.no = idx + 1;
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Klinik Ruang ${deletedKlinik.namaKlinik} berhasil dihapus`
|
||||
};
|
||||
};
|
||||
|
||||
// Add ruang to existing klinik
|
||||
const addRuangToKlinik = (klinikId, ruangData) => {
|
||||
const klinik = klinikRuangList.value.find(k => k.id === klinikId);
|
||||
|
||||
if (!klinik) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Klinik tidak ditemukan'
|
||||
};
|
||||
}
|
||||
|
||||
klinik.ruangList.push(ruangData);
|
||||
|
||||
// Update display name
|
||||
klinik.namaRuang = klinik.ruangList
|
||||
.map(r => r.namaRuang)
|
||||
.filter(n => n)
|
||||
.join(', ');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Ruang ${ruangData.namaRuang} berhasil ditambahkan`,
|
||||
data: klinik
|
||||
};
|
||||
};
|
||||
|
||||
// Remove ruang from klinik
|
||||
const removeRuangFromKlinik = (klinikId, ruangIndex) => {
|
||||
const klinik = klinikRuangList.value.find(k => k.id === klinikId);
|
||||
|
||||
if (!klinik) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Klinik tidak ditemukan'
|
||||
};
|
||||
}
|
||||
|
||||
if (klinik.ruangList.length <= 1) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Minimal harus ada 1 ruangan'
|
||||
};
|
||||
}
|
||||
|
||||
const removedRuang = klinik.ruangList[ruangIndex];
|
||||
klinik.ruangList.splice(ruangIndex, 1);
|
||||
|
||||
// Update display name
|
||||
klinik.namaRuang = klinik.ruangList
|
||||
.map(r => r.namaRuang)
|
||||
.filter(n => n)
|
||||
.join(', ');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Ruang ${removedRuang.namaRuang} berhasil dihapus`,
|
||||
data: klinik
|
||||
};
|
||||
};
|
||||
|
||||
// Search klinik ruang
|
||||
const searchKlinikRuang = (searchTerm) => {
|
||||
if (!searchTerm) return klinikRuangList.value;
|
||||
|
||||
const term = searchTerm.toLowerCase();
|
||||
return klinikRuangList.value.filter(k =>
|
||||
k.kodeKlinik.toLowerCase().includes(term) ||
|
||||
k.namaKlinik.toLowerCase().includes(term) ||
|
||||
k.namaRuang.toLowerCase().includes(term)
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
masterKlinikList,
|
||||
klinikRuangList,
|
||||
|
||||
// Computed
|
||||
totalKlinikRuang,
|
||||
totalRuangan,
|
||||
|
||||
// Getters
|
||||
getKlinikByCode,
|
||||
getRuangByKlinik,
|
||||
|
||||
// Actions
|
||||
createKlinikRuang,
|
||||
updateKlinikRuang,
|
||||
deleteKlinikRuang,
|
||||
addRuangToKlinik,
|
||||
removeRuangFromKlinik,
|
||||
searchKlinikRuang,
|
||||
};
|
||||
});
|
||||
+11
-8
@@ -35,21 +35,24 @@ const defaultNavItems: NavItem[] = [
|
||||
path: "",
|
||||
children: [
|
||||
{ id: 10, name: "Anjungan", path: "/anjungan/anjungan", icon: "mdi-circle-small" },
|
||||
{ id: 11, name: "Klinik Ruang", path: "/anjungan/AntrianKlinik", icon: "mdi-circle-small" },
|
||||
{ id: 11, name: "Klinik", path: "/anjungan/AntrianKlinik", icon: "mdi-circle-small" },
|
||||
{ id: 12, name: "Klinik Ruang", path: "/anjungan/AntrianKlinikRuang", icon: "mdi-circle-small"},
|
||||
{ id: 13, name: "Penunjang", path: "/anjungan/AntrianPenunjang", icon: "mdi-circle-small"},
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
id: 14,
|
||||
name: "Master Data",
|
||||
icon: "mdi-cog-outline",
|
||||
path: "",
|
||||
children: [
|
||||
{ id: 13, name: "Hak Akses", path: "/setting/HakAkses", icon: "mdi-circle-small" },
|
||||
{ id: 14, name: "User Login", path: "/setting/UserLogin", icon: "mdi-circle-small" },
|
||||
{ id: 15, name: "Master Loket", path: "/setting/masterloket", icon: "mdi-circle-small" },
|
||||
{ id: 16, name: "Master Klinik", path: "/setting/masterklinik", icon: "mdi-circle-small" },
|
||||
{ id: 17, name: "Master Klinik Ruang", path: "/setting/masterklinikruang", icon: "mdi-circle-small" },
|
||||
{ id: 18, name: "Screen", path: "/setting/screen", icon: "mdi-circle-small" },
|
||||
{ id: 15, name: "Hak Akses", path: "/setting/HakAkses", icon: "mdi-circle-small" },
|
||||
{ id: 16, name: "User Login", path: "/setting/UserLogin", icon: "mdi-circle-small" },
|
||||
{ id: 17, name: "Master Loket", path: "/setting/masterloket", icon: "mdi-circle-small" },
|
||||
{ id: 18, name: "Master Klinik", path: "/setting/masterklinik", icon: "mdi-circle-small" },
|
||||
{ id: 19, name: "Master Klinik Ruang", path: "/setting/masterklinikruang", icon: "mdi-circle-small" },
|
||||
{ id: 20, name: "Screen", path: "/setting/screen", icon: "mdi-circle-small" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user