1025 lines
27 KiB
Vue
1025 lines
27 KiB
Vue
<!-- eslint-disable vue/valid-v-slot -->
|
|
<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-view-dashboard</v-icon>
|
|
</div>
|
|
<div class="header-text">
|
|
<h2 class="page-title">Master Loket</h2>
|
|
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<v-container>
|
|
<!-- Action Bar -->
|
|
<div class="action-bar mb-4">
|
|
<v-spacer></v-spacer>
|
|
<v-btn
|
|
color="primary-600"
|
|
variant="flat"
|
|
elevation="0"
|
|
class="action-btn text-white"
|
|
@click="openTambahDialog"
|
|
>
|
|
<v-icon left size="20">mdi-plus-circle</v-icon>
|
|
Tambah Loket
|
|
</v-btn>
|
|
</div>
|
|
|
|
<v-card>
|
|
<!-- Table -->
|
|
<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="loketHeaders"
|
|
:items="loketStore.loketData"
|
|
:items-per-page="itemsPerPage"
|
|
:search="search"
|
|
item-value="id"
|
|
class="elevation-0 data-table"
|
|
hover
|
|
@update:itemsLength="filteredTotal = $event"
|
|
>
|
|
<template #item.pelayanan="{ item }">
|
|
<v-chip
|
|
v-for="(serviceKode, idx) in item.pelayanan.slice(0, 2)"
|
|
:key="idx"
|
|
size="small"
|
|
class="mr-1 mb-1 chip-primary-outline"
|
|
>
|
|
{{ getServiceName(item, serviceKode) }}
|
|
</v-chip>
|
|
<v-chip
|
|
v-if="item.pelayanan.length > 2"
|
|
size="small"
|
|
class="chip-neutral"
|
|
>
|
|
+{{ item.pelayanan.length - 2 }}
|
|
</v-chip>
|
|
</template>
|
|
|
|
<template #item.tipeLoket="{ item }">
|
|
<v-chip
|
|
size="small"
|
|
:class="['JKN', 'Reguler', 'REGULER'].includes(item.tipeLoket || item.tipeloket) ? 'chip-reguler' : 'chip-eksekutif'"
|
|
>
|
|
{{ item.tipeLoket || item.tipeloket }}
|
|
</v-chip>
|
|
</template>
|
|
|
|
<template #item.pembayaran="{ item }">
|
|
<v-chip
|
|
v-for="(payment, idx) in (Array.isArray(item.pembayaran) ? item.pembayaran.slice(0, 2) : [item.pembayaran])"
|
|
:key="idx"
|
|
size="small"
|
|
class="mr-1 mb-1 chip-payment"
|
|
>
|
|
{{ payment }}
|
|
</v-chip>
|
|
<v-chip
|
|
v-if="Array.isArray(item.pembayaran) && item.pembayaran.length > 2"
|
|
size="small"
|
|
class="chip-neutral"
|
|
>
|
|
+{{ item.pembayaran.length - 2 }}
|
|
</v-chip>
|
|
</template>
|
|
|
|
<template #item.loketAktif="{ item }">
|
|
<v-chip
|
|
size="small"
|
|
:color="item.loketAktif ? 'success' : 'error'"
|
|
variant="flat"
|
|
class="text-capitalize"
|
|
>
|
|
{{ item.loketAktif ? 'Aktif' : 'Tidak Aktif' }}
|
|
</v-chip>
|
|
</template>
|
|
|
|
<template #item.layarInformasi="{ item }">
|
|
<v-btn
|
|
size="small"
|
|
variant="outlined"
|
|
color="primary-600"
|
|
@click="openPreview(item)"
|
|
class="btn-preview mr-2"
|
|
>
|
|
<v-icon size="16" left>mdi-eye</v-icon>
|
|
Preview
|
|
</v-btn>
|
|
</template>
|
|
|
|
<template #item.aksi="{ item }">
|
|
<v-btn
|
|
v-if="item.source === 'api'"
|
|
size="small"
|
|
variant="flat"
|
|
color="info-600"
|
|
class="btn-refresh mr-2"
|
|
icon="mdi-refresh"
|
|
@click="handleRefreshSingle(item)"
|
|
:loading="loketStore.isLoadingAPI && refreshingId === item.id"
|
|
>
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
size="small"
|
|
variant="flat"
|
|
color="warning-600"
|
|
class="btn-edit mr-2"
|
|
@click="openEditDialog(item)"
|
|
>
|
|
<v-icon size="16" left>mdi-pencil</v-icon>
|
|
Edit
|
|
</v-btn>
|
|
<v-btn
|
|
size="small"
|
|
variant="flat"
|
|
color="error-600"
|
|
class="btn-delete"
|
|
@click="handleDelete(item)"
|
|
>
|
|
<v-icon size="16" left>mdi-delete</v-icon>
|
|
Delete
|
|
</v-btn>
|
|
</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>
|
|
</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 Loket' : 'Tambah Loket Baru' }}</span>
|
|
<v-btn icon variant="text" size="small" class="btn-close" @click="closeDialog">
|
|
<v-icon>mdi-close</v-icon>
|
|
</v-btn>
|
|
</v-card-title>
|
|
|
|
<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
|
|
v-model="formData.namaLoket"
|
|
label="Nama Loket"
|
|
variant="outlined"
|
|
density="compact"
|
|
:rules="[v => !!v || 'Nama loket harus diisi']"
|
|
hide-details="auto"
|
|
class="mb-3 input-field"
|
|
placeholder="Loket A"
|
|
/>
|
|
|
|
<v-text-field
|
|
v-model.number="formData.kuota"
|
|
label="Kuota Bangku"
|
|
type="number"
|
|
variant="outlined"
|
|
density="compact"
|
|
:rules="[v => !!v || 'Kuota harus diisi', v => v > 0 || 'Kuota minimal 1']"
|
|
hide-details="auto"
|
|
class="mb-3 input-field"
|
|
placeholder="500"
|
|
/>
|
|
</div>
|
|
|
|
<v-divider class="my-4 divider-section"/>
|
|
|
|
<!-- Konfigurasi -->
|
|
<div class="field-group">
|
|
<div class="group-label">Konfigurasi</div>
|
|
|
|
<v-select
|
|
v-model="formData.statusPelayanan"
|
|
label="Status Pelayanan"
|
|
:items="['RAWAT JALAN', 'RAWAT INAP']"
|
|
variant="outlined"
|
|
density="compact"
|
|
:rules="[v => !!v || 'Status pelayanan harus dipilih']"
|
|
hide-details="auto"
|
|
class="mb-3 input-field"
|
|
/>
|
|
|
|
<v-row dense>
|
|
<v-col cols="6">
|
|
<v-select
|
|
v-model="formData.tipeLoket"
|
|
label="Tipe Loket"
|
|
:items="['REGULER', 'EKSEKUTIF']"
|
|
variant="outlined"
|
|
density="compact"
|
|
:rules="[v => !!v || 'Tipe Loket harus dipilih']"
|
|
hide-details="auto"
|
|
class="input-field"
|
|
/>
|
|
</v-col>
|
|
<v-col cols="6">
|
|
<v-switch
|
|
v-model="formData.loketAktif"
|
|
:label="formData.loketAktif ? 'Loket Aktif' : 'Loket Tidak Aktif'"
|
|
color="success"
|
|
hide-details
|
|
density="compact"
|
|
class="mt-1"
|
|
/>
|
|
</v-col>
|
|
</v-row>
|
|
</div>
|
|
|
|
<v-divider class="my-4 divider-section"/>
|
|
|
|
<!-- Pelayanan -->
|
|
<div class="field-group">
|
|
<div class="group-label">
|
|
<v-icon size="18" class="icon-label">mdi-hospital-box</v-icon>
|
|
<span>Pelayanan</span>
|
|
</div>
|
|
|
|
<v-select
|
|
v-model="formData.pelayanan"
|
|
label="Pilih Pelayanan"
|
|
:items="masterStore.availableServices"
|
|
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"
|
|
class="input-field"
|
|
>
|
|
<template #chip="{ props, item }">
|
|
<v-chip
|
|
v-bind="props"
|
|
closable
|
|
size="small"
|
|
class="chip-primary"
|
|
>
|
|
{{ item.raw.nama }}
|
|
</v-chip>
|
|
</template>
|
|
<template #item="{ props, item }">
|
|
<v-list-item v-bind="props">
|
|
<template #prepend>
|
|
<v-chip size="x-small" class="chip-primary-small">{{ item.raw.id }}</v-chip>
|
|
</template>
|
|
<v-list-item-title class="body-3">{{ item.raw.nama }}</v-list-item-title>
|
|
</v-list-item>
|
|
</template>
|
|
</v-select>
|
|
|
|
<!-- Selected Services Preview -->
|
|
<div v-if="formData.pelayanan.length > 0" class="selected-preview">
|
|
<v-icon size="14" class="icon-success">mdi-check-circle</v-icon>
|
|
<small class="caption-2">{{ formData.pelayanan.length }} pelayanan dipilih</small>
|
|
</div>
|
|
</div>
|
|
</v-form>
|
|
</v-card-text>
|
|
|
|
<v-divider/>
|
|
|
|
<v-card-actions class="dialog-actions">
|
|
<v-spacer/>
|
|
<v-btn
|
|
variant="outlined"
|
|
class="btn-cancel"
|
|
@click="closeDialog"
|
|
>
|
|
<v-icon left size="18">mdi-close</v-icon>
|
|
Batal
|
|
</v-btn>
|
|
<v-btn
|
|
variant="flat"
|
|
class="btn-submit"
|
|
@click="submitForm"
|
|
>
|
|
<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-view-dashboard</v-icon>
|
|
<span class="headline-4">Preview Loket: {{ previewItem?.namaLoket || '' }}</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 pa-0">
|
|
<iframe
|
|
v-if="previewItem"
|
|
:src="previewUrl"
|
|
class="preview-iframe"
|
|
frameborder="0"
|
|
></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 #actions>
|
|
<v-btn variant="text" size="small" @click="snackbar.show = false">Tutup</v-btn>
|
|
</template>
|
|
</v-snackbar>
|
|
</v-container>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, computed } from 'vue';
|
|
import { useMasterStore } from '@/stores/masterStore';
|
|
import { useLoketStore } from '@/stores/loketStore';
|
|
|
|
const masterStore = useMasterStore();
|
|
const loketStore = useLoketStore();
|
|
const page = ref(1);
|
|
const itemsPerPage = ref(10);
|
|
const search = ref('');
|
|
const filteredTotal = ref((loketStore.loketData || []).length);
|
|
|
|
import { watch } from 'vue';
|
|
watch(() => (loketStore.loketData || []).length, (newLen) => {
|
|
if (!search.value) filteredTotal.value = newLen;
|
|
}, { immediate: true });
|
|
const dialog = ref(false);
|
|
const previewDialog = ref(false);
|
|
const previewItem = ref(null);
|
|
const isEdit = ref(false);
|
|
const formRef = ref(null);
|
|
|
|
const snackbar = ref({
|
|
show: false,
|
|
message: '',
|
|
color: 'success'
|
|
});
|
|
|
|
// Helper untuk mendapatkan nama layanan/klinik
|
|
const getServiceName = (item, serviceId) => {
|
|
// 1. Coba cari di _spesialisDetail (data dari API)
|
|
// Convert ID ke string untuk perbandingan aman
|
|
const idStr = String(serviceId);
|
|
|
|
if (item._spesialisDetail && Array.isArray(item._spesialisDetail)) {
|
|
const detail = item._spesialisDetail.find(s => String(s.idklinik) === idStr);
|
|
if (detail && detail.namaklinik) {
|
|
return detail.namaklinik;
|
|
}
|
|
}
|
|
|
|
// 2. Fallback ke masterStore.getKlinikById untuk local data (numeric ID seperti 1000, 1001)
|
|
// Local data EKSEKUTIF menggunakan ID numeric, bukan kode
|
|
const byId = masterStore.getKlinikById(serviceId);
|
|
if (byId && byId.nama) return byId.nama;
|
|
|
|
// 3. Fallback ke masterStore.getKlinikNameByKode untuk data yang pakai kode
|
|
return masterStore.getKlinikNameByKode(serviceId);
|
|
};
|
|
|
|
// Auto-fetch loket dari API saat component mount
|
|
onMounted(async () => {
|
|
console.log('🚀 MasterLoket mounted, fetching loket dari API...');
|
|
|
|
try {
|
|
const result = await loketStore.fetchLoketFromAPI(true); // force = true untuk fresh data
|
|
|
|
console.log('📊 Fetch result:', result);
|
|
console.log('📊 loketStore.loketData length:', loketStore.loketData.value?.length);
|
|
console.log('📊 loketStore.loketData:', loketStore.loketData.value);
|
|
|
|
if (result.success) {
|
|
console.log('✅ Fetch successful:', result.message);
|
|
|
|
snackbar.value = {
|
|
show: true,
|
|
message: result.message,
|
|
color: 'success'
|
|
};
|
|
} else {
|
|
console.error('❌ Fetch failed:', result.message);
|
|
snackbar.value = {
|
|
show: true,
|
|
message: result.message,
|
|
color: 'warning'
|
|
};
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Error in onMounted:', error);
|
|
snackbar.value = {
|
|
show: true,
|
|
message: `Error: ${error.message}`,
|
|
color: 'error'
|
|
};
|
|
}
|
|
});
|
|
|
|
const loketHeaders = ref([
|
|
{ title: "No", value: "no" },
|
|
{ title: "Nama Loket", value: "namaLoket" },
|
|
{ title: "Kuota", value: "kuota" },
|
|
{ title: "Pelayanan", value: "pelayanan" },
|
|
{ title: "Tipe Loket", value: "tipeLoket" },
|
|
{ title: "Pembayaran", value: "pembayaran" },
|
|
{ title: "Status Loket Aktif", value: "loketAktif" },
|
|
{ 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,
|
|
namaLoket: '',
|
|
kuota: null,
|
|
statusPelayanan: '',
|
|
tipeLoket: '',
|
|
pelayanan: [],
|
|
loketAktif: true,
|
|
});
|
|
|
|
const openTambahDialog = () => {
|
|
isEdit.value = false;
|
|
resetForm();
|
|
dialog.value = true;
|
|
};
|
|
|
|
const openEditDialog = (item) => {
|
|
isEdit.value = true;
|
|
formData.value = {
|
|
id: item.id,
|
|
namaLoket: item.namaLoket,
|
|
kuota: item.kuota,
|
|
statusPelayanan: item.statusPelayanan || 'RAWAT JALAN',
|
|
tipeLoket: item.tipeLoket || item.tipeloket,
|
|
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: '',
|
|
tipeLoket: '',
|
|
keterangan: '',
|
|
pelayanan: [],
|
|
};
|
|
if (formRef.value) {
|
|
formRef.value.reset();
|
|
}
|
|
};
|
|
|
|
const submitForm = async () => {
|
|
const { valid } = await formRef.value.validate();
|
|
|
|
if (!valid) return;
|
|
|
|
let result;
|
|
if (isEdit.value) {
|
|
result = masterStore.updateLoket(formData.value);
|
|
} else {
|
|
result = masterStore.addLoket(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' };
|
|
}
|
|
};
|
|
|
|
// Computed property untuk preview URL
|
|
const previewUrl = computed(() => {
|
|
if (!previewItem.value) return '';
|
|
return `/anjungan/antrianloket/${previewItem.value.id}`;
|
|
});
|
|
|
|
const openPreview = (item) => {
|
|
previewItem.value = item;
|
|
previewDialog.value = true;
|
|
};
|
|
|
|
const closePreviewDialog = () => {
|
|
previewDialog.value = false;
|
|
previewItem.value = null;
|
|
};
|
|
|
|
const handleDelete = (item) => {
|
|
if (confirm(`Hapus loket ${item.namaLoket}?`)) {
|
|
const result = masterStore.deleteLoket(item.id);
|
|
snackbar.value = {
|
|
show: true,
|
|
message: result.message,
|
|
color: result.success ? 'success' : 'error'
|
|
};
|
|
}
|
|
};
|
|
|
|
const refreshingId = ref(null);
|
|
|
|
const handleRefreshSingle = async (item) => {
|
|
if (!item.id) return;
|
|
|
|
refreshingId.value = item.id;
|
|
try {
|
|
const result = await loketStore.fetchSingleLoketFromAPI(item.id);
|
|
snackbar.value = {
|
|
show: true,
|
|
message: result.message,
|
|
color: result.success ? 'success' : 'warning'
|
|
};
|
|
} catch (error) {
|
|
snackbar.value = {
|
|
show: true,
|
|
message: `Error: ${error.message}`,
|
|
color: 'error'
|
|
};
|
|
} finally {
|
|
refreshingId.value = null;
|
|
}
|
|
};
|
|
</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;
|
|
$primary-300: #9AB2F1;
|
|
|
|
$secondary-600: #0671E0;
|
|
|
|
$success-600: #009262;
|
|
$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;
|
|
}
|
|
|
|
.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-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;
|
|
|
|
/* Vertically center-align table headers */
|
|
:deep(th) {
|
|
vertical-align: middle !important;
|
|
}
|
|
|
|
/* Vertically center-align table cells */
|
|
:deep(td) {
|
|
vertical-align: middle !important;
|
|
}
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.chip-neutral {
|
|
background-color: $neutral-600 !important;
|
|
color: $neutral-100 !important;
|
|
font-weight: $font-weight-medium;
|
|
font-size: 12px;
|
|
line-height: 16px;
|
|
}
|
|
|
|
.chip-reguler {
|
|
background-color: #009262 !important;
|
|
color: #FFFFFF !important;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.chip-eksekutif {
|
|
background-color: #E67E22 !important;
|
|
color: #FFFFFF !important;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.chip-payment {
|
|
background-color: #3A61C9 !important;
|
|
color: #FFFFFF !important;
|
|
font-weight: 500;
|
|
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-preview {
|
|
background-color: $secondary-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: $primary-600;
|
|
margin-bottom: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.icon-label {
|
|
color: $primary-600 !important;
|
|
}
|
|
|
|
.caption-2 {
|
|
font-size: 12px;
|
|
line-height: 16px;
|
|
font-weight: $font-weight-regular;
|
|
color: $neutral-700;
|
|
}
|
|
|
|
.body-3 {
|
|
font-size: 14px;
|
|
line-height: 20px;
|
|
font-weight: $font-weight-regular;
|
|
}
|
|
|
|
.input-field {
|
|
font-size: 14px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
.divider-section {
|
|
border-color: $neutral-400 !important;
|
|
}
|
|
|
|
/* ============================================ */
|
|
/* CHIPS */
|
|
/* ============================================ */
|
|
.chip-primary {
|
|
background-color: $primary-600 !important;
|
|
color: $neutral-100 !important;
|
|
font-weight: $font-weight-medium;
|
|
}
|
|
|
|
.chip-primary-small {
|
|
background-color: $primary-600 !important;
|
|
color: $neutral-100 !important;
|
|
font-weight: $font-weight-semibold;
|
|
font-size: 12px;
|
|
line-height: 16px;
|
|
}
|
|
|
|
/* ============================================ */
|
|
/* SELECTED PREVIEW */
|
|
/* ============================================ */
|
|
.selected-preview {
|
|
margin-top: 8px;
|
|
padding: 8px 12px;
|
|
background: $success-200;
|
|
border-radius: 6px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
border: 1px solid $success-300;
|
|
}
|
|
|
|
.icon-success {
|
|
color: $success-600 !important;
|
|
}
|
|
|
|
/* ============================================ */
|
|
/* 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: $primary-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 */
|
|
/* ============================================ */
|
|
.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: auto;
|
|
}
|
|
|
|
.preview-iframe {
|
|
width: 100%;
|
|
height: 75vh;
|
|
min-height: 600px;
|
|
border: none;
|
|
}
|
|
|
|
.preview-loading {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: $neutral-100;
|
|
padding: 40px;
|
|
}
|
|
</style> |