Stundenaufzeichnung/src/routes/schaetzung/+page.svelte

296 lines
8.1 KiB
Svelte
Raw Normal View History

<script lang="ts">
2025-03-25 15:25:04 +01:00
import type { PageProps } from "./$types";
import { enhance } from "$app/forms";
2025-03-25 15:25:04 +01:00
import { MONTHS, toInt, toFloat, padInt } from "$lib/util";
let { form, data }: PageProps = $props();
$inspect(data);
let next = $state((() => {
if (data.estimates.size == 0) {
return { year: (new Date()).getFullYear(), quarter: (new Date()).getMonth() / 3 + 1 };
}
const max_year = Math.max(...Array.from(data.estimates.keys()))
const max_quart = Math.max(...Array.from(data.estimates.get(max_year)?.keys() ?? [0]));
return { year: max_quart < 4 ? max_year : max_year + 1, quarter: max_quart < 4 ? max_quart + 1 : 1 };
})());
2025-03-25 15:25:04 +01:00
let new_quart = $state(next.quarter);
let estimate_store = $state({
estimate_0: { value: "" },
estimate_1: { value: "" },
estimate_2: { value: "" },
editing: { value: "" }
})
$inspect(estimate_store);
function validate_year(event: InputEvent) {
const input = event.target as HTMLInputElement;
if (event.inputType.startsWith("delete") || !isNaN(toInt(event.data ?? ""))) {
next.year = toInt(input.value);
} else {
input.value = next.year.toFixed(0);
}
}
function validate_quarter(event: InputEvent) {
const input = event.target as HTMLInputElement;
let in_num = event.data != null ? toInt(event.data) : NaN;
if (event.inputType.startsWith("delete") || (!isNaN(in_num) && 1 <= in_num && in_num <= 4)) {
next.quarter = toInt(input.value);
} else {
input.value = next.quarter.toFixed(0);
}
}
2025-03-25 15:25:04 +01:00
function validate_estimate(event: InputEvent, estimate: { value: string }) {
const input = event.target as HTMLInputElement;
if (event.inputType.startsWith("delete") || !isNaN(toFloat(input.value))) {
estimate.value = input.value;
} else {
input.value = estimate.value;
}
}
2025-03-25 15:25:04 +01:00
/*const TODAY = new Date();
const NEXT_QUART = (data?.estimates?.length > 0)
? new Date(data.estimates[0].year + (data.estimates[0].quarter === 4 ? 1 : 0), 4 * (data.estimates[0].quarter - (data.estimates[0].quarter === 4 ? -4 : 0)))
: TODAY;
const add_quart = quart_from_date(NEXT_QUART);
function quart_from_date(date: Date) {
return Math.floor((date.getMonth()+1) / 4) + 1;
}
function month_from_quart(quart: number, num: number) {
return new Date(TODAY.getFullYear(), (quart - 1) * 4 + num);
2025-03-25 15:25:04 +01:00
}*/
</script>
<form id="form_add_quart" method="POST" action="?/add_quarter" use:enhance></form>
2025-03-25 15:25:04 +01:00
<form id="form_create_pdf" method="POST" action="/dokumente?/create_estimate" use:enhance></form>
<h1>Stundenschätzung</h1>
<table class="add">
<colgroup>
<col style:min-width="30ch" />
<col style:width="12ch" />
<col style:width="10ch" />
<col style:width="7ch" />
</colgroup>
<tbody>
<tr>
<td rowspan="3">Neue Schätzung: <input form="form_add_quart" style:width="7ch" type="number" name="year" oninput={validate_year} value={next.year} tabindex="8"/> - <input form="form_add_quart" style:width="4ch" type="number" name="quarter" oninput={validate_quarter} value={new_quart} tabindex="9"/>. Quartal</td>
<td>{MONTHS[(new_quart - 1) * 3 + 0]}</td>
<td><input form="form_add_quart" type="text" name="estimate_0" oninput={(e) => validate_estimate(e, estimate_store.estimate_0)} tabindex="10" /></td>
<td rowspan="3"><button form="form_add_quart" type="submit" tabindex="13">Erstellen</button></td>
</tr>
<tr>
<td>{MONTHS[(new_quart - 1) * 3 + 1]}</td>
<td><input form="form_add_quart" type="text" name="estimate_1" oninput={(e) => validate_estimate(e, estimate_store.estimate_1)} tabindex="11" /></td>
</tr>
<tr>
<td>{MONTHS[(new_quart - 1) * 3 + 2]}</td>
<td><input form="form_add_quart" type="text" name="estimate_2" oninput={(e) => validate_estimate(e, estimate_store.estimate_2)} tabindex="12" /></td>
</tr>
</tbody>
</table>
2025-03-25 15:25:04 +01:00
<div style:width="100%" style:height="40px"></div>
<table class="list">
<thead>
<tr>
2025-03-25 15:25:04 +01:00
<th style:width="5em"></th>
<th style:width="10ch">Quartal</th>
<th style:width="20ch">Monat</th>
<th style:width="10ch">Soll</th>
<th style:width="12ch" colspan="2">Aktion</th>
</tr>
</thead>
<tbody>
2025-03-25 15:25:04 +01:00
{#each data.estimates as year_pair}
{@const year = year_pair[0]}
{@const quarter_map = year_pair[1]}
{#each quarter_map as quarter_pair, quarter_i}
{@const quarter = quarter_pair[0]}
{@const months = quarter_pair[1]}
{@const fill_str = "\u2014\u2007\u2007\u2007"}
<tr>
{#if quarter_i === 0}
<td class="year" rowspan={quarter_map.size * 3}>{year}</td>
{/if}
<td class="quarter" rowspan="3">{quarter}.</td>
<td>{months[0].month}</td>
<td class="estimate">{months[0].estimate != null ? padInt(months[0].estimate, 3, 2, "\u2007", false, false) : fill_str}</td>
<td class="action" rowspan="3">
{#if data.documents?.get(year)?.get(quarter)?.[0] != null}
{@const document = data.documents.get(year).get(quarter)[0]}
<form method="GET" action={`/dokumente/${document.path}`}>
<button type="submit">Download PDF</button>
</form>
{:else}
<form method="POST" action="/dokumente?/create_estimate" use:enhance>
<input type="hidden" name="year" value={year} />
<input type="hidden" name="quarter" value={quarter} />
<button type="submit">Create PDF</button>
</form>
{/if}
</td>
</tr>
<tr>
<td>{months[1].month}</td>
<td class="estimate">{months[1].estimate != null ? padInt(months[0].estimate, 3, 2, "\u2007", false, false) : fill_str}</td>
</tr>
<tr>
<td>{months[2].month}</td>
<td class="estimate">{months[2].estimate != null ? padInt(months[0].estimate, 3, 2, "\u2007", false, false) : fill_str}</td>
</tr>
{/each}
{/each}
</tbody>
{#if data.estimates === undefined || data.estimates.size === 0}
<tfoot>
<tr>
2025-03-25 15:25:04 +01:00
<td class="td-no-elements" colspan="999">No records</td>
</tr>
</tfoot>
{/if}
</table>
{"" /*<tr>
<td rowspan="3">{add_quart}. Quartal {NEXT_QUART.getFullYear()}</td>
<td>{month_of(month_from_quart(add_quart, 0))}</td>
<!-- svelte-ignore a11y_positive_tabindex -->
<td><input form="form_add_quart" type="text" tabindex="1" name="estimate_0" value={form?.data?.estimate_0}/></td>
<td rowspan="3">
<input form="form_add_quart" type="hidden" name="year" value={NEXT_QUART.getFullYear()}/>
<input form="form_add_quart" type="hidden" name="quarter" value={add_quart}/>
<button form="form_add_quart" type="submit">+</button>
</td>
</tr>
<tr>
<td>{month_of(month_from_quart(add_quart, 1))}</td>
<!-- svelte-ignore a11y_positive_tabindex -->
<td><input form="form_add_quart" type="text" tabindex="2" name="estimate_1" value={form?.data?.estimate_1}/></td>
</tr>
<tr>
<td>{month_of(month_from_quart(add_quart, 2))}</td>
<!-- svelte-ignore a11y_positive_tabindex -->
<td><input form="form_add_quart" type="text" tabindex="3" name="estimate_2" value={form?.data?.estimate_2}/></td>
2025-03-25 15:25:04 +01:00
</tr>*/}
{"" /*
{#if months.length > 0}
<tr>
<td rowspan={rowspan}>
{quarter.quarter}. Quartal {quarter.year}
</td>
<td>{month_of(month_from_quart(quarter.quarter, months[0].i))}</td>
<td>{months[0].estimate.toFixed(2)}</td>
<td rowspan={rowspan}>Edit</td>
</tr>
{#each months.slice(1) as month}
<tr>
<td>{month_of(month_from_quart(quarter.quarter, month.i))}</td>
<td>{month.estimate.toFixed(2)}</td>
</tr>
{/each}
2025-03-25 15:25:04 +01:00
{/if}*/}
<style>
form {
width: fit-content;
border: none;
}
table {
margin: auto;
border-collapse: collapse;
2025-03-25 15:25:04 +01:00
border: none;
}
2025-03-25 15:25:04 +01:00
.add {
tr {
border-bottom: solid 1px black;
}
tr:last-of-type {
border-bottom: none;
}
2025-03-25 15:25:04 +01:00
td {
text-align: center;
padding-left: 10px;
padding-right: 5px;
}
2025-03-25 15:25:04 +01:00
input {
width: 80%;
}
}
2025-03-25 15:25:04 +01:00
.list {
/*width: 50%;*/
margin: auto;
2025-03-25 15:25:04 +01:00
tbody > tr:has(> td.year):has(td.quarter) {
border-top: 3px black double;
}
2025-03-25 15:25:04 +01:00
tbody > tr:has(> td.quarter) {
border-top: 1px black solid;
}
tbody .year {
vertical-align: middle;
writing-mode: sideways-lr;
text-orientation: mixed;
}
tbody .estimate {
padding-right: 1ch;
text-align: right;
}
tbody > tr > td {
text-align: center;
}
tfoot {
border-top: 1px solid black;
}
.td-no-elements {
text-align: center;
}
}
</style>