Add class-specific driver training profiles and metadata
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { getDriverProfileGroups, getDriverProfileId } from "./driver_profile_logic.js";
|
||||
|
||||
function getScheduleDriftSummary(state) {
|
||||
const scheduledSessions = state.sessions
|
||||
.filter((session) => Number(session.startedAt || 0) > 0)
|
||||
@@ -373,9 +375,24 @@ export function renderDriversView(deps) {
|
||||
const classOptions = state.classes.map((c) => `<option value="${c.id}">${escapeHtml(c.name)}</option>`).join("");
|
||||
const driverSearch = driverBrandFilter().trim().toLowerCase();
|
||||
const filteredDrivers = state.drivers.filter((driver) =>
|
||||
!driverSearch || [driver.name, driver.speechName, driver.transponder, driver.brand].map((value) => String(value || "").toLowerCase()).some((value) => value.includes(driverSearch))
|
||||
!driverSearch || [driver.name, driver.speechName, driver.transponder, driver.brand, driver.club, driver.country].map((value) => String(value || "").toLowerCase()).some((value) => value.includes(driverSearch))
|
||||
);
|
||||
const editingDriver = state.drivers.find((driver) => driver.id === selectedDriverEditId()) || null;
|
||||
const profileGroups = getDriverProfileGroups(state);
|
||||
const profileOptions = profileGroups
|
||||
.map((profile) => {
|
||||
const profileName = profile.representative?.name || t("common.unknown_driver");
|
||||
const classNames = profile.classIds.map((classId) => getClassName(classId)).filter(Boolean).join(" / ");
|
||||
const label = classNames ? `${profileName} - ${classNames}` : profileName;
|
||||
return `<option value="${escapeHtml(profile.id)}">${escapeHtml(label)}</option>`;
|
||||
})
|
||||
.join("");
|
||||
const editingProfileId = editingDriver ? getDriverProfileId(editingDriver) : "";
|
||||
const existingClubs = [...new Set(state.drivers.map((driver) => String(driver.club || "").trim()).filter(Boolean))].sort();
|
||||
const existingCountries = [...new Set([
|
||||
"Sverige", "Norge", "Finland", "Danmark", "Island", "Tyskland", "Nederländerna", "Belgien", "Frankrike", "Storbritannien", "Polen", "Tjeckien", "Österrike", "Schweiz",
|
||||
...state.drivers.map((driver) => String(driver.country || "").trim()).filter(Boolean),
|
||||
])].sort();
|
||||
|
||||
dom.view.innerHTML = `
|
||||
<section class="panel">
|
||||
@@ -386,6 +403,15 @@ export function renderDriversView(deps) {
|
||||
<input name="brand" placeholder="${t("drivers.brand_placeholder")}" />
|
||||
<input name="transponder" placeholder="${t("drivers.transponder_placeholder")}" />
|
||||
<input name="speechName" placeholder="${t("drivers.speech_name_placeholder")}" title="${t("drivers.speech_name_hint")}" />
|
||||
<input name="club" list="driverClubOptions" placeholder="${t("drivers.club_placeholder")}" />
|
||||
<input name="country" list="driverCountryOptions" placeholder="${t("drivers.country_placeholder")}" />
|
||||
<label>${t("drivers.training_profile")}<select name="profileId" title="${t("drivers.training_profile_hint")}">
|
||||
<option value="">${t("drivers.training_profile_auto")}</option>
|
||||
${profileOptions}
|
||||
<option value="__new__">${t("drivers.training_profile_new")}</option>
|
||||
</select></label>
|
||||
<datalist id="driverClubOptions">${existingClubs.map((club) => `<option value="${escapeHtml(club)}"></option>`).join("")}</datalist>
|
||||
<datalist id="driverCountryOptions">${existingCountries.map((country) => `<option value="${escapeHtml(country)}"></option>`).join("")}</datalist>
|
||||
<button class="btn btn-primary" type="submit">${t("drivers.add")}</button>
|
||||
</form>
|
||||
</section>
|
||||
@@ -397,21 +423,27 @@ export function renderDriversView(deps) {
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
${renderTable(
|
||||
[t("table.name"), t("table.class"), t("table.brand"), t("table.transponder"), t("drivers.speech_name"), t("events.actions")],
|
||||
[t("table.name"), t("table.class"), t("drivers.club"), t("drivers.country"), t("table.brand"), t("table.transponder"), t("drivers.training_profile"), t("drivers.speech_name"), t("events.actions")],
|
||||
filteredDrivers.map(
|
||||
(d) => `
|
||||
(d) => {
|
||||
const profile = profileGroups.find((item) => item.id === getDriverProfileId(d));
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(d.name)}</td>
|
||||
<td>${escapeHtml(getClassName(d.classId))}</td>
|
||||
<td>${escapeHtml(profile?.club || d.club || "-")}</td>
|
||||
<td>${escapeHtml(profile?.country || d.country || "-")}</td>
|
||||
<td>${escapeHtml(d.brand || "-")}</td>
|
||||
<td>${escapeHtml(d.transponder || "-")}</td>
|
||||
<td>${escapeHtml(profile?.representative?.name || d.name)}${profile?.drivers.length > 1 ? ` · ${profile.drivers.length}` : ""}</td>
|
||||
<td>${escapeHtml(d.speechName || d.name)}</td>
|
||||
<td class="actions-inline">
|
||||
<button id="driver-edit-${d.id}" class="btn">${t("common.edit")}</button>
|
||||
<button id="driver-delete-${d.id}" class="btn btn-danger">${t("common.delete")}</button>
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
`;
|
||||
}
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
@@ -436,6 +468,16 @@ export function renderDriversView(deps) {
|
||||
<input name="brand" value="${escapeHtml(editingDriver.brand || "")}" placeholder="${t("drivers.brand_placeholder")}" />
|
||||
<input name="transponder" value="${escapeHtml(editingDriver.transponder || "")}" placeholder="${t("drivers.transponder_placeholder")}" />
|
||||
<input name="speechName" value="${escapeHtml(editingDriver.speechName || "")}" placeholder="${t("drivers.speech_name_placeholder")}" title="${t("drivers.speech_name_hint")}" />
|
||||
<input name="club" list="driverClubOptions" value="${escapeHtml(profileGroups.find((profile) => profile.id === editingProfileId)?.club || editingDriver.club || "")}" placeholder="${t("drivers.club_placeholder")}" />
|
||||
<input name="country" list="driverCountryOptions" value="${escapeHtml(profileGroups.find((profile) => profile.id === editingProfileId)?.country || editingDriver.country || "")}" placeholder="${t("drivers.country_placeholder")}" />
|
||||
<label>${t("drivers.training_profile")}<select name="profileId" title="${t("drivers.training_profile_hint")}">
|
||||
${profileGroups.map((profile) => {
|
||||
const profileName = profile.representative?.name || t("common.unknown_driver");
|
||||
const classNames = profile.classIds.map((classId) => getClassName(classId)).filter(Boolean).join(" / ");
|
||||
return `<option value="${escapeHtml(profile.id)}" ${profile.id === editingProfileId ? "selected" : ""}>${escapeHtml(classNames ? `${profileName} - ${classNames}` : profileName)}</option>`;
|
||||
}).join("")}
|
||||
<option value="__new__">${t("drivers.training_profile_separate")}</option>
|
||||
</select></label>
|
||||
<p class="form-error" id="driverEditError" hidden></p>
|
||||
<div class="actions-inline">
|
||||
<button class="btn btn-primary" id="driverEditSave" type="button">${t("common.save")}</button>
|
||||
@@ -452,14 +494,29 @@ export function renderDriversView(deps) {
|
||||
document.getElementById("driverForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
const driverId = uid("driver");
|
||||
const driverName = String(form.get("name") || "").trim();
|
||||
const requestedProfileId = String(form.get("profileId") || "").trim();
|
||||
const sameNameProfile = profileGroups.find((profile) => (
|
||||
String(profile.representative?.name || "").trim().toLocaleLowerCase().replace(/\s+/g, " ") === driverName.toLocaleLowerCase().replace(/\s+/g, " ")
|
||||
));
|
||||
const profileId = requestedProfileId && requestedProfileId !== "__new__"
|
||||
? requestedProfileId
|
||||
: requestedProfileId === "__new__"
|
||||
? uid("profile")
|
||||
: sameNameProfile?.id || uid("profile");
|
||||
const linkedProfile = profileGroups.find((profile) => profile.id === profileId);
|
||||
state.drivers.push(
|
||||
normalizeDriver({
|
||||
id: uid("driver"),
|
||||
name: String(form.get("name")).trim(),
|
||||
id: driverId,
|
||||
name: driverName,
|
||||
classId: String(form.get("classId")),
|
||||
brand: String(form.get("brand") || "").trim(),
|
||||
transponder: String(form.get("transponder") || "").trim(),
|
||||
speechName: String(form.get("speechName") || "").trim(),
|
||||
profileId,
|
||||
club: String(form.get("club") || linkedProfile?.club || "").trim(),
|
||||
country: String(form.get("country") || linkedProfile?.country || "").trim(),
|
||||
})
|
||||
);
|
||||
saveState();
|
||||
@@ -475,6 +532,21 @@ export function renderDriversView(deps) {
|
||||
renderDriversView(deps);
|
||||
});
|
||||
|
||||
const bindProfileMetadataFill = (formId) => {
|
||||
const formNode = document.getElementById(formId);
|
||||
const profileSelect = formNode?.querySelector('select[name="profileId"]');
|
||||
profileSelect?.addEventListener("change", () => {
|
||||
const profile = profileGroups.find((item) => item.id === profileSelect.value);
|
||||
if (!profile) return;
|
||||
const clubInput = formNode.querySelector('input[name="club"]');
|
||||
const countryInput = formNode.querySelector('input[name="country"]');
|
||||
if (clubInput instanceof HTMLInputElement) clubInput.value = profile.club || "";
|
||||
if (countryInput instanceof HTMLInputElement) countryInput.value = profile.country || "";
|
||||
});
|
||||
};
|
||||
bindProfileMetadataFill("driverForm");
|
||||
bindProfileMetadataFill("driverEditForm");
|
||||
|
||||
state.drivers.forEach((d) => {
|
||||
document.getElementById(`driver-edit-${d.id}`)?.addEventListener("click", () => {
|
||||
setSelectedDriverEditId(d.id);
|
||||
@@ -527,6 +599,10 @@ export function renderDriversView(deps) {
|
||||
const cleanedBrand = String(form.get("brand") || "").trim();
|
||||
const cleanedTp = String(form.get("transponder") || "").trim();
|
||||
const cleanedSpeechName = String(form.get("speechName") || "").trim();
|
||||
const requestedProfileId = String(form.get("profileId") || "").trim();
|
||||
const cleanedProfileId = requestedProfileId === "__new__" ? uid("profile") : requestedProfileId || editingProfileId;
|
||||
const cleanedClub = String(form.get("club") || "").trim();
|
||||
const cleanedCountry = String(form.get("country") || "").trim();
|
||||
if (!cleanedName) {
|
||||
setFormError("driverEditError", t("validation.required_name"));
|
||||
return;
|
||||
@@ -536,18 +612,25 @@ export function renderDriversView(deps) {
|
||||
return;
|
||||
}
|
||||
setFormError("driverEditError", "");
|
||||
state.drivers = state.drivers.map((item) =>
|
||||
item.id === editingDriver.id
|
||||
? normalizeDriver({
|
||||
state.drivers = state.drivers.map((item) => {
|
||||
if (item.id === editingDriver.id) {
|
||||
return normalizeDriver({
|
||||
...item,
|
||||
name: cleanedName,
|
||||
classId: cleanedClassId || item.classId,
|
||||
brand: cleanedBrand,
|
||||
transponder: cleanedTp,
|
||||
speechName: cleanedSpeechName,
|
||||
})
|
||||
: item
|
||||
);
|
||||
profileId: cleanedProfileId,
|
||||
club: cleanedClub,
|
||||
country: cleanedCountry,
|
||||
});
|
||||
}
|
||||
if (getDriverProfileId(item) === cleanedProfileId) {
|
||||
return normalizeDriver({ ...item, club: cleanedClub, country: cleanedCountry });
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setSelectedDriverEditId(null);
|
||||
saveState();
|
||||
renderView();
|
||||
|
||||
Reference in New Issue
Block a user