Restructure Web App into server/ + web/ (GsmNode parity)
Reorganize the Web App to match the GsmNode project layout: a Go backend-for-frontend in server/ that embeds the built SPA and reverse-proxies /api/* to the API Server, with the Vue 3 + Vite frontend moved into web/. - Move all frontend files into web/ (history preserved via renames) - Point vite build output at ../server/dist for Go embedding - Add server/ Go BFF (main.go, go.mod, .env.example, Run-WebApp.ps1) - Drop Docker/nginx deploy (Dockerfile, docker-compose.yml, nginx.conf.template, .dockerignore) in favor of the BFF, matching GsmNode - Update .claude/launch.json to run the dev server from web/ - Rewrite README.md for the new layout Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ba3f227361
commit
75a2ccc226
@@ -0,0 +1,173 @@
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { api } from "../api";
|
||||
import Modal from "./Modal.vue";
|
||||
|
||||
const props = defineProps({ car: { type: Object, default: null } });
|
||||
const emit = defineEmits(["saved", "close"]);
|
||||
|
||||
const isEdit = !!props.car;
|
||||
const saving = ref(false);
|
||||
const error = ref("");
|
||||
const form = ref({
|
||||
name: props.car?.name ?? "",
|
||||
make: props.car?.make ?? "",
|
||||
model: props.car?.model ?? "",
|
||||
year: props.car?.year || "",
|
||||
registration: props.car?.registration ?? "",
|
||||
registrationCountry: props.car?.registrationCountry ?? "",
|
||||
vin: props.car?.vin ?? "",
|
||||
fuelType: props.car?.fuelType ?? "",
|
||||
buildDate: props.car?.buildDate ?? "",
|
||||
firstRegistrationDate: props.car?.firstRegistrationDate ?? "",
|
||||
oilSpec: props.car?.oilSpec ?? "",
|
||||
transmissionOilSpec: props.car?.transmissionOilSpec ?? "",
|
||||
differentialOilSpec: props.car?.differentialOilSpec ?? "",
|
||||
brakeFluidSpec: props.car?.brakeFluidSpec ?? "",
|
||||
coolantSpec: props.car?.coolantSpec ?? "",
|
||||
serviceIntervalDays: props.car?.serviceIntervalDays || 365,
|
||||
serviceIntervalKm: props.car?.serviceIntervalKm || 15000,
|
||||
currentKm: props.car?.currentKm || "",
|
||||
});
|
||||
|
||||
async function submit() {
|
||||
saving.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const payload = {
|
||||
name: form.value.name.trim(),
|
||||
make: form.value.make.trim(),
|
||||
model: form.value.model.trim(),
|
||||
year: form.value.year ? Number(form.value.year) : 0,
|
||||
registration: form.value.registration.trim(),
|
||||
registrationCountry: form.value.registrationCountry.trim(),
|
||||
vin: form.value.vin.trim(),
|
||||
fuelType: form.value.fuelType,
|
||||
buildDate: form.value.buildDate,
|
||||
firstRegistrationDate: form.value.firstRegistrationDate,
|
||||
oilSpec: form.value.oilSpec.trim(),
|
||||
transmissionOilSpec: form.value.transmissionOilSpec.trim(),
|
||||
differentialOilSpec: form.value.differentialOilSpec.trim(),
|
||||
brakeFluidSpec: form.value.brakeFluidSpec.trim(),
|
||||
coolantSpec: form.value.coolantSpec.trim(),
|
||||
serviceIntervalDays: Number(form.value.serviceIntervalDays) || 365,
|
||||
serviceIntervalKm: Number(form.value.serviceIntervalKm) || 15000,
|
||||
currentKm: form.value.currentKm ? Number(form.value.currentKm) : 0,
|
||||
};
|
||||
const saved = isEdit
|
||||
? await api.updateCar(props.car.id, payload)
|
||||
: await api.createCar(payload);
|
||||
emit("saved", saved);
|
||||
} catch (e) {
|
||||
error.value = e.message;
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="isEdit ? 'Edit car' : 'Add a car'" @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">Name *</label>
|
||||
<input v-model="form.name" required placeholder="Toyota Yaris" class="dh-input" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div>
|
||||
<label class="dh-label">Make</label>
|
||||
<input v-model="form.make" placeholder="Toyota" class="dh-input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">Model</label>
|
||||
<input v-model="form.model" placeholder="Yaris" class="dh-input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">Year</label>
|
||||
<input v-model="form.year" type="number" placeholder="2015" class="dh-input data" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div>
|
||||
<label class="dh-label">Registration</label>
|
||||
<input v-model="form.registration" placeholder="ABC 1234" class="dh-input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">Registration country</label>
|
||||
<input v-model="form.registrationCountry" placeholder="Poland" class="dh-input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">VIN</label>
|
||||
<input v-model="form.vin" placeholder="Vehicle Identification Number" maxlength="17" class="dh-input data uppercase" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div>
|
||||
<label class="dh-label">Fuel type</label>
|
||||
<select v-model="form.fuelType" class="dh-input">
|
||||
<option value="">—</option>
|
||||
<option value="petrol">Petrol (gasoline)</option>
|
||||
<option value="diesel">Diesel</option>
|
||||
<option value="hybrid">Hybrid</option>
|
||||
<option value="electric">Electric</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">Build date</label>
|
||||
<input v-model="form.buildDate" type="date" class="dh-input data" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">First registration</label>
|
||||
<input v-model="form.firstRegistrationDate" type="date" class="dh-input data" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label class="dh-label">Engine oil spec</label>
|
||||
<input v-model="form.oilSpec" placeholder="0W20" class="dh-input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">Current odometer (km)</label>
|
||||
<input v-model="form.currentKm" type="number" placeholder="270185" class="dh-input data" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label class="dh-label">Transmission oil spec</label>
|
||||
<input v-model="form.transmissionOilSpec" placeholder="Toyota WS" class="dh-input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">Differential oil spec</label>
|
||||
<input v-model="form.differentialOilSpec" placeholder="SAE 75W-90 GL-5" class="dh-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label class="dh-label">Brake fluid spec</label>
|
||||
<input v-model="form.brakeFluidSpec" placeholder="DOT 4" class="dh-input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">Coolant spec</label>
|
||||
<input v-model="form.coolantSpec" placeholder="Toyota Super Long Life Coolant" class="dh-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label class="dh-label">Service interval (days)</label>
|
||||
<input v-model="form.serviceIntervalDays" type="number" class="dh-input data" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label">Service interval (km)</label>
|
||||
<input v-model="form.serviceIntervalKm" type="number" class="dh-input data" />
|
||||
</div>
|
||||
</div>
|
||||
<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 car" }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</template>
|
||||
Reference in New Issue
Block a user