Skip to content

Commit 0be0499

Browse files
authored
fix(conferia): agenda ordered by time (#361)
1 parent b128ab0 commit 0be0499

File tree

2 files changed

+189
-100
lines changed

2 files changed

+189
-100
lines changed

packages/conferia/frontend/src/views/calendar/TabAgenda.vue

Lines changed: 126 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,20 @@
6666
<div class="px-4 py-6 pb-20">
6767
<!-- Session List -->
6868
<div
69-
v-if="state.selectedDay && groupedSessionsByType && Object.keys(groupedSessionsByType).length > 0"
69+
v-if="state.selectedDay && groupedSessionsByTime && Object.keys(groupedSessionsByTime).length > 0"
7070
class="space-y-6">
7171
<div
72-
v-for="(group, sessionType) in groupedSessionsByType"
73-
:key="sessionType"
72+
v-for="(group, timeSlot) in groupedSessionsByTime"
73+
:key="timeSlot"
7474
class="space-y-3">
75-
<!-- Session Type Header -->
75+
<!-- Time Slot Header -->
7676
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-xl p-3 border border-blue-100 dark:border-blue-800">
7777
<h2 class="font-bold text-lg text-blue-900 dark:text-blue-100">
78-
{{ formatSessionType(sessionType) }}
78+
{{ formatTimeSlot(timeSlot) }}
7979
</h2>
8080
</div>
8181

82-
<!-- Sessions of this type -->
82+
<!-- Sessions of this time -->
8383
<div class="space-y-3">
8484
<div
8585
v-for="session in group"
@@ -97,7 +97,7 @@
9797
{{ formatSessionTime(session) }}
9898
</p>
9999
<p
100-
v-if="session.session_host"
100+
v-if="session.session_host && session.session_host.trim() && session.session_host !== 'TBA'"
101101
class="text-gray-600 dark:text-gray-300 text-sm mb-1">
102102
Host: {{ session.session_host }}
103103
</p>
@@ -325,19 +325,71 @@ async function toggleLike(session: Session): Promise<void> {
325325
}
326326
}
327327
328+
/**
329+
* Fix UTF-8 encoding issues for text that may contain special characters
330+
*/
331+
function fixTextEncoding(text: string | undefined): string | undefined {
332+
if (!text) return text;
333+
334+
try {
335+
// Common double-encoded UTF-8 patterns for Polish and other European characters
336+
let fixed = text
337+
// Polish characters
338+
.replace(/Å›/g, 'ś')
339+
.replace(/Ä…/g, 'ą')
340+
.replace(/Å„/g, 'ń')
341+
.replace(/Å‚/g, 'ł')
342+
.replace(/ż/g, 'ż')
343+
.replace(/ů/g, 'ź')
344+
.replace(/Ä™/g, 'ę')
345+
.replace(/ć/g, 'ć')
346+
.replace(/ó/g, 'ó')
347+
348+
// German/French characters
349+
.replace(/á/g, 'á')
350+
.replace(/é/g, 'é')
351+
.replace(/í/g, 'í')
352+
.replace(/ú/g, 'ú')
353+
.replace(/ñ/g, 'ñ')
354+
.replace(/ü/g, 'ü')
355+
.replace(/ä/g, 'ä')
356+
.replace(/ö/g, 'ö')
357+
.replace(/ß/g, 'ß')
358+
359+
// More double-encoded patterns
360+
.replace(/’/g, "'")
361+
.replace(/“/g, '"')
362+
.replace(/â€/g, '"')
363+
.replace(/â€"/g, '')
364+
.replace(/â€"/g, '');
365+
366+
// Handle question mark replacements for specific known cases
367+
// This is specifically for "Mateusz ?la?y?ski" -> "Mateusz Ślażyński"
368+
if (fixed.includes('?la?y?ski')) {
369+
fixed = fixed.replace('?la?y?ski', 'ślażyński');
370+
}
371+
if (fixed.includes('Mateusz ?')) {
372+
fixed = fixed.replace('Mateusz ?la?y?ski', 'Mateusz Ślażyński');
373+
}
374+
375+
return fixed;
376+
} catch (error) {
377+
console.warn('Error fixing text encoding:', error);
378+
return text;
379+
}
380+
}
381+
328382
/**
329383
* Process session data and add timezone adjustment and like status
330384
*/
331385
function processSessions(sessionsData: Record<string, unknown>[]): Session[] {
332386
return sessionsData.map((session: Record<string, unknown>) => {
333387
const sessionIdAsString = session.id?.toString();
334-
const isLikedCheck = state.likedSessionIds.has(sessionIdAsString ?? '');
388+
const isLikedCheck = state.likedSessionIds.has(parseInt(sessionIdAsString ?? '0'));
335389
336-
// Add 2 hours to fix timezone misalignment
390+
// Use original times without timezone adjustment
337391
const startTime = new Date(session.startTime as string);
338392
const endTime = new Date(session.endTime as string);
339-
startTime.setHours(startTime.getHours() + 2);
340-
endTime.setHours(endTime.getHours() + 2);
341393
342394
// Format times properly (YYYY-MM-DD HH:MM:SS)
343395
const formatDateTime = (date: Date) => {
@@ -352,19 +404,19 @@ function processSessions(sessionsData: Record<string, unknown>[]): Session[] {
352404
353405
return {
354406
id: session.id as number,
355-
title: (session.name ?? session.session_name ?? 'Untitled Session') as string,
356-
session_name: (session.name ?? session.session_name) as string | undefined,
357-
session_host: (session.host ?? session.session_host) as string | undefined,
358-
session_location: (session.location ?? session.session_location) as string | undefined,
407+
title: fixTextEncoding((session.name ?? session.session_name ?? 'Untitled Session') as string) as string,
408+
session_name: fixTextEncoding((session.name ?? session.session_name) as string | undefined),
409+
session_host: fixTextEncoding((session.host ?? session.session_host) as string | undefined),
410+
session_location: fixTextEncoding((session.location ?? session.session_location) as string | undefined),
359411
start_time: formatDateTime(startTime),
360412
end_time: formatDateTime(endTime),
361413
startTime: formatDateTime(startTime),
362414
endTime: formatDateTime(endTime),
363415
type: session.type as string,
364416
isLiked: isLikedCheck,
365417
likes: (session.likes ?? 0) as number,
366-
abstract: session.abstract as string,
367-
authors: session.authors as string
418+
abstract: fixTextEncoding(session.abstract as string),
419+
authors: fixTextEncoding(session.authors as string)
368420
};
369421
});
370422
}
@@ -407,7 +459,16 @@ function selectDay(value: string): void {
407459
* Navigate to different agenda type (personal/general) with query parameters
408460
*/
409461
function navigateToAgendaType(type: string): void {
410-
const query: Record<string, string> = { ...route.query }; // Get current query parameters
462+
const query: Record<string, string> = {};
463+
464+
// Copy existing query parameters
465+
for (const [key, value] of Object.entries(route.query)) {
466+
if (typeof value === 'string') {
467+
query[key] = value;
468+
} else if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'string') {
469+
query[key] = value[0];
470+
}
471+
}
411472
412473
// Add or update the `type` parameter based on the selected segment
413474
if (type === 'personal') {
@@ -456,76 +517,32 @@ const filteredSessions = computed(() => {
456517
.sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime());
457518
});
458519
459-
const groupedSessionsByType = computed(() => {
460-
const groups: Record<string, Session[]> = {};
461-
462-
// Define the importance order for session types
463-
const typeImportanceOrder = [
464-
'KEYNOTE',
465-
'FOOD',
466-
'COFFEE',
467-
'PRACTICAL',
468-
'QnA',
469-
'DOCTORALCONSORTIUM',
470-
'MAIN',
471-
'BPMFORUM',
472-
'EDUCATORSFORUM',
473-
'PROCESSTECHNOLOGYFORUM',
474-
'INDUSTRYFORUM',
475-
'RESPONSIBLEBPMFORUM',
476-
'JOURNALFIRST',
477-
'PANEL',
478-
'TUTORIAL',
479-
'WORKSHOP',
480-
'DEMO',
481-
'OTHER'
482-
];
483-
484-
for (const session of filteredSessions.value) {
485-
const sessionType = session.type ?? 'OTHER';
486-
let groupKey = sessionType;
487-
488-
// For workshops, create separate groups based on workshop name
489-
if (sessionType === 'WORKSHOP' && session.session_name) {
490-
const workshopName = extractWorkshopName(session.session_name);
491-
groupKey = `WORKSHOP_${workshopName}`;
520+
const groupedSessionsByTime = computed(() => {
521+
// Sort all sessions by start time
522+
const sortedSessions = [...filteredSessions.value].sort((a, b) =>
523+
new Date(a.start_time).getTime() - new Date(b.start_time).getTime()
524+
);
525+
526+
// Group sessions by time slots (start time)
527+
const timeGroups: Record<string, Session[]> = {};
528+
529+
for (const session of sortedSessions) {
530+
const timeKey = session.start_time; // Use full start time as key
531+
532+
if (!timeGroups[timeKey]) {
533+
timeGroups[timeKey] = [];
492534
}
493-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
494-
groups[groupKey] = groups[groupKey] || [];
495-
groups[groupKey].push(session);
535+
timeGroups[timeKey].push(session);
496536
}
497537
498-
// Sort sessions within each type by start time
499-
Object.keys(groups).forEach((type) => {
500-
groups[type].sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime());
501-
});
538+
// Sort time groups chronologically
539+
const sortedTimeKeys = Object.keys(timeGroups).sort((a, b) =>
540+
new Date(a).getTime() - new Date(b).getTime()
541+
);
502542
503-
// Convert to ordered object based on type importance
504543
const orderedGroups: Record<string, Session[]> = {};
505-
506-
// First, add groups in importance order
507-
for (const type of typeImportanceOrder) {
508-
if (type in groups) {
509-
orderedGroups[type] = groups[type];
510-
}
511-
}
512-
513-
// Then add workshop groups (sorted alphabetically)
514-
const workshopGroups = Object.keys(groups)
515-
.filter(key => key.startsWith('WORKSHOP_'))
516-
.sort();
517-
518-
for (const workshopType of workshopGroups) {
519-
if (workshopType in groups) {
520-
orderedGroups[workshopType] = groups[workshopType];
521-
}
522-
}
523-
524-
// Finally, add any remaining groups not in the importance order
525-
for (const type in groups) {
526-
if (!(type in orderedGroups)) {
527-
orderedGroups[type] = groups[type];
528-
}
544+
for (const timeKey of sortedTimeKeys) {
545+
orderedGroups[timeKey] = timeGroups[timeKey];
529546
}
530547
531548
return orderedGroups;
@@ -558,15 +575,28 @@ function extractWorkshopName(sessionTitle: string): string {
558575
}
559576
560577
/**
561-
* Format session type for display
578+
* Format time slot for display as header
562579
*/
563-
function formatSessionType(type: string): string {
564-
// Handle workshop groups
565-
if (type.startsWith('WORKSHOP_')) {
566-
const workshopName = type.replace('WORKSHOP_', '');
567-
return workshopName;
580+
function formatTimeSlot(timeSlot: string): string {
581+
try {
582+
const timePart = timeSlot.split(' ')[1];
583+
if (!timePart) {
584+
return 'Time TBA';
585+
}
586+
587+
// Extract just hours:minutes from HH:MM:SS format
588+
const time = timePart.substring(0, 5);
589+
return time;
590+
} catch (error) {
591+
console.error('Error formatting time slot:', error, timeSlot);
592+
return 'Time TBA';
568593
}
594+
}
569595
596+
/**
597+
* Format session type for display (kept for potential future use)
598+
*/
599+
function formatSessionType(type: string): string {
570600
const typeMap: Record<string, string> = {
571601
KEYNOTE: 'Keynote',
572602
FOOD: 'Lunch',
@@ -587,6 +617,13 @@ function formatSessionType(type: string): string {
587617
JOURNALFIRST: 'Journal First Track',
588618
OTHER: 'Other Sessions'
589619
};
620+
621+
// Handle workshop groups
622+
if (type.startsWith('WORKSHOP_')) {
623+
const workshopName = type.replace('WORKSHOP_', '');
624+
return workshopName;
625+
}
626+
590627
return typeMap[type] || type;
591628
}
592629

0 commit comments

Comments
 (0)