chore: added sorting icons to vehicles table

This commit is contained in:
2025-11-26 14:40:52 +01:00
parent c2c95d50f7
commit 8b42d2c395
3 changed files with 119 additions and 21 deletions
@@ -1,22 +1,40 @@
<template> <template>
<div class="table-search-box"> <div class="table-search-box">
<input type="text" placeholder="Wyszukaj pojazd..." v-model="vehicleSearchInput" /> <input type="text" placeholder="Wyszukaj pojazd..." v-model="vehicleSearchInput" />
<button @click="addVehicleRow()">DODAJ NOWY POJAZD</button>
<button class="btn--center" @click="clearSearchInput()">
<LucideX :size="18" :stroke-width="4" />
</button>
<button class="btn--center" @click="addVehicleRow()">
<LucidePlus :size="18" />
&nbsp;NOWY POJAZD
</button>
</div>
<div class="table-visible-results-box">
Pokazuj maks.
<input type="number" min="1" v-model="maxVisibleResults" />
wyników
</div> </div>
<div class="table-wrapper"> <div class="table-wrapper">
<table class="vehicle-manager-table"> <table class="vehicle-manager-table">
<thead> <thead>
<tr> <tr>
<td style="width: 50px">#</td> <td
<td style="width: 200px">Nazwa</td> v-for="header in headers"
<td style="width: 150px">Typ</td> :style="`width: ${header.elementWidth}px`"
<td style="width: 150px">Kabina (lok.)</td> @click="sortTableBy(header.id)"
<td style="width: 170px">Grupa</td> :data-sortable="header.sortable"
<td style="width: 100px">Tylko sponsorzy do</td> >
<td style="width: 100px">Tylko zespół</td> <div>
<td style="width: 50px">Ukryty</td> {{ header.title }}
<td style="width: 50px">Usuń</td>
<LucideArrowUp :size="18" v-if="activeSortKey === header.id && activeSortDir == 1" />
<LucideArrowDown :size="18" v-else-if="activeSortKey === header.id" />
</div>
</td>
</tr> </tr>
</thead> </thead>
@@ -76,7 +94,9 @@
{{ row.vehicleRef.hidden ? '' : '' }} {{ row.vehicleRef.hidden ? '' : '' }}
</td> </td>
<td class="editable" @click="removeVehicle(row.vehicleRef.id)"><img src="/icon-trash.svg" alt="remove" /></td> <td class="editable" @click="removeVehicleRow(row.vehicleRef.id)">
<img src="/icon-trash.svg" alt="remove" />
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -86,21 +106,71 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, nextTick, ref, useTemplateRef } from 'vue'; import { computed, nextTick, ref, useTemplateRef } from 'vue';
import { useVehiclesStore } from '../../stores/vehicles.store'; import { useVehiclesStore } from '../../stores/vehicles.store';
import { IVehicleTableRow, VehicleEditRestrictionKey, VehicleEditRowKey } from '../../types/vehicles.types'; import { IVehicle, IVehicleTableRow, VehicleEditRestrictionKey, VehicleEditRowKey } from '../../types/vehicles.types';
import { LucideArrowDown, LucideArrowUp, LucidePlus, LucideX } from 'lucide-vue-next';
interface TableHeader {
id: string;
elementWidth: number;
title: string;
sortable: boolean;
}
const sorterFunctions: Record<string, (v1: IVehicle, v2: IVehicle) => number> = {
id: (v1, v2) => (v1.id - v2.id) * activeSortDir.value,
name: (v1, v2) => v1.name.localeCompare(v2.name) * activeSortDir.value,
cabinName: (v1, v2) => (v1.cabinName || '').localeCompare(v2.cabinName || '') * activeSortDir.value,
type: (v1, v2) => v1.type.localeCompare(v2.type) * activeSortDir.value,
group: (v1, v2) => (v1.group.id - v2.group.id) * activeSortDir.value,
sponsorOnly: (v1, v2) =>
((v1.restrictions?.sponsorOnly || v1.id) - (v2.restrictions?.sponsorOnly || v2.id)) * activeSortDir.value,
teamOnly: (v1, v2) =>
((v1.restrictions?.teamOnly ? 1 : v1.id) - (v2.restrictions?.teamOnly ? 1 : v2.id)) * activeSortDir.value,
hidden: (v1, v2) => (Number(v1.hidden) || v1.id) - (Number(v2.hidden) || v2.id) * activeSortDir.value,
};
const headers: TableHeader[] = [
{ id: 'id', elementWidth: 50, title: '#', sortable: true },
{ id: 'name', elementWidth: 200, title: 'Nazwa', sortable: true },
{ id: 'type', elementWidth: 150, title: 'Typ', sortable: true },
{ id: 'cabinName', elementWidth: 150, title: 'Kabina (lok.)', sortable: true },
{ id: 'group', elementWidth: 170, title: 'Grupa', sortable: true },
{ id: 'sponsorOnly', elementWidth: 130, title: 'Tylko sponsorzy do', sortable: true },
{ id: 'teamOnly', elementWidth: 100, title: 'Tylko zespół', sortable: true },
{ id: 'hidden', elementWidth: 75, title: 'Ukryty', sortable: true },
{ id: 'remove', elementWidth: 75, title: 'Usuń', sortable: false },
];
const vehiclesStore = useVehiclesStore(); const vehiclesStore = useVehiclesStore();
const vehicleSearchInput = ref(''); const vehicleSearchInput = ref('');
const maxVisibleResults = ref(50);
const activeSortKey = ref<string>('id');
const activeSortDir = ref(1);
const currentEditingGroupId = ref(-1); const currentEditingGroupId = ref(-1);
const selectGroup = useTemplateRef('select-group'); const selectGroup = useTemplateRef('select-group');
const vehiclesTableComp = computed(() => { const vehiclesTableComp = computed(() => {
return vehiclesStore.vehiclesTable return vehiclesStore.vehiclesTable
.filter((row) => row.vehicleRef.name.toLowerCase().includes(vehicleSearchInput.value.trim().toLowerCase())) .filter((row) => row.vehicleRef.name.toLowerCase().includes(vehicleSearchInput.value.trim().toLowerCase()))
.sort((r1, r2) => { .sort((v1, v2) => sorterFunctions[activeSortKey.value](v1.vehicleRef, v2.vehicleRef))
return r1.vehicleRef.id - r2.vehicleRef.id; .filter((_, i) => i < maxVisibleResults.value);
});
}); });
function clearSearchInput() {
vehicleSearchInput.value = '';
}
function sortTableBy(id: string) {
if (!(id in sorterFunctions)) return;
if (activeSortKey.value == id) activeSortDir.value *= -1;
else activeSortDir.value = 1;
activeSortKey.value = id;
}
async function editRowPrimitive(row: IVehicleTableRow, editKey: VehicleEditRowKey) { async function editRowPrimitive(row: IVehicleTableRow, editKey: VehicleEditRowKey) {
if (!(editKey in row.vehicleRef)) return; if (!(editKey in row.vehicleRef)) return;
@@ -175,7 +245,6 @@ async function selectRowVehicleGroup(row: IVehicleTableRow) {
async function editVehicleGroup(e: Event, row: IVehicleTableRow) { async function editVehicleGroup(e: Event, row: IVehicleTableRow) {
const id = (e.target as HTMLSelectElement).value; const id = (e.target as HTMLSelectElement).value;
if (row.vehicleRef.group.id !== +id) { if (row.vehicleRef.group.id !== +id) {
const updatedData = await vehiclesStore.updateVehicle(row.vehicleRef.id, 'vehicleGroupId', +id); const updatedData = await vehiclesStore.updateVehicle(row.vehicleRef.id, 'vehicleGroupId', +id);
@@ -210,7 +279,7 @@ async function addVehicleRow() {
} }
} }
async function removeVehicle(id: number) { async function removeVehicleRow(id: number) {
const confirmRemove = confirm('Czy na pewno chcesz usunąć ten pojazd?'); const confirmRemove = confirm('Czy na pewno chcesz usunąć ten pojazd?');
if (!confirmRemove) return; if (!confirmRemove) return;
@@ -222,5 +291,3 @@ async function removeVehicle(id: number) {
} }
} }
</script> </script>
<style></style>
+12 -2
View File
@@ -48,13 +48,17 @@ table thead {
top: 0; top: 0;
} }
table thead td { table thead tr td {
padding: 0.4rem 0.45rem; padding: 0.4rem 0.45rem;
background-color: #151b24; background-color: #151b24;
color: white; color: white;
top: 0; top: 0;
} }
table thead tr td img {
vertical-align: middle;
}
table tbody tr { table tbody tr {
background-color: #2c394b; background-color: #2c394b;
transition: background-color 100ms; transition: background-color 100ms;
@@ -77,7 +81,7 @@ table tbody tr td {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
td img { table tbody tr td img {
height: 1.45em; height: 1.45em;
vertical-align: middle; vertical-align: middle;
} }
@@ -125,6 +129,12 @@ button {
padding: 0; padding: 0;
} }
&.btn--center {
display: flex;
justify-content: center;
align-items: center;
}
&:focus-visible { &:focus-visible {
outline: 1px solid gold; outline: 1px solid gold;
} }
+21
View File
@@ -81,4 +81,25 @@ img.brand-image {
overflow: auto; overflow: auto;
margin-top: 1em; margin-top: 1em;
} }
.table-wrapper table > thead > tr > td {
& > div {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5em;
}
&[data-sortable='true'] {
cursor: pointer;
}
}
.table-visible-results-box {
margin-top: 0.5em;
}
.table-visible-results-box input {
max-width: 70px;
}
</style> </style>