Files
DriverVault/Web App/web/src/components/PartFormModal.vue
T
tajniak81andClaude Opus 4.8 07192f1238 Add attachments to service, maintenance, fuel and parts records
Documents already carried a single optional file: upload, download, detach,
access-checked on every request and proxied through this server, so PocketBase's
files are never public URLs. Service records, workshop visits, refills and
catalog parts all want the same thing — a receipt, an invoice, a photo of the
box — so extend it to them.

Rather than copy the document handlers four more times, lift them into one
shared layer. Every attachable collection has a car relation and a file field,
which is what lets a single set of handlers authorize and serve all of them. An
upload finishes by delegating to the collection's own GET handler, so the
response carries the full record — derived fields and all — exactly as a re-read
would. The web side gets the same treatment: one picker component and one
upload-after-save helper behind all five forms. Net effect is five features for
about the cost of the one that was already there.

Alongside:
- parts gain a notes field
- Reminders moves behind Parts catalog in the car detail tabs
- the changed-part labels spell out in full ("Oil & Oil filter" rather than
  "Oil & filter"), and the form and table now agree

The PocketBase schema must be migrated before the new attachments work:
scripts/setup-pocketbase.mjs adds the file fields and parts.notes. It is
additive and safe to re-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:41:01 +02:00

79 lines
2.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref } from "vue";
import { api } from "../api";
import { applyAttachment } from "../lib/attachment.js";
import AttachmentField from "./AttachmentField.vue";
import Modal from "./Modal.vue";
const props = defineProps({
carId: { type: String, required: true },
part: { type: Object, default: null },
});
const emit = defineEmits(["saved", "close"]);
const isEdit = !!props.part;
const saving = ref(false);
const error = ref("");
const form = ref({
name: props.part?.name ?? "",
partNumber: props.part?.partNumber ?? "",
category: props.part?.category ?? "",
notes: props.part?.notes ?? "",
});
const file = ref(null);
const removeFile = ref(false);
async function submit() {
saving.value = true;
error.value = "";
try {
const payload = {
car: props.carId,
name: form.value.name.trim(),
partNumber: form.value.partNumber.trim(),
category: form.value.category.trim(),
notes: form.value.notes.trim(),
};
const saved = isEdit
? await api.updatePart(props.part.id, payload)
: await api.createPart(payload);
emit("saved", await applyAttachment(api.files.parts, saved, {
file: file.value,
remove: removeFile.value,
}));
} catch (e) {
error.value = e.message;
} finally {
saving.value = false;
}
}
</script>
<template>
<Modal :title="isEdit ? 'Edit part' : 'Add part'" @close="emit('close')">
<p v-if="error" class="mb-3 rounded-control bg-danger-soft px-3 py-2 text-sm font-medium text-danger">{{ error }}</p>
<form class="space-y-3" @submit.prevent="submit">
<div>
<label class="dh-label">Part name *</label>
<input v-model="form.name" required placeholder="Oil Filter" class="dh-input" />
</div>
<div>
<label class="dh-label">Part number</label>
<input v-model="form.partNumber" placeholder="04152-YZZA7" class="dh-input data" />
</div>
<div>
<label class="dh-label">Notes</label>
<input v-model="form.notes" placeholder="Fits 20152020 · buy in pairs" class="dh-input" />
</div>
<AttachmentField v-model:file="file" v-model:remove="removeFile" :record="part" legend="Photo or spec sheet" />
<div class="mt-4 flex justify-end gap-2">
<button type="button" class="dh-btn dh-btn-ghost" @click="emit('close')">Cancel</button>
<button type="submit" :disabled="saving" class="dh-btn dh-btn-primary">
{{ saving ? "Saving…" : isEdit ? "Save changes" : "Add part" }}
</button>
</div>
</form>
</Modal>
</template>