Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Dayjs } from 'dayjs';
import { Box } from '@mui/material';
import { useTranslations } from 'next-intl';
import { BLOCK_COLORS, AgendaBlockVisibility, AgendaBlock } from '../calendar-types';
import { calculateBlockPosition } from '../calendar-utils';
import { calculateBlockPosition, BlockOverlapInfo } from '../calendar-utils';
import { useCalendar } from '../calendar-context';
import { EditAgendaDialog } from './dialog/edit-agenda-dialog';
import { ResizeHandles } from './resize-handles';
Expand All @@ -19,6 +19,7 @@ interface AgendaBlockProps {
draggedPosition: number;
draggedDuration?: number; // For bottom-edge resize preview
draggedStartTime?: Dayjs; // For top-edge resize preview
overlapInfo?: BlockOverlapInfo; // Layout info for overlapping blocks
onDragStartBody: (block: AgendaBlock, startY: number) => void;
onDragStartTopEdge: (block: AgendaBlock, startY: number) => void;
onDragStartBottomEdge: (block: AgendaBlock, startY: number) => void;
Expand All @@ -32,6 +33,7 @@ export const AgendaBlockComponent: React.FC<AgendaBlockProps> = ({
draggedPosition,
draggedDuration,
draggedStartTime,
overlapInfo,
onDragStartBody,
onDragStartTopEdge,
onDragStartBottomEdge
Expand Down Expand Up @@ -117,13 +119,29 @@ export const AgendaBlockComponent: React.FC<AgendaBlockProps> = ({
setEditingBlockId(null);
};

const horizontalPosition = useMemo(() => {
if (!overlapInfo || overlapInfo.totalColumns === 1) {
return { left: 8, right: 8 };
}

const columnWidth = 100 / overlapInfo.totalColumns;
const leftPercent = columnWidth * overlapInfo.column;
const rightPercent = 100 - (leftPercent + columnWidth);
const gap = 2;

return {
left: `calc(${leftPercent}% + ${gap}px)`,
right: `calc(${rightPercent}% + ${gap}px)`
};
}, [overlapInfo]);

return (
<Box
sx={{
position: 'absolute',
top: finalTop,
left: 8,
right: 8,
left: horizontalPosition.left,
right: horizontalPosition.right,
height: finalHeight,
backgroundColor: BLOCK_COLORS[block.type],
border: '1px solid rgba(0,0,0,0.1)',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
'use client';

import React, { useState, useRef, useCallback, useEffect } from 'react';
import React, { useState, useRef, useCallback, useEffect, useMemo } from 'react';
import { Dayjs } from 'dayjs';
import { useTranslations } from 'next-intl';
import { Box, Typography, Stack } from '@mui/material';
import { AgendaBlock, HEADER_HEIGHT } from '../calendar-types';
import { snapToGrid } from '../drag-utils';
import { calculateOverlapLayout } from '../calendar-utils';
import { useCalendar } from '../calendar-context';
import { AgendaBlockComponent } from '../agenda-block/agenda-block';
import { AgendaDragState } from './drag-types';
Expand All @@ -26,6 +27,10 @@ export const AgendaColumn: React.FC<AgendaColumnProps> = ({ startTime, endTime }
const [dragState, setDragState] = useState<AgendaDragState | null>(null);
const columnRef = useRef<HTMLDivElement>(null);

const overlapLayout = useMemo(() => {
return calculateOverlapLayout(agendaBlocks as AgendaBlock[]);
}, [agendaBlocks]);

const { handleMouseMove, handleMouseUp } = useDragHandlers({
dragState,
startTime,
Expand Down Expand Up @@ -193,6 +198,7 @@ export const AgendaColumn: React.FC<AgendaColumnProps> = ({ startTime, endTime }
? dragState.originalStartTime
: undefined
}
overlapInfo={overlapLayout.get(block.id)}
onDragStartBody={handleDragStartBody}
onDragStartTopEdge={handleDragStartTopEdge}
onDragStartBottomEdge={handleDragStartBottomEdge}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Dayjs } from 'dayjs';
import { ScheduleBlock, ScheduleColumn, TIME_SLOT_HEIGHT } from './calendar-types';
import { ScheduleBlock, ScheduleColumn, TIME_SLOT_HEIGHT, AgendaBlock } from './calendar-types';

export function getBlockColumn(block: ScheduleBlock): ScheduleColumn {
if (block.type === 'judging-session') return 'judging';
Expand All @@ -18,3 +18,90 @@ export function calculateBlockPosition(startTime: Dayjs, block: ScheduleBlock) {
export function getDuration(date: Dayjs): number {
return date.hour() * 3600 + date.minute() * 60 + date.second();
}

export interface BlockOverlapInfo {
column: number;
totalColumns: number;
}

/**
* Checks if two blocks overlap in time
*/
function blocksOverlap(block1: AgendaBlock, block2: AgendaBlock): boolean {
const end1 = block1.startTime.add(block1.durationSeconds, 'second');
const end2 = block2.startTime.add(block2.durationSeconds, 'second');

return block1.startTime.isBefore(end2) && block2.startTime.isBefore(end1);
}

/**
* Calculate layout information for overlapping agenda blocks
* Returns a map of block ID to its column position and total columns in its group
*/
export function calculateOverlapLayout(blocks: AgendaBlock[]): Map<string, BlockOverlapInfo> {
const layout = new Map<string, BlockOverlapInfo>();

const sortedBlocks = [...blocks].sort((a, b) => a.startTime.diff(b.startTime));
const groups: AgendaBlock[][] = [];

for (const block of sortedBlocks) {
let foundGroup = false;

for (const group of groups) {
if (group.some(groupBlock => blocksOverlap(block, groupBlock))) {
group.push(block);
foundGroup = true;
break;
}
}

if (!foundGroup) {
groups.push([block]);
}
}

for (const group of groups) {
if (group.length === 1) {
layout.set(group[0].id, { column: 0, totalColumns: 1 });
} else {
const sortedGroup = [...group].sort((a, b) => {
const timeDiff = a.startTime.diff(b.startTime);
if (timeDiff !== 0) return timeDiff;
return b.durationSeconds - a.durationSeconds;
});

const columns: AgendaBlock[][] = [];

for (const block of sortedGroup) {
let assignedColumn = -1;

for (let i = 0; i < columns.length; i++) {
const columnBlocks = columns[i];
const hasOverlap = columnBlocks.some(colBlock => blocksOverlap(block, colBlock));

if (!hasOverlap) {
assignedColumn = i;
columnBlocks.push(block);
break;
}
}

if (assignedColumn === -1) {
assignedColumn = columns.length;
columns.push([block]);
}

layout.set(block.id, { column: assignedColumn, totalColumns: columns.length });
}

for (const block of group) {
const info = layout.get(block.id);
if (info) {
info.totalColumns = columns.length;
}
}
}
}

return layout;
}