add new data 2025 05 12

This commit is contained in:
Fanrouver
2025-12-05 10:33:40 +07:00
parent 642be0f9df
commit 10ef054de6
6 changed files with 75 additions and 48 deletions

No files matched your search

+1 -1
View File
@@ -110,7 +110,7 @@
}
.hospital-logo {
height: 232px;
height: 332px;
width: auto;
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.1));
}
+34 -12
View File
@@ -261,7 +261,7 @@ const mockQueueData = ref([
{ date: '2025-10-18', registrants: 435, attendees: 380 },
]);
// --- REALTIME CHART LOGIC - FIXED ---
// --- OPTIMIZED REALTIME CHART LOGIC ---
const realtimeChart = ref(null);
const maxDataPoints = 10;
let realtimeInterval = null;
@@ -289,7 +289,7 @@ const realtimeLineData = ref({
const lineOptions = ref({
responsive: true,
maintainAspectRatio: false,
animation: { duration: 300 },
animation: { duration: 0 }, // Disable animation for better performance
plugins: {
legend: { display: true },
title: { display: false }
@@ -309,24 +309,46 @@ const lineOptions = ref({
});
const updateRealtimeData = () => {
const data = realtimeLineData.value.datasets[0].data;
const labels = realtimeLineData.value.labels;
if (!realtimeChart.value) return; // Guard clause
// Remove first data point
data.shift();
labels.shift();
const chart = realtimeChart.value.chart;
if (!chart) return;
// Direct chart data manipulation (more efficient)
chart.data.labels.shift();
chart.data.datasets[0].data.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);
chart.data.labels.push(newTimeLabel);
chart.data.datasets[0].data.push(newDataPoint);
console.log('Realtime chart updated:', newTimeLabel, newDataPoint);
chart.update('none'); // Update without animation
};
// --- END REALTIME CHART LOGIC ---
// Start/stop interval management
const startRealtimeUpdates = () => {
if (realtimeInterval) return; // Prevent multiple intervals
realtimeInterval = setInterval(updateRealtimeData, 3000); // Update every 3 seconds
};
const stopRealtimeUpdates = () => {
if (realtimeInterval) {
clearInterval(realtimeInterval);
realtimeInterval = null;
}
};
// Lifecycle hooks
onMounted(() => {
startRealtimeUpdates();
});
onUnmounted(() => {
stopRealtimeUpdates();
});
// --- END OPTIMIZED REALTIME CHART LOGIC ---
onMounted(async () => {
try {
const sessionUser = await checkAuth()
+16 -12
View File
@@ -34,7 +34,7 @@
<div class="d-flex flex-column align-center text-center mb-6">
<span class="text-h5 font-weight-bold app-title-dark">Antrean RSSA</span>
<img
src="/AntreanLogobg.png"
src="/AntreanLogobg.png"
alt="Antrean Logo"
class="mt-3 hospital-logo"
/>
@@ -48,9 +48,7 @@
{{ successMessage }}
</v-alert>
<div class="text-center mb-6">
<p class="sso-text-dark">Login menggunakan Single Sign-On</p>
</div>
<v-btn
@click="handleLogin"
@@ -69,8 +67,14 @@
</v-btn>
<v-divider class="my-6 custom-divider-dark"></v-divider>
<div class="text-center mb-6">
<p class="sso-text-dark">Login menggunakan Single Sign-On</p>
</div>
<div class="text-center">
<!-- <div class="text-center">
<v-btn
@click="showRegistrationDialog = true"
class="register-btn-dark"
@@ -97,11 +101,11 @@
Hubungi Administrator
</v-btn>
</div>
</div>
</div> -->
<div class="text-center mt-4">
<!-- <div class="text-center mt-4">
<span class="help-link-dark">Masalah dengan kata sandi Anda?</span>
</div>
</div> -->
</div>
</v-col>
</v-row>
@@ -109,7 +113,7 @@
</v-col>
</v-row>
<v-dialog v-model="showRegistrationDialog" max-width="500">
<!-- <v-dialog v-model="showRegistrationDialog" max-width="500">
<v-card class="white-dialog rounded-xl">
<v-card-title class="text-h5 text-center pa-6 bg-grey-lighten-4">
<v-icon left color="#FF9B1B">mdi-account-plus</v-icon> Pendaftaran Akun Baru
@@ -169,9 +173,9 @@
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-dialog> -->
<v-dialog v-model="showAdminContact" max-width="500">
<!-- <v-dialog v-model="showAdminContact" max-width="500">
<v-card class="white-dialog rounded-xl">
<v-card-title class="text-h5 text-center pa-6 bg-grey-lighten-4">
<v-icon left color="#FF9B1B">mdi-account-tie</v-icon> Kontak Administrator
@@ -265,7 +269,7 @@
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-dialog> -->
</v-container>
</template>
+3 -3
View File
@@ -152,10 +152,10 @@
<div v-else>
<v-card class="d-flex flex-wrap 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-account-group-outline" class="mr-2" size="40"></v-icon>
<v-icon icon="mdi-account-group-outline" class="mr-2" size="40"></v-icon>
<span>User Login Management</span>
</v-card-title>
<v-btn
<!-- <v-btn
color="success"
prepend-icon="mdi-plus"
rounded
@@ -164,7 +164,7 @@
:disabled="isPending"
>
Tambah User
</v-btn>
</v-btn> -->
</v-card>
<v-card class="pa-4 rounded-lg elevation-2">
+5 -5
View File
@@ -108,7 +108,7 @@
<v-btn
v-if="patient.status === 'Belum Terverifikasi'"
color="#FFA532"
size="x-large"
size="x-medium"
@click="openDaftarModal(patient)"
prepend-icon="mdi-qrcode-scan"
variant="flat"
@@ -182,7 +182,7 @@
<div v-else class="text-center mt-6 pa-4 pa-sm-7 qr-container rounded-xl">
<div class="pulse-icon mb-3 mb-sm-4">
<v-icon color="#4A5F7A" :size="display.xs.value ? 48 : 64">mdi-cellphone-check</v-icon>
<v-icon color="#4A5F7A" :size="display.xs.value ? 32 : 48">mdi-cellphone-check</v-icon>
</div>
<h3 class="text-h5 text-sm-h4 font-weight-black mb-2 mb-sm-3" style="color: #4A5F7A;">Pindai QR Code</h3>
<p class="text-body-2 text-sm-body-1 secondary-text mb-4 mb-sm-6">Arahkan kamera smartphone ke QR code</p>
@@ -310,9 +310,9 @@ const qrCodeData = computed(() => {
// Adaptive QR code size based on screen size
const qrSize = computed(() => {
if (display.xs.value) return 200; // Extra small screens (mobile)
if (display.sm.value) return 240; // Small screens (large phones)
return 280; // Medium and larger screens
if (display.xs.value) return 120; // Extra small screens (mobile)
if (display.sm.value) return 200; // Small screens (large phones)
return 220; // Medium and larger screens
});
// --- LOGIC MODAL ---
+16 -15
View File
@@ -1,4 +1,12 @@
// server/api/auth/keycloak-callback.ts - FIX APPLIED
// server/api/auth/keycloak-callback.ts - EXTENDED SESSION FIX
// Add this at the top of the file (after imports)
const SESSION_DURATION = 24 * 60 * 60; // 7 days in seconds (customize as needed)
// Or use one of these alternatives:
// const SESSION_DURATION = 24 * 60 * 60; // 1 day
// const SESSION_DURATION = 30 * 24 * 60 * 60; // 30 days
// const SESSION_DURATION = 12 * 60 * 60; // 12 hours
export default defineEventHandler(async (event) => {
try {
const config = useRuntimeConfig();
@@ -28,8 +36,8 @@ export default defineEventHandler(async (event) => {
if (!state || state !== storedState) {
console.error('❌ Invalid state parameter - possible CSRF attack');
console.error('   Expected:', storedState);
console.error('   Received:', state);
console.error(' Expected:', storedState);
console.error(' Received:', state);
const errorMsg = encodeURIComponent('Security validation failed. Please try logging in again.');
return sendRedirect(event, `/LoginPage?error=${errorMsg}`);
@@ -46,7 +54,6 @@ export default defineEventHandler(async (event) => {
const tokenUrl = `${config.keycloakIssuer}/protocol/openid-connect/token`;
const redirectUri = `${config.public.authUrl}/api/auth/keycloak-callback`;
// ... (Token exchange logic remains the same) ...
const tokenPayload = new URLSearchParams({
grant_type: 'authorization_code',
client_id: config.keycloakClientId,
@@ -72,7 +79,6 @@ export default defineEventHandler(async (event) => {
const tokens = await tokenResponse.json();
// ... (Token decoding and sessionData creation remains the same) ...
let idTokenPayload;
try {
idTokenPayload = JSON.parse(
@@ -96,33 +102,28 @@ export default defineEventHandler(async (event) => {
accessToken: tokens.access_token,
idToken: tokens.id_token,
refreshToken: tokens.refresh_token,
expiresAt: Date.now() + (tokens.expires_in * 1000),
// CHANGED: Use custom session duration instead of Keycloak's token expiry
expiresAt: Date.now() + (SESSION_DURATION * 1000),
createdAt: Date.now(),
};
// ----------------------------------------------------
// 👇 CRITICAL FIX FOR DEPLOYED HTTPS ENVIRONMENTS 👇
// ----------------------------------------------------
// Check if the request was originally HTTPS (via proxy)
const isSecure = process.env.NODE_ENV === 'production' ||
event.node.req.headers['x-forwarded-proto'] === 'https';
console.log('🔗 Setting session cookie with secure flag:', isSecure);
console.log('⏱️ Session duration:', SESSION_DURATION, 'seconds');
setCookie(event, 'user_session', JSON.stringify(sessionData), {
httpOnly: true,
// CRITICAL: Must be TRUE when operating over HTTPS (deployed)
secure: isSecure,
// Ensures cookie is sent on cross-site redirects (Keycloak -> Your App)
sameSite: 'lax',
maxAge: tokens.expires_in,
// CHANGED: Use custom session duration (7 days default)
maxAge: SESSION_DURATION,
path: '/',
});
console.log('✅ Session cookie created successfully');
// Note: The following line will still log false because the cookie
// is in the response header, not the request header yet. This is expected.
const testCookie = getCookie(event, 'user_session');
console.log('🧪 Cookie test - can read back in this handler (Expected False):', !!testCookie);