Squash commmits

This commit is contained in:
tobias
2025-07-21 11:33:44 +02:00
commit 60d6c1cb8c
32 changed files with 4495 additions and 0 deletions

View File

@ -0,0 +1,7 @@
<script lang="ts">
import '../app.css';
let { children } = $props();
</script>
{@render children()}

View File

@ -0,0 +1,38 @@
import { fail } from '@sveltejs/kit';
// import type { Actions } from './$types';
import { getCrabfitData } from '$lib/crabfitData';
import { findMeetingOptions, filterMemberData } from '$lib/meeting';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ fetch }) => {
const res = await fetch('/api/members');
const data = await res.json();
return { members: data.names };
};
export const actions: Actions = {
getMeetings: async ({ request }) => {
const form = await request.formData();
const selectedMembers = form.getAll('selectedMembers');
const days = form.getAll('selectedDays');
// const number = form.get('number');
const number = 1;
if (!selectedMembers) return fail(500, { success: false, error: 'Select a member' });
if (!days) return fail(500, { success: false, error: 'Select a day' });
try {
const crabfit = await getCrabfitData();
const members = await filterMemberData(selectedMembers, crabfit);
const options = findMeetingOptions(members, { days, number });
return { success: true, options };
} catch (err) {
console.error('findMeetingOptions error:', err);
return fail(500, { success: false, error: 'Failed to fetch meetings' });
}
}
};

206
src/routes/+page.svelte Normal file
View File

