File size: 3,541 Bytes
4ae345a
 
 
 
 
3b74a95
4ae345a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3b74a95
 
4ae345a
 
 
 
 
 
 
 
 
3b74a95
 
 
 
 
 
 
 
 
 
 
 
 
 
4ae345a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3b74a95
4ae345a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<script lang="ts">
	import { enhance } from '$app/forms';
	import { downscaleImage } from '$lib/client/downscale';
	import type { ExpensePhoto, PublicUser } from '$lib/domain/types';
	import type { ExpenseFormValues, FieldErrors } from '$lib/forms/types';
	import { untrack } from 'svelte';
	import FormError from './FormError.svelte';
	import SplitEditor from './SplitEditor.svelte';

	let {
		members,
		currency,
		values,
		errors = {},
		existingPhoto,
		version,
		submitLabel
	}: {
		members: PublicUser[];
		currency: string;
		values: ExpenseFormValues;
		errors?: FieldErrors;
		existingPhoto?: ExpensePhoto;
		version?: number;
		submitLabel: string;
	} = $props();

	// one-time snapshot of props at mount; not meant to react to later prop changes
	let amount = $state(untrack(() => values.amount));
	let submitting = $state(false);
</script>

<form
	method="POST"
	enctype="multipart/form-data"
	class="space-y-4"
	use:enhance={async ({ formData }) => {
		submitting = true;
		let reachedReturn = false;
		try {
			const photo = formData.get('photo');
			if (photo instanceof File && photo.size > 0 && photo.type.startsWith('image/')) {
				try {
					const small = await downscaleImage(photo);
					if (small) formData.set('photo', small, 'photo.jpg');
				} catch {
					/* keep original file */
				}
			}
			reachedReturn = true;
		} finally {
			if (!reachedReturn) submitting = false;
		}
		return async ({ update }) => {
			submitting = false;
			await update();
		};
	}}
>
	{#if version !== undefined}
		<input type="hidden" name="version" value={version} />
	{/if}

	<div>
		<label for="description">Description</label>
		<input
			id="description"
			name="description"
			required
			maxlength="120"
			value={values.description}
			placeholder="Dinner, groceries, tickets…"
		/>
		<FormError message={errors.description} />
	</div>

	<div class="grid grid-cols-2 gap-3">
		<div>
			<label for="amount">Amount ({currency})</label>
			<input id="amount" name="amount" inputmode="decimal" placeholder="0.00" required bind:value={amount} />
			<FormError message={errors.amount} />
		</div>
		<div>
			<label for="date">Date</label>
			<input id="date" name="date" type="date" required value={values.date} />
			<FormError message={errors.date} />
		</div>
	</div>

	<div>
		<label for="paidBy">Paid by</label>
		<select id="paidBy" name="paidBy" value={values.paidBy}>
			<option value="">Choose…</option>
			{#each members as m (m.id)}
				<option value={m.id}>{m.displayName}</option>
			{/each}
		</select>
		<FormError message={errors.paidBy} />
	</div>

	<SplitEditor {members} {currency} {values} {amount} error={errors.participants ?? errors.shares} />

	<div>
		<label for="notes">Notes (optional)</label>
		<textarea id="notes" name="notes" rows="2" maxlength="2000" value={values.notes}></textarea>
	</div>

	<div>
		<label for="photo">Receipt photo (optional)</label>
		{#if existingPhoto}
			<img src="/{existingPhoto.key}" alt="Current receipt" class="mb-2 max-h-40 rounded-lg" />
			<label class="mb-2 flex items-center gap-2 font-normal"
				><input type="checkbox" name="removePhoto" class="h-5 w-5" /> Remove current photo</label
			>
		{/if}
		<input id="photo" name="photo" type="file" accept="image/*" />
		<FormError message={errors.photo} />
	</div>

	<FormError message={errors.form} />
	<button
		type="submit"
		disabled={submitting}
		class="w-full rounded-lg bg-green-700 py-3 font-semibold text-white disabled:opacity-60"
	>
		{submitting ? 'Saving…' : submitLabel}
	</button>
</form>