Mega push vol2
This commit is contained in:
@@ -1,28 +1,47 @@
|
||||
<script lang="ts">
|
||||
import type { CalendarEvent } from '$lib/supabase/types';
|
||||
import { getMonthDays, isSameDay } from '$lib/api/calendar';
|
||||
import type { CalendarEvent } from "$lib/supabase/types";
|
||||
import { getMonthDays, isSameDay } from "$lib/api/calendar";
|
||||
|
||||
type ViewType = "month" | "week" | "day";
|
||||
|
||||
interface Props {
|
||||
events: CalendarEvent[];
|
||||
onDateClick?: (date: Date) => void;
|
||||
onEventClick?: (event: CalendarEvent) => void;
|
||||
initialView?: ViewType;
|
||||
}
|
||||
|
||||
let { events, onDateClick, onEventClick }: Props = $props();
|
||||
let {
|
||||
events,
|
||||
onDateClick,
|
||||
onEventClick,
|
||||
initialView = "month",
|
||||
}: Props = $props();
|
||||
|
||||
let currentDate = $state(new Date());
|
||||
let currentView = $state<ViewType>(initialView);
|
||||
const today = new Date();
|
||||
|
||||
const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
|
||||
const days = $derived(getMonthDays(currentDate.getFullYear(), currentDate.getMonth()));
|
||||
const days = $derived(
|
||||
getMonthDays(currentDate.getFullYear(), currentDate.getMonth()),
|
||||
);
|
||||
|
||||
function prevMonth() {
|
||||
currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1);
|
||||
currentDate = new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth() - 1,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
function nextMonth() {
|
||||
currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1);
|
||||
currentDate = new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth() + 1,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
function goToToday() {
|
||||
@@ -41,14 +60,109 @@
|
||||
}
|
||||
|
||||
const monthYear = $derived(
|
||||
currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
|
||||
currentDate.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}),
|
||||
);
|
||||
|
||||
// Get week days for week view
|
||||
function getWeekDays(date: Date): Date[] {
|
||||
const startOfWeek = new Date(date);
|
||||
startOfWeek.setDate(date.getDate() - date.getDay());
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
const d = new Date(startOfWeek);
|
||||
d.setDate(startOfWeek.getDate() + i);
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
const weekDates = $derived(getWeekDays(currentDate));
|
||||
|
||||
// Navigation functions for different views
|
||||
function prev() {
|
||||
if (currentView === "month") {
|
||||
currentDate = new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth() - 1,
|
||||
1,
|
||||
);
|
||||
} else if (currentView === "week") {
|
||||
currentDate = new Date(
|
||||
currentDate.getTime() - 7 * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
} else {
|
||||
currentDate = new Date(currentDate.getTime() - 24 * 60 * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (currentView === "month") {
|
||||
currentDate = new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth() + 1,
|
||||
1,
|
||||
);
|
||||
} else if (currentView === "week") {
|
||||
currentDate = new Date(
|
||||
currentDate.getTime() + 7 * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
} else {
|
||||
currentDate = new Date(currentDate.getTime() + 24 * 60 * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
const headerTitle = $derived(() => {
|
||||
if (currentView === "day") {
|
||||
return currentDate.toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
} else if (currentView === "week") {
|
||||
const start = weekDates[0];
|
||||
const end = weekDates[6];
|
||||
return `${start.toLocaleDateString("en-US", { month: "short", day: "numeric" })} - ${end.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
|
||||
}
|
||||
return monthYear;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="bg-surface rounded-xl p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-xl font-semibold text-light">{monthYear}</h2>
|
||||
<h2 class="text-xl font-semibold text-light">{headerTitle()}</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- View Switcher -->
|
||||
<div class="flex bg-dark rounded-lg p-0.5">
|
||||
<button
|
||||
class="px-3 py-1 text-sm rounded-md transition-colors {currentView ===
|
||||
'day'
|
||||
? 'bg-primary text-white'
|
||||
: 'text-light/60 hover:text-light'}"
|
||||
onclick={() => (currentView = "day")}
|
||||
>
|
||||
Day
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 text-sm rounded-md transition-colors {currentView ===
|
||||
'week'
|
||||
? 'bg-primary text-white'
|
||||
: 'text-light/60 hover:text-light'}"
|
||||
onclick={() => (currentView = "week")}
|
||||
>
|
||||
Week
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 text-sm rounded-md transition-colors {currentView ===
|
||||
'month'
|
||||
? 'bg-primary text-white'
|
||||
: 'text-light/60 hover:text-light'}"
|
||||
onclick={() => (currentView = "month")}
|
||||
>
|
||||
Month
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="px-3 py-1.5 text-sm text-light/60 hover:text-light hover:bg-light/10 rounded-lg transition-colors"
|
||||
onclick={goToToday}
|
||||
@@ -57,68 +171,183 @@
|
||||
</button>
|
||||
<button
|
||||
class="p-2 text-light/60 hover:text-light hover:bg-light/10 rounded-lg transition-colors"
|
||||
onclick={prevMonth}
|
||||
aria-label="Previous month"
|
||||
onclick={prev}
|
||||
aria-label="Previous"
|
||||
>
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="m15 18-6-6 6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="p-2 text-light/60 hover:text-light hover:bg-light/10 rounded-lg transition-colors"
|
||||
onclick={nextMonth}
|
||||
aria-label="Next month"
|
||||
onclick={next}
|
||||
aria-label="Next"
|
||||
>
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="m9 18 6-6-6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 gap-px bg-light/10 rounded-lg overflow-hidden">
|
||||
{#each weekDays as day}
|
||||
<div class="bg-dark px-2 py-2 text-center text-sm font-medium text-light/50">
|
||||
{day}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#each days as day}
|
||||
{@const dayEvents = getEventsForDay(day)}
|
||||
{@const isToday = isSameDay(day, today)}
|
||||
{@const inMonth = isCurrentMonth(day)}
|
||||
<button
|
||||
class="bg-dark min-h-[80px] p-1 text-left transition-colors hover:bg-light/5"
|
||||
class:opacity-40={!inMonth}
|
||||
onclick={() => onDateClick?.(day)}
|
||||
>
|
||||
<div class="flex items-center justify-center w-7 h-7 mb-1">
|
||||
<span
|
||||
class="text-sm {isToday
|
||||
? 'bg-primary text-white rounded-full w-7 h-7 flex items-center justify-center'
|
||||
: 'text-light/80'}"
|
||||
>
|
||||
{day.getDate()}
|
||||
</span>
|
||||
<!-- Month View -->
|
||||
{#if currentView === "month"}
|
||||
<div
|
||||
class="grid grid-cols-7 gap-px bg-light/10 rounded-lg overflow-hidden"
|
||||
>
|
||||
{#each weekDays as day}
|
||||
<div
|
||||
class="bg-dark px-2 py-2 text-center text-sm font-medium text-light/50"
|
||||
>
|
||||
{day}
|
||||
</div>
|
||||
<div class="space-y-0.5">
|
||||
{#each dayEvents.slice(0, 3) as event}
|
||||
<button
|
||||
class="w-full text-xs px-1 py-0.5 rounded truncate text-left"
|
||||
style="background-color: {event.color ?? '#6366f1'}20; color: {event.color ?? '#6366f1'}"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEventClick?.(event);
|
||||
}}
|
||||
{/each}
|
||||
|
||||
{#each days as day}
|
||||
{@const dayEvents = getEventsForDay(day)}
|
||||
{@const isToday = isSameDay(day, today)}
|
||||
{@const inMonth = isCurrentMonth(day)}
|
||||
<button
|
||||
class="bg-dark min-h-[80px] p-1 text-left transition-colors hover:bg-light/5"
|
||||
class:opacity-40={!inMonth}
|
||||
onclick={() => onDateClick?.(day)}
|
||||
>
|
||||
<div class="flex items-center justify-center w-7 h-7 mb-1">
|
||||
<span
|
||||
class="text-sm {isToday
|
||||
? 'bg-primary text-white rounded-full w-7 h-7 flex items-center justify-center'
|
||||
: 'text-light/80'}"
|
||||
>
|
||||
{event.title}
|
||||
{day.getDate()}
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-0.5">
|
||||
{#each dayEvents.slice(0, 3) as event}
|
||||
<button
|
||||
class="w-full text-xs px-1 py-0.5 rounded truncate text-left"
|
||||
style="background-color: {event.color ??
|
||||
'#6366f1'}20; color: {event.color ??
|
||||
'#6366f1'}"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEventClick?.(event);
|
||||
}}
|
||||
>
|
||||
{event.title}
|
||||
</button>
|
||||
{/each}
|
||||
{#if dayEvents.length > 3}
|
||||
<p class="text-xs text-light/40 px-1">
|
||||
+{dayEvents.length - 3} more
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Week View -->
|
||||
{#if currentView === "week"}
|
||||
<div
|
||||
class="grid grid-cols-7 gap-px bg-light/10 rounded-lg overflow-hidden"
|
||||
>
|
||||
{#each weekDates as day}
|
||||
{@const dayEvents = getEventsForDay(day)}
|
||||
{@const isToday = isSameDay(day, today)}
|
||||
<div class="bg-dark">
|
||||
<div class="px-2 py-2 text-center border-b border-light/10">
|
||||
<div class="text-xs text-light/50">
|
||||
{weekDays[day.getDay()]}
|
||||
</div>
|
||||
<div
|
||||
class="text-lg font-medium {isToday
|
||||
? 'text-primary'
|
||||
: 'text-light'}"
|
||||
>
|
||||
{day.getDate()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-h-[300px] p-1 space-y-1">
|
||||
{#each dayEvents as event}
|
||||
<button
|
||||
class="w-full text-xs px-2 py-1.5 rounded text-left"
|
||||
style="background-color: {event.color ??
|
||||
'#6366f1'}20; color: {event.color ??
|
||||
'#6366f1'}"
|
||||
onclick={() => onEventClick?.(event)}
|
||||
>
|
||||
<div class="font-medium truncate">
|
||||
{event.title}
|
||||
</div>
|
||||
<div class="text-[10px] opacity-70">
|
||||
{new Date(
|
||||
event.start_time,
|
||||
).toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Day View -->
|
||||
{#if currentView === "day"}
|
||||
{@const dayEvents = getEventsForDay(currentDate)}
|
||||
<div class="bg-dark rounded-lg p-4 min-h-[400px]">
|
||||
{#if dayEvents.length === 0}
|
||||
<div class="text-center text-light/40 py-12">
|
||||
<p>No events for this day</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-2">
|
||||
{#each dayEvents as event}
|
||||
<button
|
||||
class="w-full text-left p-3 rounded-lg transition-colors hover:opacity-80"
|
||||
style="background-color: {event.color ??
|
||||
'#6366f1'}20; border-left: 3px solid {event.color ??
|
||||
'#6366f1'}"
|
||||
onclick={() => onEventClick?.(event)}
|
||||
>
|
||||
<div class="font-medium text-light">
|
||||
{event.title}
|
||||
</div>
|
||||
<div class="text-sm text-light/60 mt-1">
|
||||
{new Date(event.start_time).toLocaleTimeString(
|
||||
"en-US",
|
||||
{ hour: "numeric", minute: "2-digit" },
|
||||
)}
|
||||
- {new Date(event.end_time).toLocaleTimeString(
|
||||
"en-US",
|
||||
{ hour: "numeric", minute: "2-digit" },
|
||||
)}
|
||||
</div>
|
||||
{#if event.description}
|
||||
<div class="text-sm text-light/50 mt-2">
|
||||
{event.description}
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
{#if dayEvents.length > 3}
|
||||
<p class="text-xs text-light/40 px-1">+{dayEvents.length - 3} more</p>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user