234 lines
6.0 KiB
Svelte
234 lines
6.0 KiB
Svelte
<script lang="ts">
|
|
import type { PageProps } from "./$types";
|
|
|
|
import { enhance } from "$app/forms";
|
|
import { MONTHS, toInt, toFloat, padInt } from "$lib/util";
|
|
|
|
let { data }: PageProps = $props();
|
|
|
|
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 };
|
|
})());
|
|
|
|
let new_quart = $state(next.quarter);
|
|
let estimate_store = $state({
|
|
estimate_0: { value: "" },
|
|
estimate_1: { value: "" },
|
|
estimate_2: { value: "" },
|
|
editing: { value: "" }
|
|
})
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
</script>
|
|
|
|
<form id="form_add_quart" method="POST" action="?/add_quarter" use:enhance></form>
|
|
<form id="form_create_pdf" method="POST" action="{base}/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 as any} value={next.year} tabindex="8"/> - <input form="form_add_quart" style:width="4ch" type="number" name="quarter" oninput={validate_quarter as any} 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: any) => 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: any) => 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: any) => validate_estimate(e, estimate_store.estimate_2)} tabindex="12" /></td>
|
|
</tr>
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
<div style:width="100%" style:height="40px"></div>
|
|
|
|
<table class="list">
|
|
|
|
<thead>
|
|
<tr>
|
|
<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>
|
|
|
|
{#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="{base}/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[1].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[2].estimate, 3, 2, "\u2007", false, false) : fill_str}</td>
|
|
</tr>
|
|
{/each}
|
|
{/each}
|
|
</tbody>
|
|
|
|
{#if data.estimates.size === 0}
|
|
<tfoot>
|
|
<tr>
|
|
<td class="td-no-elements" colspan="999">No records</td>
|
|
</tr>
|
|
</tfoot>
|
|
{/if}
|
|
</table>
|
|
|
|
<style>
|
|
|
|
form {
|
|
width: fit-content;
|
|
border: none;
|
|
}
|
|
|
|
table {
|
|
margin: auto;
|
|
|
|
border-collapse: collapse;
|
|
border: none;
|
|
}
|
|
|
|
.add {
|
|
|
|
tr {
|
|
border-bottom: solid 1px black;
|
|
}
|
|
tr:last-of-type {
|
|
border-bottom: none;
|
|
}
|
|
|
|
td {
|
|
text-align: center;
|
|
padding-left: 10px;
|
|
padding-right: 5px;
|
|
}
|
|
|
|
input {
|
|
width: 80%;
|
|
}
|
|
}
|
|
|
|
.list {
|
|
/*width: 50%;*/
|
|
margin: auto;
|
|
|
|
|
|
|
|
tbody > tr:has(> td.year):has(td.quarter) {
|
|
border-top: 3px black double;
|
|
}
|
|
|
|
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>
|