Files
web-antrean/pages/Setting/MasterKlinikRuang.vue
T

1090 lines
29 KiB
Vue

<template>
<div>
<!-- Header -->
<div class="page-header">
<div class="header-content">
<div class="header-left">
<div class="header-icon">
<v-icon size="28" color="white">mdi-door-open</v-icon>
</div>
<div class="header-text">
<h2 class="page-title">Master Klinik Ruang</h2>
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Manajemen Ruangan</p>
</div>
</div>
</div>
</div>
<v-container>
<!-- Action Bar -->
<div class="action-bar mb-4">
<div class="action-bar-left">
<v-chip color="primary" variant="tonal" class="stat-chip mr-2">
<v-icon start size="16">mdi-hospital-building</v-icon>
{{ masterStore.totalKlinikRuang }} Klinik
</v-chip>
<v-chip color="primary" variant="tonal" class="stat-chip mr-2">
<v-icon start size="16">mdi-door</v-icon>
{{ masterStore.totalRuangan }} Ruang
</v-chip>
</div>
<v-btn
color="primary-600"
variant="flat"
@click="openTambahDialog"
elevation="0"
class="action-btn text-white"
>
<v-icon left size="20">mdi-plus-circle</v-icon>
Tambah Ruang
</v-btn>
</div>
<v-card>
<v-card-text>
<!-- Filter Row -->
<div class="d-flex flex-wrap align-center justify-space-between mb-4">
<div class="d-flex align-center">
<span class="mr-2 body-3">Show</span>
<v-select
v-model="itemsPerPage"
:items="[10, 25, 50, 100]"
variant="outlined"
density="compact"
hide-details
style="max-width: 80px;"
rounded
class="mr-2"
></v-select>
<span class="body-3">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
style="min-width: 250px;"
></v-text-field>
</div>
</div>
<v-data-table
v-model:page="page"
:headers="headers"
:items="masterStore.ruangData"
:items-per-page="itemsPerPage"
:search="search"
item-value="id"
class="elevation-0 data-table"
hover
@update:itemsLength="filteredTotal = $event"
>
<template v-slot:item.namaKlinik="{ item }">
<v-chip size="small" class="chip-success-outline">
{{ item.kodeKlinik }}
</v-chip>
<span class="ml-2 body-3 text-medium">{{ item.namaKlinik }}</span>
</template>
<template v-slot:item.namaRuang="{ item }">
<div class="ruang-tags">
<v-chip
v-for="(ruang, idx) in item.ruangList.slice(0, 3)"
:key="idx"
size="small"
class="mr-1 mb-1 chip-primary-outline"
>
{{ ruang.namaRuang }}
</v-chip>
<v-chip
v-if="item.ruangList.length > 3"
size="small"
class="chip-neutral"
>
+{{ item.ruangList.length - 3 }}
</v-chip>
</div>
</template>
<template v-slot:item.layarInformasi="{ item }">
<v-btn
size="small"
variant="outlined"
color="primary-600"
@click="openPreviewDialog(item)"
class="btn-preview mr-2"
>
<v-icon size="16" left>mdi-eye</v-icon>
Preview
</v-btn>
</template>
<template v-slot:item.jenisLayanan="{ item }">
<v-chip
size="small"
:class="item.jenisLayanan === 'Reguler' ? 'chip-reguler' : 'chip-eksekutif'"
>
{{ item.jenisLayanan }}
</v-chip>
</template>
<template #bottom>
<v-row class="ma-2" align="center">
<v-col cols="12" sm="6" class="d-flex align-center justify-start body-3 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"
total-visible="5"
rounded="circle"
size="small"
></v-pagination>
</v-col>
</v-row>
</template>
<template v-slot:item.aksi="{ item }">
<v-btn
size="small"
color=" warning-600"
@click="openEditDialog(item)"
class="btn-edit mr-2"
variant="flat"
>
<v-icon size="16" left>mdi-pencil</v-icon>
Edit
</v-btn>
<v-btn
size="small"
color="error-600"
@click="handleDelete(item)"
class="btn-delete"
variant="flat"
>
<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 scrollable>
<v-card class="dialog-card">
<v-card-title class="dialog-header">
<span class="headline-4">{{ isEdit ? 'Edit Klinik Ruang' : 'Tambah Klinik Ruang' }}</span>
<v-btn icon variant="text" @click="closeDialog" size="small" class="btn-close">
<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">
<!-- Pilih Klinik -->
<div class="field-group">
<div class="group-label">
<v-icon size="18" class="icon-label">mdi-hospital-building</v-icon>
<span>Pilih Klinik</span>
</div>
<v-autocomplete
label="Kode Klinik"
v-model="formData.kodeKlinik"
:items="masterStore.klinikList"
item-title="nama"
item-value="kode"
variant="outlined"
density="compact"
:rules="[v => !!v || 'Kode klinik harus dipilih']"
hide-details="auto"
placeholder="Pilih Klinik"
@update:model-value="onKlinikChange"
class="mb-3 input-field"
>
<template v-slot:item="{ props, item }">
<v-list-item v-bind="props">
<template v-slot:prepend>
<v-chip size="x-small" class="chip-success-small">{{ item.raw.kode }}</v-chip>
</template>
<v-list-item-title class="body-3">{{ item.raw.nama }}</v-list-item-title>
</v-list-item>
</template>
</v-autocomplete>
</div>
<v-divider class="my-4 divider-section"></v-divider>
<!-- Daftar Ruangan -->
<div class="field-group">
<div class="group-label">
<v-icon size="18" class="icon-label">mdi-door</v-icon>
<span>Daftar Ruangan</span>
</div>
<div v-for="(ruang, index) in formData.ruangList" :key="index" class="ruang-item">
<v-row dense align="center">
<v-col cols="1">
<div class="ruang-badge body-3">{{ index + 1 }}</div>
</v-col>
<v-col cols="3">
<v-text-field
label="No. Ruang"
v-model="ruang.nomorRuang"
variant="outlined"
density="compact"
hide-details
placeholder="1"
class="input-field"
></v-text-field>
</v-col>
<v-col cols="4">
<v-text-field
label="Nama Ruang"
v-model="ruang.namaRuang"
variant="outlined"
density="compact"
hide-details
placeholder="Ruang Konsultasi"
class="input-field"
></v-text-field>
</v-col>
<v-col cols="3">
<v-text-field
label="No. Screen"
v-model="ruang.nomorScreen"
variant="outlined"
density="compact"
hide-details
placeholder="101"
class="input-field"
></v-text-field>
</v-col>
<v-col cols="1">
<v-btn
v-if="formData.ruangList.length > 1"
icon
size="small"
variant="text"
@click="removeRuang(index)"
class="btn-delete-ruang"
>
<v-icon size="18">mdi-delete</v-icon>
</v-btn>
</v-col>
</v-row>
</div>
<v-btn
variant="outlined"
size="small"
@click="addRuang"
class="btn-add-ruang mt-2"
>
<v-icon left size="18">mdi-plus</v-icon>
Tambah Ruang
</v-btn>
</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="btn-cancel"
>
<v-icon left size="18">mdi-close</v-icon>
Batal
</v-btn>
<v-btn
variant="flat"
@click="submitForm"
class="btn-submit"
>
<v-icon left size="18">mdi-content-save</v-icon>
Simpan
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Preview Dialog -->
<v-dialog v-model="previewDialog" max-width="95vw" max-height="95vh" persistent>
<v-card class="preview-dialog-card">
<v-card-title class="preview-dialog-header">
<div class="preview-header-content">
<v-icon size="24" class="mr-2">mdi-door-open</v-icon>
<span class="headline-4">Preview Klinik Ruang: {{ previewItem?.namaKlinik || '' }}</span>
</div>
<v-btn icon variant="text" size="small" class="btn-close" @click="closePreviewDialog">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="preview-content">
<iframe
v-if="previewUrl"
:src="previewUrl"
class="preview-iframe"
frameborder="0"
allowfullscreen
></iframe>
<div v-else class="preview-loading">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
<p class="mt-4">Memuat preview...</p>
</div>
</v-card-text>
</v-card>
</v-dialog>
<!-- Snackbar -->
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
<span class="body-3">{{ snackbar.message }}</span>
<template v-slot:actions>
<v-btn variant="text" @click="snackbar.show = false" size="small">Tutup</v-btn>
</template>
</v-snackbar>
</v-container>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useMasterStore } from '@/stores/masterStore';
import { useClinicStore } from '@/stores/clinicStore';
import { useRuangStore } from '@/stores/ruangStore';
const masterStore = useMasterStore();
const clinicStore = useClinicStore();
const ruangStore = useRuangStore();
const page = ref(1);
const itemsPerPage = ref(10);
const search = ref('');
const filteredTotal = ref(masterStore.ruangData.length);
import { watch } from 'vue';
watch(() => masterStore.ruangData.length, (newLen) => {
if (!search.value) filteredTotal.value = newLen;
}, { immediate: true });
const dialog = ref(false);
const isEdit = ref(false);
const formRef = ref(null);
const previewDialog = ref(false);
const previewItem = ref(null);
const previewUrl = ref('');
const snackbar = ref({
show: false,
message: '',
color: 'success'
});
const headers = ref([
{ title: 'No', value: 'no', sortable: true },
{ title: 'Nama Klinik', value: 'namaKlinik', sortable: true },
{ title: 'Kode', value: 'kodeKlinik', sortable: true },
{ title: 'Nama Ruang', value: 'namaRuang', sortable: true },
{ title: 'Jenis Layanan', value: 'jenisLayanan', sortable: true },
{ title: 'Layar Informasi', value: 'layarInformasi', sortable: false, width: '150px' },
{ title: 'Aksi', value: 'aksi', sortable: false },
]);
const pageCount = computed(() => {
return Math.ceil(filteredTotal.value / itemsPerPage.value) || 1;
});
const showingEntriesText = computed(() => {
if (filteredTotal.value === 0) return 'Showing 0 to 0 of 0 entries';
const start = (page.value - 1) * itemsPerPage.value + 1;
const end = Math.min(page.value * itemsPerPage.value, filteredTotal.value);
return `Showing ${start} to ${end} of ${filteredTotal.value} entries${search.value ? ' (filtered)' : ''}`;
});
const formData = ref({
id: null,
kodeKlinik: '',
namaKlinik: '',
ruangList: [
{ nomorRuang: '', namaRuang: '', nomorScreen: '' }
],
});
const onKlinikChange = (kode) => {
const selectedKlinik = masterStore.getKlinikByKode(kode);
if (selectedKlinik) {
formData.value.namaKlinik = selectedKlinik.nama;
}
};
const addRuang = () => {
formData.value.ruangList.push({
nomorRuang: '',
namaRuang: '',
nomorScreen: ''
});
};
const removeRuang = (index) => {
if (formData.value.ruangList.length > 1) {
formData.value.ruangList.splice(index, 1);
}
};
const openTambahDialog = () => {
isEdit.value = false;
resetForm();
dialog.value = true;
};
const openEditDialog = (item) => {
isEdit.value = true;
formData.value = {
id: item.id,
kodeKlinik: item.kodeKlinik,
namaKlinik: item.namaKlinik,
ruangList: JSON.parse(JSON.stringify(item.ruangList)),
};
dialog.value = true;
};
const closeDialog = () => {
dialog.value = false;
resetForm();
};
const resetForm = () => {
formData.value = {
id: null,
kodeKlinik: '',
namaKlinik: '',
ruangList: [
{ nomorRuang: '', namaRuang: '', nomorScreen: '' }
],
};
if (formRef.value) {
formRef.value.reset();
}
};
const submitForm = async () => {
const { valid } = await formRef.value.validate();
if (!valid) return;
if (formData.value.ruangList.length === 0) {
snackbar.value = {
show: true,
message: 'Tambahkan minimal 1 ruangan',
color: 'warning'
};
return;
}
let result;
if (isEdit.value) {
result = masterStore.updateRuang(formData.value);
} else {
result = masterStore.addRuang(formData.value);
}
if (result.success) {
snackbar.value = { show: true, message: result.message, color: 'success' };
closeDialog();
} else {
snackbar.value = { show: true, message: result.message, color: 'error' };
}
};
const handleDelete = (item) => {
if (confirm(`Hapus ruangan ${item.namaRuang} dari klinik ${item.namaKlinik}?`)) {
const result = masterStore.deleteRuang(item.id);
snackbar.value = {
show: true,
message: result.message,
color: result.success ? 'success' : 'error'
};
}
};
const openPreviewDialog = (item) => {
previewItem.value = item;
// Generate preview URL untuk klinik ruang menggunakan kodeKlinik
previewUrl.value = `/anjungan/antrianklinikruang/${item.kodeKlinik}`;
previewDialog.value = true;
};
const closePreviewDialog = () => {
previewDialog.value = false;
previewItem.value = null;
previewUrl.value = '';
};
// Generate rooms from clinics based on spesialis field
const generateRoomsFromClinics = () => {
console.log('🔄 Generating rooms from clinics...');
const allRooms = [];
let roomIdCounter = 1;
// Get all clinics (Reguler from API + Eksekutif from seed)
const allClinics = clinicStore.clinics;
console.log('📊 Total clinics:', allClinics.length);
console.log('📊 Clinics by type:', {
Reguler: allClinics.filter(c => c.jenisLayanan === 'Reguler').length,
Eksekutif: allClinics.filter(c => c.jenisLayanan === 'Eksekutif').length
});
allClinics.forEach(clinic => {
console.log(`\n Processing clinic: ${clinic.name} (${clinic.kode}) - ${clinic.jenisLayanan}`);
if (clinic.jenisLayanan === 'Reguler') {
// For Reguler: check spesialis from API
if (clinic.spesialis && Array.isArray(clinic.spesialis) && clinic.spesialis.length > 0) {
// Create room ONLY for each specialist (no duplicate clinic-name room)
console.log(` ✅ Has ${clinic.spesialis.length} specialists - creating rooms ONLY from specialists`);
clinic.spesialis.forEach((spec, index) => {
allRooms.push({
id: roomIdCounter++,
kodeKlinik: clinic.kode,
namaKlinik: clinic.name,
kodeRuang: `${clinic.kode}-${spec.idspesialis}`,
namaRuang: spec.Spesialis,
nomorRuang: (index + 1).toString(),
nomorScreen: `${clinic.kode}${index + 1}`,
jenisLayanan: 'Reguler'
});
console.log(` → Room: ${spec.Spesialis}`);
});
} else {
// Only create 1 room with clinic name when NO spesialis
console.log(' ⚠️ No specialists, creating 1 room with clinic name');
allRooms.push({
id: roomIdCounter++,
kodeKlinik: clinic.kode,
namaKlinik: clinic.name,
kodeRuang: clinic.kode,
namaRuang: clinic.name,
nomorRuang: '1',
nomorScreen: `${clinic.kode}1`,
jenisLayanan: 'Reguler'
});
}
} else if (clinic.jenisLayanan === 'Eksekutif') {
// For Eksekutif: always 1 room (no spesialis in seed data)
console.log(' 📦 Eksekutif clinic, creating 1 room');
allRooms.push({
id: roomIdCounter++,
kodeKlinik: clinic.kode,
namaKlinik: clinic.name,
kodeRuang: clinic.kode,
namaRuang: clinic.name,
nomorRuang: '1',
nomorScreen: `${clinic.kode}1`,
jenisLayanan: 'Eksekutif'
});
} else {
console.log(` ⚠️ Unknown jenisLayanan: ${clinic.jenisLayanan}`);
}
});
console.log(`\n✅ Generated ${allRooms.length} rooms from ${allClinics.length} clinics`);
console.log('📋 Rooms by Jenis Layanan:');
console.log(' - Reguler:', allRooms.filter(r => r.jenisLayanan === 'Reguler').length);
console.log(' - Eksekutif:', allRooms.filter(r => r.jenisLayanan === 'Eksekutif').length);
// Update ruangStore with generated rooms
const result = ruangStore.replaceAllRooms(allRooms);
console.log('💾 Rooms saved to ruangStore:', result.message);
return result;
};
// Fetch clinics and generate rooms on mount
onMounted(async () => {
console.log('🚀 MasterKlinikRuang mounted, fetching clinics...');
try {
// Fetch clinics from API (uses cache if available)
const fetchResult = await clinicStore.fetchRegulerClinics();
console.log('📥 Fetch result:', fetchResult);
if (!fetchResult.success) {
snackbar.value = {
show: true,
message: fetchResult.message,
color: 'warning'
};
}
// Generate rooms based on fetched data
const generateResult = generateRoomsFromClinics();
snackbar.value = {
show: true,
message: generateResult.message,
color: 'success'
};
} catch (error) {
console.error('❌ Error in onMounted:', error);
snackbar.value = {
show: true,
message: `Error: ${error.message}`,
color: 'error'
};
}
});
</script>
<style scoped lang="scss">
// Colors from Design System
$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: #3556AE;
$primary-600: #3A61C9;
$success-700: #1B6E53;
$success-600: #009262;
$success-400: #32C997;
$success-300: #84DFC1;
$success-200: #F1FBF8;
$danger-600: #E02B1D;
// Font Family & Weights
$font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-weight-regular: 400;
$font-weight-medium: 500;
$font-weight-semibold: 600;
// Apply font family
* {
font-family: $font-family-base;
}
// ============================================
// PAGE HEADER
// ============================================
.page-header {
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
border-radius: 0 !important;
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 28px;
height: 80px;
color: $neutral-100;
}
.header-left {
display: flex;
align-items: center;
}
.header-icon {
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 12px;
margin-right: 16px;
backdrop-filter: blur(10px);
}
.page-title {
font-size: 32px;
line-height: 40px;
font-weight: $font-weight-semibold;
margin: 0;
color: $neutral-100;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.page-subtitle {
margin: 2px 0 0 0;
opacity: 0.9;
font-size: 15px;
line-height: 22px;
font-weight: $font-weight-regular;
color: $neutral-100;
}
.header-stats {
display: flex;
align-items: center;
gap: 8px;
}
.stat-chip {
color: $primary-600 !important;
font-weight: $font-weight-semibold;
font-size: 14px;
line-height: 20px;
}
.add-btn {
font-weight: $font-weight-semibold;
text-transform: none;
letter-spacing: 0.5px;
font-size: 16px;
line-height: 24px;
color: $primary-600 !important;
}
// ============================================
// ACTION BAR
// ============================================
.action-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background: $neutral-100;
border-radius: 12px;
border: 1px solid $neutral-400;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.action-bar-left {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.action-btn {
font-weight: $font-weight-semibold;
text-transform: none;
letter-spacing: 0.5px;
font-size: 16px;
line-height: 24px;
}
// ============================================
// DATA TABLE
// ============================================
.data-table {
font-family: $font-family-base;
}
.chip-success-outline {
border: 1px solid $primary-600;
background-color: transparent !important;
color: $primary-600 !important;
font-weight: $font-weight-medium;
font-size: 12px;
line-height: 16px;
}
.btn-edit {
background-color: $primary-600 !important;
color: $neutral-100 !important;
text-transform: none;
font-weight: $font-weight-semibold;
font-size: 14px;
line-height: 20px;
}
.btn-delete {
border-color: $danger-600 !important;
color: $danger-600 !important;
text-transform: none;
font-weight: $font-weight-semibold;
font-size: 14px;
line-height: 20px;
}
// ============================================
// DIALOG
// ============================================
.dialog-card {
font-family: $font-family-base;
}
.dialog-header {
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
color: $neutral-100;
padding: 20px 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.headline-4 {
font-size: 20px;
line-height: 28px;
font-weight: $font-weight-semibold;
margin: 0;
}
.btn-close {
color: $neutral-100 !important;
}
.dialog-content {
padding: 24px !important;
background: $neutral-300;
}
.dialog-actions {
padding: 16px 24px;
background: $neutral-300;
}
// ============================================
// FORM ELEMENTS
// ============================================
.field-group {
background: $neutral-100;
padding: 20px;
border-radius: 12px;
margin-bottom: 0;
border: 1px solid $neutral-400;
}
.group-label {
font-size: 14px;
line-height: 20px;
font-weight: $font-weight-semibold;
color: $success-600;
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
display: flex;
align-items: center;
gap: 6px;
}
.icon-label {
color: $success-600 !important;
}
.body-3 {
font-size: 14px;
line-height: 20px;
font-weight: $font-weight-regular;
}
.text-medium {
font-weight: $font-weight-medium !important;
}
.input-field {
font-size: 14px;
line-height: 20px;
}
.divider-section {
border-color: $neutral-400 !important;
}
// ============================================
// CHIPS
// ============================================
.chip-success-small {
background-color: $success-600 !important;
color: $neutral-100 !important;
font-weight: $font-weight-semibold;
font-size: 12px;
line-height: 16px;
}
.chip-reguler {
background-color: $primary-600 !important;
color: $neutral-100 !important;
font-weight: $font-weight-semibold;
font-size: 12px;
line-height: 16px;
}
.chip-eksekutif {
background-color: $secondary-600 !important;
color: $neutral-100 !important;
font-weight: $font-weight-semibold;
font-size: 12px;
line-height: 16px;
}
.ruang-tags {
display: flex;
flex-wrap: nowrap;
max-width: 600px;
overflow: hidden;
}
.chip-primary-outline {
border: 1px solid $primary-600;
background-color: transparent !important;
color: $primary-600 !important;
font-weight: $font-weight-medium;
font-size: 12px;
line-height: 16px;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chip-neutral {
background-color: $neutral-600 !important;
color: $neutral-100 !important;
font-weight: $font-weight-medium;
font-size: 12px;
line-height: 16px;
flex-shrink: 0;
}
// ============================================
// RUANG ITEMS
// ============================================
.ruang-item {
background: $neutral-300;
padding: 12px;
border-radius: 8px;
margin-bottom: 8px;
border: 1px solid $neutral-500;
}
.ruang-badge {
background: linear-gradient(135deg, $success-600 0%, $success-700 100%);
color: $neutral-100;
padding: 8px;
border-radius: 6px;
text-align: center;
font-weight: $font-weight-semibold;
}
.btn-delete-ruang {
color: $danger-600 !important;
}
.btn-add-ruang {
border-color: $success-600 !important;
color: $success-600 !important;
text-transform: none;
font-weight: $font-weight-semibold;
font-size: 14px;
line-height: 20px;
}
// ============================================
// BUTTONS
// ============================================
.btn-cancel {
border-color: $neutral-600 !important;
color: $neutral-800 !important;
text-transform: none;
font-weight: $font-weight-semibold;
font-size: 16px;
line-height: 24px;
min-width: 100px;
}
.btn-submit {
background-color: $success-600 !important;
color: $neutral-100 !important;
text-transform: none;
font-weight: $font-weight-semibold;
font-size: 16px;
line-height: 24px;
min-width: 100px;
}
// ============================================
// PREVIEW DIALOG
// ============================================
.btn-preview {
background-color: $success-600 !important;
color: $neutral-100 !important;
text-transform: none;
font-weight: $font-weight-semibold;
font-size: 14px;
line-height: 20px;
}
.preview-dialog-card {
font-family: $font-family-base;
height: 95vh;
display: flex;
flex-direction: column;
}
.preview-dialog-header {
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
color: $neutral-100;
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.preview-header-content {
display: flex;
align-items: center;
}
.preview-content {
padding: 0 !important;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: $neutral-800;
overflow: hidden;
}
.preview-iframe {
width: 100%;
height: 100%;
min-height: 80vh;
border: none;
}
.preview-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: $neutral-100;
padding: 40px;
}
// ============================================
// RESPONSIVE
// ============================================
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 16px;
}
.header-stats {
width: 100%;
flex-wrap: wrap;
}
}
</style>