@ -0,0 +1,206 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { Copy } from '@lucide/svelte';
import type { PageProps } from './$types';
import ClickSpark from '$lib/components/ClickSpark.svelte';
let { data }: PageProps = $props();
// let members: string[] = $state(data.members);
let selectedDays: string[] = $state([]);
let lastSelectedDays = $state([]);
let selectedMembers: string[] = $state([]);
let number = $state(1);
let form: HTMLFormElement;
let noMeetings = $state(false);
let meetingOptions: { [optionName: string]: [string, string] }[] = $state([]);
let sparkRef;
const daysOfTheWeek = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday'
];
const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
function updateMeetings() {
noMeetings = false;
if (selectedDays.length > 0 && selectedMembers.length > 0) {
if (form) form.requestSubmit();
} else {
meetingOptions = [];
}
}
function sortByDay(options) {
const grouped = {};
for (const option of options) {
for (const [optName, [day, time]] of Object.entries(option)) {
if (!grouped[day]) grouped[day] = [];
grouped[day].push(time);
}
}
for (const day in grouped) {
grouped[day].sort();
}
return grouped;
}
function formatTime(time: string) {
const t = time.split('');
return `${t[0]}${t[1]}:${t[2]}${t[3]}`;
}
function copyOptions(e) {
const membs = selectedMembers.reduce((prev, curr) => `${prev}, ${curr}`);
let text = `Meeting times for ${membs}\n`;
meetingOptions.forEach((opt, i) => {
const [day, time] = Object.values(opt)[0];
text += `${day} at ${formatTime(time)}\n`;
});
navigator.clipboard.writeText(text);
sparkRef.triggerSpark(e);
}
</script>
<div class="flex flex-col items-center justify-center gap-8 px-8 pt-8 pb-20">
<div>
<form
class="flex flex-wrap gap-8 md:gap-24"
bind:this={form}
id="meeting-form"
method="POST"
action="?/getMeetings"
use:enhance={({ formElement, formData, action, cancel, submitter }) => {
return async ({ result, update }) => {
if (result.type === 'success') {
meetingOptions = result.data.options;
if (meetingOptions.length === 0) noMeetings = true;
}
if (result.type === 'error' || result.type === 'failure') {
meetingOptions = [];
}
};
}}
>
<div class="flex flex-col gap-4">
<ul class="menu-horizontal rounded-box bg-base-200">
<li class="menu-title">Select</li>
<li>
<button
onclick={() => {
if (selectedDays.toString() === weekdays.toString()) {
selectedDays = [...lastSelectedDays];
} else {
lastSelectedDays = [...selectedDays];
selectedDays = [...weekdays];
}
}}
class="btn">Weekdays</button
>
</li>
<li>
<button
onclick={() => {
if (selectedDays.toString() === daysOfTheWeek.toString()) {
selectedDays = [...lastSelectedDays];
} else {
lastSelectedDays = [...selectedDays];
selectedDays = [...daysOfTheWeek];
}
}}
class="btn">All</button
>
</li>
<li>
<button
onclick={() => {
if (selectedDays.toString() === '') {
selectedDays = [...lastSelectedDays];
} else {
lastSelectedDays = [...selectedDays];
selectedDays = [];
}
}}
class="btn">None</button
>
</li>
</ul>
<fieldset id="day-select" class="fieldset flex flex-col gap-3">
<legend class="fieldset-legend text-xl">Which days</legend>
{#each daysOfTheWeek as day}
<label class="label gap-3 text-xl text-base-content select-none"
><input
class="checkbox checkbox-lg checkbox-secondary"
type="checkbox"
bind:group={selectedDays}
onchange={updateMeetings}
name="selectedDays"
value={day}
/>{day}</label
>
{/each}
</fieldset>
</div>
<fieldset id="member-select" class="fieldset flex flex-col gap-3">
<legend class="fieldset-legend text-xl">Which members</legend>
{#each data.members as member}
<label class="label gap-3 text-xl text-base-content select-none"
><input
class="checkbox checkbox-lg checkbox-primary"
type="checkbox"
bind:group={selectedMembers}
onchange={updateMeetings}
value={member}
name="selectedMembers"
/>{member}</label
>
{/each}
</fieldset>
</form>
</div>
<div class="divider"><h2 class="text-2xl">Possible meeting times</h2></div>
<div>
{#if meetingOptions.length > 0}
<button class="btn absolute right-5 sm:right-20" onclick={copyOptions}><Copy /> </button>
<div class="flex flex-wrap justify-center gap-8 md:gap-4">
{#each Object.entries(sortByDay(meetingOptions)) as [day, times]}
<ul class="list h-fit w-56 rounded-box bg-base-200 shadow-md sm:w-fit">
<h3 class="divide-y-2 rounded-t-box bg-base-300 px-8 py-4 text-center text-2xl">
{day}
</h3>
{#each times as time}
<li
class="list-row flex justify-center divide-y-2 rounded-box text-2xl font-extralight tabular-nums"
>
{formatTime(time)}
</li>
{/each}
</ul>
{/each}
</div>
{:else if noMeetings}
<p class="text-lg font-semibold">No meeting options found</p>
{:else}
<p class="text-lg font-semibold">Select days and members first</p>
{/if}
</div>
</div>
<ClickSpark bind:this={sparkRef} />

View File

@ -0,0 +1,12 @@
import type { RequestHandler } from '@sveltejs/kit';
import { getCrabfitData } from '$lib/crabfitData';
export const GET: RequestHandler = async () => {
const crabfitData = await getCrabfitData();
const names = crabfitData.map((m) => m.name);
return new Response(JSON.stringify({ names }), {
headers: { 'Content-Type': 'application/json' }
});
};

96
src/routes/page.html Normal file
View File

@ -0,0 +1,96 @@
<script lang="ts">
import { options } from '../../.svelte-kit/generated/server/internal.js';
import { enhance } from '$app/forms';
import { onMount } from 'svelte';
let members: string[] = $state([]);
let selectedDay = $state('Mon');
let selectedMembers: string[] = $state([]);
let number = $state(1);
let form: HTMLFormElement;
let meetingOptions: { [optionName: string]: [string, string] }[] = $state([]);
// Fetch members on mount
onMount(async () => {
const res = await fetch('/api/members');
const data = await res.json();
members = data.names;
});
function updateMeetings() {
if (form) form.requestSubmit();
}
</script>
<form
bind:this="{form}"
id="meeting-form"
method="POST"
action="?/getMeetings"
use:enhance="{({"
formElement,
formData,
action,
cancel,
submitter
})=""
>
{ return async ({ result, update }) => { if (result.type === 'success') { meetingOptions =
result.data; } if (result.type === 'error' || result.type === 'failure') { meetingOptions = []; }
}; }} >
<div>
<label for="day-select">Select day of week:</label>
<select id="day-select" name="days" bind:value="{selectedDay}" onchange="{updateMeetings}">
<option>Mon</option>
<option>Tue</option>
<option>Wed</option>
<option>Thu</option>
<option>Fri</option>
<option>Sat</option>
<option>Sun</option>
</select>
</div>
<div>
<label>Select members:</label>
{#if members.length === 0}
<p>Loading members...</p>
{:else}
<select multiple bind:value="{members}">
{#each members as member}
<option value="{member}">{member}</option>
{/each}
</select>
{/if}
</div>
<div>
<label for="number-input">Number of meetings:</label>
<input
id="number-input"
type="number"
name="number"
min="1"
bind:value="{number}"
onchange="{updateMeetings}"
/>
</div>
<!-- Hidden inputs for comma-separated values -->
<!-- <input type="hidden" name="members" value={selectedMembers.join(',')} />
<input type="hidden" name="days" value={selectedDay} /> -->
</form>
<div>
<h2>Possible meeting times:</h2>
{#if meetingOptions.length > 0}
<ul>
{#each meetingOptions as option, i} {#each Object.entries(option) as [optName, [day, time]]}
<li><strong>{optName}</strong>: {day} at {time}</li>
{/each} {/each}
</ul>
{:else}
<p>No meeting options found.</p>
{/if}
</div>