update loket dan monitoring

This commit is contained in:
Fanrouver
2026-01-19 15:27:50 +07:00
parent f2efd8355e
commit 81b877bb7f
5 changed files with 383 additions and 175 deletions

No files matched your search

+113 -18
View File
@@ -1,19 +1,30 @@
<template>
<v-card
class="pa-4 rounded-lg elevation-2 d-flex flex-column ticket-card"
class="rounded-lg elevation-2 d-flex flex-column ticket-card"
color="white"
style="border-top: 8px solid #FFB95F;"
height="100%"
>
<div class="d-flex align-center justify-center mb-4">
<v-icon size="36" color="orange-lighten-2" class="mr-2">mdi-hospital-box-outline</v-icon>
<div class="text-h6 font-weight-bold text-orange-lighten-2">{{ title }}</div>
</div>
<!-- Header dengan background biru -->
<v-card-title class="ticket-header">
<div class="ticket-header-content">
<v-icon color="white" class="mr-2">mdi-hospital-box-outline</v-icon>
<span class="ticket-header-title">{{ cleanTitle }}</span>
<v-chip
v-if="extractedQueueNumber"
size="small"
class="ml-2 ticket-header-chip"
color="primary-700"
>
Antrean: {{ extractedQueueNumber }}
</v-chip>
</div>
</v-card-title>
<v-divider class="mb-4"></v-divider>
<!-- Content -->
<v-card-text class="ticket-content pa-4">
<div class="timeline-scroll-container flex-grow-1">
<v-timeline density="compact" align="start" line-inset="12" line-color="#FFB95F">
<v-timeline density="compact" align="start" line-inset="12" line-color="#567EE7">
<v-timeline-item
v-for="(step, index) in steps"
:key="index"
@@ -24,12 +35,12 @@
>
<div class="d-flex flex-column align-start">
<span
class="font-weight-bold"
:class="index === currentStepIndex ? 'text-orange-lighten-1' : index < currentStepIndex ? 'text-success' : 'text-grey-darken-1'"
class="font-weight-bold"
:class="getStepTextClass(index)"
>
{{ step.label }}
</span>
<span class="text-caption text-grey-darken-1">
<span class="text-caption" :class="getStepDateClass(index)">
{{ step.date !== '-' ? `${step.date}, ${step.time}` : 'Menunggu' }}
</span>
</div>
@@ -42,13 +53,14 @@
<v-btn
size="large"
variant="tonal"
color="#FFA532"
class="mt-auto font-weight-bold reprint-button"
color="#567EE7"
class="mt-auto font-weight-bold reprint-button text-secondary-500"
prepend-icon="mdi-printer"
block
>
Cetak Ulang Tiket
</v-btn>
</v-card-text>
</v-card>
</template>
@@ -59,13 +71,30 @@ const props = defineProps({
title: String,
steps: Array,
color: String,
currentStepLabel: String,
currentStepLabel: String,
queueNumber: String, // Nomor antrean untuk ditampilkan di chip
});
const currentStepIndex = computed(() => {
return props.steps.findIndex(step => step.label === props.currentStepLabel);
});
// Extract nomor antrean dari title jika queueNumber tidak disediakan
const extractedQueueNumber = computed(() => {
if (props.queueNumber) {
return props.queueNumber;
}
// Extract dari title format: "JIWA (Antrean: 1)" atau "RADIOLOGI (Antrean: 5)"
const match = props.title.match(/\(Antrean:\s*(\d+)\)/);
return match ? match[1] : null;
});
// Clean title tanpa nomor antrean untuk ditampilkan di header
const cleanTitle = computed(() => {
// Hapus bagian "(Antrean: X)" dari title
return props.title.replace(/\s*\(Antrean:\s*\d+\)/g, '').trim();
});
const getStepIcon = (index) => {
if (index < currentStepIndex.value) {
return 'mdi-check';
@@ -77,16 +106,39 @@ const getStepIcon = (index) => {
};
const getStepColor = (index) => {
// Warna berbeda berdasarkan status step (dari _colors.scss)
if (index < currentStepIndex.value) {
return 'success';
return '#33A484'; // success-600 - hijau untuk completed
} else if (index === currentStepIndex.value) {
return 'orange-lighten-1';
return '#FF8441'; // secondary-500 - orange untuk current
}
return 'grey-lighten-1';
return '#CDD4DC'; // neutral-500 - abu-abu untuk pending
};
const getStepTextClass = (index) => {
// Warna text label sesuai dengan warna dot
if (index < currentStepIndex.value) {
return 'text-success-600'; // hijau untuk completed
} else if (index === currentStepIndex.value) {
return 'text-secondary-500'; // orange untuk current
}
return 'text-neutral-600'; // abu-abu untuk pending
};
const getStepDateClass = (index) => {
// Warna text date/time lebih terang
if (index < currentStepIndex.value) {
return 'text-success-400'; // hijau terang untuk completed
} else if (index === currentStepIndex.value) {
return 'text-secondary-400'; // orange terang untuk current
}
return 'text-neutral-500'; // abu-abu untuk pending
};
</script>
<style scoped>
<style scoped lang="scss">
@import '@/assets/scss/_colors.scss';
/* =============================================== */
/* SCROLLABLE TIMELINE STYLES (Menggunakan Tinggi Tetap) */
/* =============================================== */
@@ -137,6 +189,49 @@ const getStepColor = (index) => {
min-height: 550px !important;
display: flex !important;
flex-direction: column !important;
overflow: hidden;
}
/* =============================================== */
/* TICKET HEADER */
/* =============================================== */
.ticket-header {
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
color: $neutral-100;
padding: 16px 20px;
box-shadow: 0 2px 8px rgba(58, 97, 201, 0.2);
border-radius: 8px 8px 0 0;
}
.ticket-header-content {
display: flex;
align-items: center;
font-weight: 700;
font-size: 16px;
gap: 8px;
width: 100%;
}
.ticket-header-title {
flex: 1;
color: $neutral-100;
font-weight: 700;
}
.ticket-header-chip {
color: $neutral-100 !important;
font-weight: 600;
font-size: 12px;
}
/* =============================================== */
/* TICKET CONTENT */
/* =============================================== */
.ticket-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 24px !important;
}
/* =============================================== */
+3 -1
View File
@@ -77,6 +77,8 @@ export default defineNuxtConfig({
keycloakClientId: process.env.KEYCLOAK_CLIENT_ID,
keycloakClientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
keycloakIssuer: process.env.KEYCLOAK_ISSUER,
keycloakLogoutUri: process.env.KEYCLOAK_LOGOUT_URI, // Optional: custom logout URI
postLogoutRedirectUri: process.env.POST_LOGOUT_REDIRECT_URI, // Optional: custom post-logout redirect URI
public: {
authUrl: process.env.AUTH_ORIGIN,
// authUrl: process.env.AUTH_ORIGIN || "http://10.10.150.175:3001",
@@ -134,4 +136,4 @@ export default defineNuxtConfig({
noExternal: ["vuetify"],
},
},
});
});
+239 -127
View File
@@ -35,10 +35,16 @@
>
<!-- Loket Header -->
<div class="loket-header-bar">
<div class="loket-name">{{ loketData.nama }}</div>
<div class="queue-count">
<v-icon size="18" color="white">mdi-account-multiple</v-icon>
<span class="count-number">{{ loketData.totalQueues }}</span>
<div class="loket-header-content">
<span class="loket-name">{{ loketData.nama }}</span>
<v-chip
size="default"
class="ml-2 loket-header-chip"
color="primary-700"
>
<v-icon start size="20" color="white">mdi-account-multiple</v-icon>
{{ loketData.totalQueues }}
</v-chip>
</div>
</div>
@@ -46,22 +52,43 @@
<div
:ref="el => setQueueDisplayRef(loketData.id, el)"
class="queue-display"
:class="{ 'has-scroll': loketData.queues.length > 20 }"
:class="{
'has-scroll': loketData.queues.length > 20,
'ticker-scroll': loketData.queues.length > 0
}"
>
<!-- Ticket Cards List -->
<div v-if="loketData.queues.length > 0" class="ticket-cards-list" :style="ticketGridStyle">
<div
v-for="(queue, index) in loketData.queues"
:key="`${loketData.id}-${queue.no}-${queue.barcode || index}`"
class="ticket-card"
:class="{ 'ticket-card-fast-track': queue.fastTrack === 'YA' }"
<!-- Ticket Cards List - Infinite Ticker -->
<div v-if="loketData.queues.length > 0" class="ticket-cards-wrapper">
<div
class="ticket-cards-list ticker-container"
:style="ticketGridStyle"
>
<!-- Fast Track Badge - Pojok Kanan Atas -->
<div v-if="queue.fastTrack === 'YA'" class="ticket-fast-track">
<v-icon color="white" size="14">mdi-flash</v-icon>
<!-- Original Content -->
<div
v-for="(queue, index) in loketData.queues"
:key="`${loketData.id}-${queue.no}-${queue.barcode || index}`"
class="ticket-card"
:class="{ 'ticket-card-fast-track': queue.fastTrack === 'YA' }"
>
<!-- Fast Track Badge - Pojok Kanan Atas -->
<div v-if="queue.fastTrack === 'YA'" class="ticket-fast-track">
<v-icon color="white" size="14">mdi-flash</v-icon>
</div>
<div class="ticket-number">{{ queue.noAntrian?.split(" |")[0] || queue.no || '' }}</div>
</div>
<!-- Duplicated Content for Seamless Infinite Loop -->
<div
v-for="(queue, index) in loketData.queues"
:key="`${loketData.id}-dup-${queue.no}-${queue.barcode || index}`"
class="ticket-card"
:class="{ 'ticket-card-fast-track': queue.fastTrack === 'YA' }"
>
<!-- Fast Track Badge - Pojok Kanan Atas -->
<div v-if="queue.fastTrack === 'YA'" class="ticket-fast-track">
<v-icon color="white" size="14">mdi-flash</v-icon>
</div>
<div class="ticket-number">{{ queue.noAntrian?.split(" |")[0] || queue.no || '' }}</div>
</div>
<div class="ticket-number">{{ queue.noAntrian?.split(" |")[0] || queue.no || '' }}</div>
<!-- <div v-if="queue.klinik" class="ticket-klinik">{{ queue.klinik }}</div> -->
</div>
</div>
@@ -70,8 +97,9 @@
v-else
class="empty-queue"
>
<v-icon size="40" color="grey-lighten-3">mdi-minus-circle-outline</v-icon>
<v-icon size="64" color="grey-lighten-2" class="empty-icon">mdi-ticket-outline</v-icon>
<div class="empty-text">Tidak Ada Antrian</div>
<div class="empty-subtext">Antrian akan muncul di sini</div>
</div>
</div>
</div>
@@ -376,61 +404,93 @@ const updateTime = () => {
})
}
// Auto-scroll setiap 2 detik dengan loop (atas -> bawah -> atas)
let scrollIntervals = {}
// Ticker scroll dengan smooth continuous animation
let tickerAnimations = {}
const startAutoScroll = () => {
// Clear existing intervals
Object.values(scrollIntervals).forEach(interval => {
if (interval) clearInterval(interval)
// Clear existing animations
Object.values(tickerAnimations).forEach(animation => {
if (animation) {
cancelAnimationFrame(animation.frameId)
}
})
scrollIntervals = {}
tickerAnimations = {}
nextTick(() => {
displayedLokets.value.forEach(loketData => {
const container = queueDisplayRefs.value[loketData.id]
if (container && loketData.queues.length > 0) {
let isScrollingDown = true
let currentScrollTop = 0
const maxScroll = container.scrollHeight - container.clientHeight
// Jika konten tidak cukup untuk di-scroll, skip
if (maxScroll <= 0) {
return
}
scrollIntervals[loketData.id] = setInterval(() => {
const maxScroll = container.scrollHeight - container.clientHeight
// Jika konten tidak cukup untuk di-scroll, skip
if (maxScroll <= 0) {
return
}
// Hitung ukuran scroll berdasarkan tinggi card
// Ambil tinggi card pertama jika ada, atau gunakan default 80px (min-height ticket-card)
let scrollAmount = 80 // Default: min-height ticket-card
const firstCard = container.querySelector('.ticket-card')
if (firstCard) {
const cardRect = firstCard.getBoundingClientRect()
const cardHeight = cardRect.height
// Tambahkan gap (10px dari .ticket-cards-list gap)
scrollAmount = cardHeight + 10
}
if (isScrollingDown) {
// Scroll ke bawah dengan ukuran card
currentScrollTop += scrollAmount
if (currentScrollTop >= maxScroll) {
// Sudah sampai bawah, kembali ke atas dan lanjut scroll ke bawah
currentScrollTop = 0
// Tetap isScrollingDown = true agar langsung scroll ke bawah lagi
}
} else {
// Scroll ke atas (tidak digunakan karena langsung reset ke atas)
currentScrollTop -= scrollAmount
if (currentScrollTop <= 0) {
currentScrollTop = 0
isScrollingDown = true
// Tunggu sebentar untuk memastikan DOM sudah render dengan sempurna
setTimeout(() => {
// Hitung tinggi konten asli dengan lebih akurat menggunakan scrollHeight
// Karena konten di-duplicate, scrollHeight total = 2x contentHeight
const totalScrollHeight = container.scrollHeight
const contentHeight = totalScrollHeight / 2
// Pastikan contentHeight valid
if (contentHeight <= 0 || contentHeight > maxScroll) {
// Fallback: hitung berdasarkan card pertama
const firstCard = container.querySelector('.ticket-card')
if (firstCard) {
const cardHeight = firstCard.offsetHeight
const gap = 10
const totalCards = loketData.queues.length
const gridCols = ticketGridColumns.value || 1
const rows = Math.ceil(totalCards / gridCols)
const calculatedHeight = (cardHeight + gap) * rows
if (calculatedHeight > 0) {
contentHeight = calculatedHeight
} else {
return // Skip jika tidak bisa dihitung
}
} else {
return // Skip jika tidak ada card
}
}
container.scrollTop = currentScrollTop
}, 2000) // Setiap 2 detik
let currentScrollTop = 0
const scrollSpeed = 0.2 // pixels per frame (smooth ticker speed - diperlambat)
const animate = () => {
currentScrollTop += scrollSpeed
// Infinite loop: gunakan modulo untuk seamless transition tanpa jitter
// Modulo memastikan transisi smooth tanpa jump
if (currentScrollTop >= contentHeight) {
// Reset dengan modulo untuk seamless loop
currentScrollTop = currentScrollTop % contentHeight
// Pastikan tidak ada nilai negatif
if (currentScrollTop < 0) {
currentScrollTop = 0
}
}
// Set scrollTop langsung (lebih smooth daripada scrollTo)
container.scrollTop = currentScrollTop
tickerAnimations[loketData.id] = {
frameId: requestAnimationFrame(animate)
}
}
// Start animation
tickerAnimations[loketData.id] = {
frameId: requestAnimationFrame(animate)
}
}, 150) // Delay untuk memastikan DOM dan layout sudah selesai render
// Start animation
tickerAnimations[loketData.id] = {
frameId: requestAnimationFrame(animate)
}
}
})
})
@@ -507,11 +567,13 @@ onMounted(() => {
onUnmounted(() => {
if (timeInterval) clearInterval(timeInterval)
// Clear all scroll intervals
Object.values(scrollIntervals).forEach(interval => {
if (interval) clearInterval(interval)
// Clear all ticker animations
Object.values(tickerAnimations).forEach(animation => {
if (animation && animation.frameId) {
cancelAnimationFrame(animation.frameId)
}
})
scrollIntervals = {}
tickerAnimations = {}
})
</script>
@@ -684,43 +746,44 @@ onUnmounted(() => {
/* Loket Header */
.loket-header-bar {
background: #FFFFFF;
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
color: var(--color-neutral-100);
padding: 16px 20px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 3px solid #FF9B1B;
box-shadow: 0 2px 8px rgba(58, 97, 201, 0.2);
border-radius: 8px 8px 0 0;
flex-shrink: 0;
}
.loket-name {
font-size: 20px;
font-weight: 700;
color: #212121;
letter-spacing: 0.5px;
text-transform: uppercase;
flex: 1;
}
.queue-count {
.loket-header-content {
display: flex;
align-items: center;
gap: 6px;
background: var(--color-primary-600);
padding: 6px 12px;
border-radius: 20px;
min-width: 50px;
justify-content: center;
}
.queue-count .v-icon {
color: white !important;
}
.count-number {
font-size: 16px;
font-weight: 700;
color: #FFFFFF;
font-size: 16px;
gap: 8px;
width: 100%;
}
.loket-name {
flex: 1;
color: var(--color-neutral-100);
font-weight: 700;
font-size: 24px;
letter-spacing: 0.5px;
text-transform: uppercase;
line-height: 1.2;
}
.loket-header-chip {
color: var(--color-neutral-100) !important;
font-weight: 700;
font-size: 16px;
height: 36px !important;
padding: 0 16px !important;
min-width: auto !important;
}
.loket-header-chip .v-icon {
margin-right: 6px;
}
/* Queue Display - Vertical Scrollable */
@@ -732,22 +795,38 @@ onUnmounted(() => {
overflow-x: hidden;
background: #FFFFFF;
min-height: 0;
scroll-behavior: smooth;
box-sizing: border-box;
position: relative;
/* Smooth scrolling untuk menghindari jitter */
scroll-behavior: auto;
/* Hardware acceleration */
transform: translateZ(0);
-webkit-overflow-scrolling: touch;
}
.queue-display.has-scroll {
max-height: calc(100vh - 400px);
overflow-y: hidden;
}
/* Hide scrollbar */
.queue-display {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
/* Ticker scroll effect */
.queue-display.ticker-scroll {
overflow-y: hidden;
}
.queue-display::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
.ticket-cards-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.ticker-container {
will-change: transform;
transform: translateZ(0); /* Hardware acceleration */
display: grid;
gap: 10px;
width: 100%;
padding-bottom: 20px;
}
.ticket-cards-list {
@@ -759,37 +838,47 @@ onUnmounted(() => {
}
.ticket-card {
background: #F5F7FA ;
border: 1px solid #E0E0E0;
background: var(--color-neutral-400, #E1E5EA);
border: 1px solid var(--color-neutral-500, #CDD4DC);
border-radius: 8px;
padding: 12px 16px;
padding: 16px 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
min-height: 80px;
position: relative; /* Untuk absolute positioning fast track icon */
gap: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
min-height: 90px;
position: relative;
cursor: default;
}
.ticket-card-fast-track {
background: rgba(255, 132, 65, 0.4) !important; /* #FF8441 dengan 40% opacity (66 hex = 102/255) */
background: var(--color-secondary-50, rgba(255, 132, 65, 0.15)) !important;
border-color: var(--color-secondary-300, rgba(255, 132, 65, 0.4)) !important;
border-width: 2px;
}
.ticket-card:hover {
box-shadow: 0 2px 6px rgba(25, 118, 210, 0.2);
border-color: var(--color-primary-600);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(58, 97, 201, 0.15);
border-color: var(--color-primary-500, #567EE7);
transform: translateY(-2px);
background: var(--color-neutral-300, #F5F7FA);
}
.ticket-card-fast-track:hover {
background: var(--color-secondary-100, rgba(255, 132, 65, 0.2)) !important;
border-color: var(--color-secondary-500, #FF8441) !important;
}
.ticket-number {
font-size: 28px;
font-size: 32px;
font-weight: 800;
color: #3556AE;
letter-spacing: 1px;
line-height: 1.2;
color: var(--color-primary-600, #3A61C9);
letter-spacing: 1.5px;
line-height: 1.1;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.ticket-klinik {
@@ -802,8 +891,17 @@ onUnmounted(() => {
.ticket-fast-track {
position: absolute;
top: 8px;
right: 8px;
top: 6px;
right: 6px;
background: var(--color-danger-600, #DE473B);
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(222, 71, 59, 0.4);
z-index: 1;
width: 32px;
height: 32px;
border-radius: 50%;
@@ -838,21 +936,35 @@ onUnmounted(() => {
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
padding: 80px 20px;
text-align: center;
min-height: 300px;
color: var(--color-neutral-600, #89939E);
}
.empty-queue .v-icon {
color: #BDBDBD !important;
margin-bottom: 12px;
.empty-icon {
margin-bottom: 20px;
opacity: 0.6;
transition: all 0.3s ease;
}
.empty-queue:hover .empty-icon {
opacity: 0.8;
transform: scale(1.05);
}
.empty-text {
font-size: 20px;
font-weight: 600;
color: var(--color-neutral-700, #717171);
margin-bottom: 8px;
}
.empty-subtext {
font-size: 14px;
color: #89939E;
margin-top: 8px;
font-weight: 500;
font-weight: 400;
color: var(--color-neutral-600, #89939E);
opacity: 0.8;
}
/* ========== FOOTER STATS ========== */
+8 -24
View File
@@ -101,8 +101,8 @@
:key="ticket.title"
cols="12"
sm="6"
md="4"
lg="4"
md="3"
lg="3"
>
<MonitorPasienTicketCard
:title="ticket.title"
@@ -273,23 +273,7 @@ onMounted(fetchPatientData);
</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;
$success-700: #1B6E53;
$success-600: #009262;
$success-400: #32C997;
$success-300: #84DFC1;
$success-200: #F1FBF8;
$danger-600: #E02B1D;
@import '@/assets/scss/_colors.scss';
// Font Family & Weights
$font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
@@ -306,9 +290,9 @@ $font-weight-semibold: 600;
// PAGE HEADER
// ============================================
.page-header {
background: linear-gradient(135deg, $success-600 0%, $success-700 100%);
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
border-radius: 16px 16px 0 0;
box-shadow: 0 4px 16px rgba(0, 146, 98, 0.2);
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
}
.header-content {
@@ -357,7 +341,7 @@ $font-weight-semibold: 600;
}
.stat-chip {
color: $success-600 !important;
color: $primary-600 !important;
font-weight: $font-weight-semibold;
font-size: 14px;
line-height: 20px;
@@ -369,7 +353,7 @@ $font-weight-semibold: 600;
letter-spacing: 0.5px;
font-size: 16px;
line-height: 24px;
color: $success-600 !important;
color: $primary-600 !important;
}
// ============================================
@@ -392,7 +376,7 @@ $font-weight-semibold: 600;
font-size: 24px;
line-height: 32px;
font-weight: $font-weight-semibold;
color: $success-600;
color: $primary-600;
margin-bottom: 16px;
}
+20 -5
View File
@@ -38,13 +38,27 @@ export default defineEventHandler(async (event) => {
// Construct the Keycloak logout URL with proper parameters
// IMPORTANT: The post_logout_redirect_uri must be registered in Keycloak client settings
const logoutUrl = new URL(`${config.keycloakIssuer}/protocol/openid-connect/logout`);
// Use custom logout URI from env if provided, otherwise use default
const logoutPath = config.keycloakLogoutUri || `${config.keycloakIssuer}/protocol/openid-connect/logout`;
const logoutUrl = new URL(logoutPath);
// Debug: Log the authUrl being used
// Debug: Log the authUrl and logout URI being used
console.log('🔧 Using authUrl from config:', config.public.authUrl);
if (config.keycloakLogoutUri) {
console.log('🔧 Using custom logout URI from env:', config.keycloakLogoutUri);
} else {
console.log('🔧 Using default logout URI:', logoutPath);
}
// Build the redirect URI - must match what's configured in Keycloak
const postLogoutRedirectUri = `${config.public.authUrl}/LoginPage?logout=success`;
// Use custom post-logout redirect URI from env if provided, otherwise use default
const postLogoutRedirectUri = config.postLogoutRedirectUri || `${config.public.authUrl}/LoginPage?logout=success`;
if (config.postLogoutRedirectUri) {
console.log('🔧 Using custom post-logout redirect URI from env:', config.postLogoutRedirectUri);
} else {
console.log('🔧 Using default post-logout redirect URI:', postLogoutRedirectUri);
}
// Add required parameters for proper Keycloak logout
logoutUrl.searchParams.set('client_id', config.keycloakClientId);
@@ -77,8 +91,9 @@ export default defineEventHandler(async (event) => {
// Even if there's an error, try to provide a basic logout URL
const config = useRuntimeConfig();
const postLogoutRedirectUri = `${config.public.authUrl}/LoginPage?logout=success`;
const fallbackLogoutUrl = new URL(`${config.keycloakIssuer}/protocol/openid-connect/logout`);
const postLogoutRedirectUri = config.postLogoutRedirectUri || `${config.public.authUrl}/LoginPage?logout=success`;
const logoutPath = config.keycloakLogoutUri || `${config.keycloakIssuer}/protocol/openid-connect/logout`;
const fallbackLogoutUrl = new URL(logoutPath);
fallbackLogoutUrl.searchParams.set('client_id', config.keycloakClientId);
fallbackLogoutUrl.searchParams.set('post_logout_redirect_uri', postLogoutRedirectUri);