Implement DTOs, add managed state for schedule, export to txt
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { Calendar, Popover } from "bits-ui";
|
||||
import { CalendarDate, getDayOfWeek } from "@internationalized/date";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { warn, debug, trace, info, error } from "@tauri-apps/plugin-log";
|
||||
import { trace } from "@tauri-apps/plugin-log";
|
||||
|
||||
import Basic from "./components/configurations/basic.svelte";
|
||||
import Residents from "./components/configurations/residents.svelte";
|
||||
@@ -99,7 +96,7 @@
|
||||
<div class="h-1.5 w-full overflow-hidden rounded-full bg-zinc-200">
|
||||
<div
|
||||
class="h-full bg-emerald-600 transition-all duration-500"
|
||||
style="width: {(rota.currentStep / steps.length) * 100}%"
|
||||
style="width: {(((rota.currentStep - 1) / (steps.length - 1)) * 100).toFixed(0)}%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import type { CalendarDate } from "@internationalized/date";
|
||||
import { trace } from "@tauri-apps/plugin-log";
|
||||
import { Calendar, Popover } from "bits-ui";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { rota, steps } from "../../state.svelte.js";
|
||||
|
||||
@@ -321,7 +321,7 @@
|
||||
<div class="flex flex-col items-center space-y-2 px-4">
|
||||
<p class="text-[10px] font-black tracking-widest text-zinc-500 uppercase">Shift Types</p>
|
||||
<div class="flex gap-1">
|
||||
{#each ["OpenAsFirst", "OpenAsSecond", "Closed"] as type}
|
||||
{#each ["Closed", "OpenFirst", "OpenSecond"] as type}
|
||||
{@const active = resident.allowedTypes.includes(type)}
|
||||
<button
|
||||
type="button"
|
||||
@@ -348,14 +348,14 @@
|
||||
Reduced Workload
|
||||
</p>
|
||||
<button
|
||||
onclick={() => (resident.hasReducedLoad = !resident.hasReducedLoad)}
|
||||
onclick={() => (resident.reducedLoad = !resident.reducedLoad)}
|
||||
class="flex items-center gap-2 rounded-lg border px-3 py-1 transition-all hover:border-blue-200 hover:bg-white
|
||||
{resident.hasReducedLoad
|
||||
{resident.reducedLoad
|
||||
? 'border-green-200 bg-green-50 text-green-700'
|
||||
: 'border-zinc-200 bg-white text-zinc-500'}"
|
||||
>
|
||||
<div
|
||||
class="size-2 rounded-full {resident.hasReducedLoad
|
||||
class="size-2 rounded-full {resident.reducedLoad
|
||||
? 'animate-pulse bg-green-500'
|
||||
: 'bg-zinc-300'}"
|
||||
></div>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { rota, steps } from "../../state.svelte.js";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { type MonthlyScheduleDTO, rota, steps, type ShiftPosition } from "../../state.svelte.js";
|
||||
|
||||
function getResidentName(day: number, slot: number) {
|
||||
function getResidentName(day: number, pos: ShiftPosition) {
|
||||
const residentId = rota.solution[`${day}-${pos}`];
|
||||
const r = rota.findResident(residentId);
|
||||
if (r) return r.name;
|
||||
|
||||
// check for manual
|
||||
const assignedResidents = rota.residents.filter((resident) =>
|
||||
resident.manualShifts.some(
|
||||
(shift) =>
|
||||
@@ -12,6 +18,13 @@
|
||||
)
|
||||
);
|
||||
|
||||
let slot;
|
||||
if (pos == "First") {
|
||||
slot = 1;
|
||||
} else {
|
||||
slot = 2;
|
||||
}
|
||||
|
||||
const resident = assignedResidents[slot - 1];
|
||||
return resident ? resident.name : "-";
|
||||
}
|
||||
@@ -21,23 +34,31 @@
|
||||
return day % 2 === 0 ? 1 : 2;
|
||||
}
|
||||
|
||||
//TODO: invoke rust?
|
||||
// const resident = {
|
||||
// id: crypto.randomUUID(),
|
||||
// name: "",
|
||||
// negativeShifts: [] as CalendarDate[],
|
||||
// manualShifts: [] as CalendarDate[]
|
||||
// };
|
||||
async function generate() {
|
||||
let config = rota.toDTO();
|
||||
console.log(config);
|
||||
|
||||
// try {
|
||||
// let replyFrom = await invoke("add_resident", { resident });
|
||||
// console.log("Result:", replyFrom);
|
||||
// residents = [...residents, resident];
|
||||
// } catch (error) {
|
||||
// console.error("Error:", error);
|
||||
// }
|
||||
try {
|
||||
let schedule = await invoke<MonthlyScheduleDTO>("generate", { config });
|
||||
console.log("replyFromGenerate:", schedule);
|
||||
rota.solution = schedule;
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function export_file() {
|
||||
let config = rota.toDTO();
|
||||
let schedule = rota.solution;
|
||||
|
||||
try {
|
||||
await invoke("export", { config, schedule });
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<h2 class="text-2xl font-bold text-zinc-800">{steps[rota.currentStep - 1].title}</h2>
|
||||
<div class="justify-end">
|
||||
@@ -78,15 +99,15 @@
|
||||
onclick={() => "handleCellClick(day, 1)"}
|
||||
class="w-full overflow-hidden rounded border border-pink-200 bg-pink-50 px-1.5 py-1 text-left text-[10px] font-bold text-pink-600 transition-colors hover:bg-pink-100"
|
||||
>
|
||||
{getResidentName(day, 1)}
|
||||
{getResidentName(day, "First")}
|
||||
</button>
|
||||
{/if}
|
||||
{#if slotCount == 2}
|
||||
{#if slotCount > 1}
|
||||
<button
|
||||
onclick={() => "handleCellClick(day, 2)"}
|
||||
class="w-full overflow-hidden rounded border border-emerald-200 bg-emerald-50 px-1.5 py-1 text-left text-[10px] font-bold text-emerald-600 transition-colors hover:bg-emerald-100"
|
||||
>
|
||||
{getResidentName(day, 2)}
|
||||
{getResidentName(day, "Second")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -99,13 +120,14 @@
|
||||
<Button
|
||||
variant="outline"
|
||||
class="border-zinc-200 bg-white text-zinc-600 shadow-sm transition-all hover:bg-zinc-50 hover:text-zinc-900 active:scale-95"
|
||||
onclick={() => generate()}
|
||||
>
|
||||
Ανακατανομή
|
||||
</Button>
|
||||
<Button
|
||||
onclick={() => "export adasds"}
|
||||
variant="outline"
|
||||
class="border-zinc-200 bg-white text-zinc-600 shadow-sm transition-all hover:bg-zinc-50 hover:text-zinc-900 active:scale-95"
|
||||
onclick={() => export_file()}
|
||||
>
|
||||
Εξαγωγή</Button
|
||||
>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
return day % 2 === 0 ? 1 : 2;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<h2 class="text-2xl font-bold text-zinc-800">{steps[rota.currentStep - 1].title}</h2>
|
||||
<div class="justify-end">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// state.svelte.ts
|
||||
import { CalendarDate, getDayOfWeek } from "@internationalized/date";
|
||||
import { trace } from "@tauri-apps/plugin-log";
|
||||
|
||||
export interface Resident {
|
||||
id: string;
|
||||
@@ -9,7 +8,7 @@ export interface Resident {
|
||||
manualShifts: CalendarDate[];
|
||||
maxShifts: number | undefined;
|
||||
allowedTypes: string[];
|
||||
hasReducedLoad: boolean;
|
||||
reducedLoad: boolean;
|
||||
}
|
||||
|
||||
export interface ForbiddenPair {
|
||||
@@ -30,6 +29,8 @@ export class RotaState {
|
||||
daysArray = $derived(Array.from({ length: this.projectMonthDays }, (_, i) => i + 1));
|
||||
emptySlots = $derived(Array.from({ length: getDayOfWeek(this.projectMonth, "en-GB") }));
|
||||
|
||||
solution = $state<MonthlySchedule>({} as MonthlySchedule);
|
||||
|
||||
addResident() {
|
||||
this.residents.push({
|
||||
id: crypto.randomUUID(),
|
||||
@@ -37,13 +38,17 @@ export class RotaState {
|
||||
negativeShifts: [],
|
||||
manualShifts: [],
|
||||
maxShifts: undefined,
|
||||
allowedTypes: ["OpenAsFirst", "OpenAsSecond", "Closed"],
|
||||
hasReducedLoad: false
|
||||
allowedTypes: ["Closed", "OpenFirst", "OpenSecond"],
|
||||
reducedLoad: false
|
||||
});
|
||||
}
|
||||
|
||||
removeResident(id: string) {
|
||||
this.residents = this.residents.filter((p) => p.id !== id);
|
||||
this.residents = this.residents.filter((r) => r.id !== id);
|
||||
}
|
||||
|
||||
findResident(id: string) {
|
||||
return this.residents.find((r) => r.id === id);
|
||||
}
|
||||
|
||||
addForbiddenPair() {
|
||||
@@ -57,10 +62,61 @@ export class RotaState {
|
||||
removeForbiddenPair(index: number) {
|
||||
this.forbiddenPairs.splice(index, 1);
|
||||
}
|
||||
|
||||
toDTO(): UserConfigDTO {
|
||||
return {
|
||||
month: this.selectedMonth,
|
||||
year: this.selectedYear,
|
||||
holidays: this.holidays.map((d) => d.day),
|
||||
residents: this.residents.map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
negativeShifts: r.negativeShifts.map((d) => d.day),
|
||||
manualShifts: r.manualShifts.map((s) => ({
|
||||
day: s.day,
|
||||
position: "First"
|
||||
})),
|
||||
maxShifts: r.maxShifts ?? null,
|
||||
allowedTypes: r.allowedTypes as ShiftType[],
|
||||
reducedLoad: r.reducedLoad
|
||||
})),
|
||||
|
||||
toxic_pairs: this.forbiddenPairs.map((pair) => [pair.id1, pair.id2])
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const rota = new RotaState();
|
||||
|
||||
export type MonthlyScheduleDTO = {
|
||||
schedule: Record<number, Record<ShiftPosition, string>>;
|
||||
};
|
||||
|
||||
export type UserConfigDTO = {
|
||||
month: number;
|
||||
year: number;
|
||||
holidays: Array<number>;
|
||||
residents: Array<ResidentDTO>;
|
||||
toxic_pairs: Array<[string, string]>;
|
||||
};
|
||||
|
||||
export type ResidentDTO = {
|
||||
id: string;
|
||||
name: string;
|
||||
negativeShifts: Array<number>;
|
||||
manualShifts: Array<{ day: number; position: ShiftPosition }>;
|
||||
maxShifts: number | null;
|
||||
allowedTypes: Array<ShiftType>;
|
||||
reducedLoad: boolean;
|
||||
};
|
||||
|
||||
export type ShiftType = "Closed" | "OpenFirst" | "OpenSecond";
|
||||
|
||||
export type ShiftPosition = "First" | "Second";
|
||||
|
||||
export type SlotKey = `${number}-${"First" | "Second"}`;
|
||||
export type MonthlySchedule = { [key: SlotKey]: string };
|
||||
|
||||
export const steps = [
|
||||
{
|
||||
id: 1,
|
||||
|
||||
Reference in New Issue
Block a user