Organise edit page into tabs

Use tabs for the various sections on the edit page so that the schedule
timetable is more easily visible at the same time as the editable tables.
This commit is contained in:
Hornwitser 2025-06-18 00:23:43 +02:00
parent 7a95d6c3c4
commit 6ef3800a53
4 changed files with 155 additions and 53 deletions

View file

@ -1,6 +1,5 @@
<template>
<div>
<Timetable :schedule :eventSlotFilter :shiftSlotFilter />
<table>
<thead>
<tr>
@ -205,7 +204,6 @@ const props = defineProps<{
edit?: boolean,
locationId?: number,
eventSlotFilter?: (slot: ClientScheduleEventSlot) => boolean,
shiftSlotFilter?: (slot: ClientScheduleShiftSlot) => boolean,
}>();
interface EventSlot {

View file

@ -1,6 +1,5 @@
<template>
<div>
<Timetable :schedule :eventSlotFilter :shiftSlotFilter />
<table>
<thead>
<tr>
@ -204,8 +203,6 @@ import type { Id } from '~/shared/types/common';
const props = defineProps<{
edit?: boolean,
roleId?: Id,
eventSlotFilter?: (slot: ClientScheduleEventSlot) => boolean,
shiftSlotFilter?: (slot: ClientScheduleShiftSlot) => boolean,
}>();
interface ShiftSlot {

86
components/Tabs.vue Normal file
View file

@ -0,0 +1,86 @@
<template>
<section class="tabs">
<nav>
<div
v-for="tab in tabs"
class="tab"
:class="{ active: tab.id === activeTab }"
>
<div class="flap">
<h2>
<NuxtLink
:to="{ ...route, query: { ...route.query, tab: tab.id }}"
>
{{ tab.title }}
</NuxtLink>
</h2>
</div>
<div class="spacer"></div>
</div>
</nav>
<slot :name="activeTab">No content for {{ activeTab }}</slot>
</section>
</template>
<script lang="ts" setup>
const props = defineProps<{
tabs: { id: string, title: string }[],
default: string,
}>();
const route = useRoute();
const activeTab = computed({
get: () => queryToString(route.query.tab ?? props.default),
set: (value: string | undefined) => navigateTo({
path: route.path,
query: {
...route.query,
tab: value,
},
}),
});
</script>
<style scoped>
nav {
margin-block: 1rem 0.5rem;
display: flex;
flex-wrap: wrap;
row-gap: 0.5rem;
}
.tab {
display: flex;
flex-wrap: wrap;
}
.tab.active {
padding-block-start: 1px;
}
.tab:last-child {
flex-grow: 1;
}
.tab .spacer {
flex: 1 0 0.75rem;
}
.tab .flap,
.tab .spacer {
border-block-end: 1px solid color-mix(in srgb, CanvasText, Canvas 20%);
}
.tab.active .flap {
border-block-end: none;
}
.tab:not(.active) .flap h2 {
opacity: 0.7;
}
h2 {
border: 1px solid color-mix(in srgb, CanvasText, Canvas 20%);
border-start-start-radius: 0.4rem;
border-start-end-radius: 0.4rem;
border-block-end: none;
padding-inline: 0.5rem;
margin-block: 0;
font-size: 1.2rem;
padding-block: 0.3rem 0.1rem;
}
</style>

View file

@ -18,54 +18,66 @@
>{{ account.name }}</option>
</select>
</label>
<h2>Locations</h2>
<TableScheduleLocations :edit="accountStore.canEditPublic" />
<h2>Schedule</h2>
<label>
Location Filter:
<select
v-model="locationFilter"
>
<option
:value="undefined"
:selected="locationFilter === undefined"
>&lt;All locations&gt;</option>
<option
v-for="location in schedule.locations.values()"
:key="location.id"
:value="location.id"
:disabled="location.deleted"
:selected="locationFilter === location.id"
>{{ location.name }}</option>
</select>
</label>
<TableScheduleEventSlots :edit="true" :locationId="locationFilter" :eventSlotFilter :shiftSlotFilter />
<h2>Events</h2>
<TableScheduleEvents :edit="true" />
<h2>Roles</h2>
<TableScheduleRoles :edit="true" />
<h2>Shift Schedule</h2>
<label>
Role Filter:
<select
v-model="roleFilter"
>
<option
:value="undefined"
:selected="roleFilter === undefined"
>&lt;All roles&gt;</option>
<option
v-for="role in schedule.roles.values()"
:key="role.id"
:value="role.id"
:disabled="role.deleted"
:selected="roleFilter === role.id"
>{{ role.name }}</option>
</select>
</label>
<TableScheduleShiftSlots :edit="true" :roleId="roleFilter" :eventSlotFilter :shiftSlotFilter />
<h2>Shifts</h2>
<TableScheduleShifts :edit="true" :roleId="roleFilter" />
<Timetable :schedule :eventSlotFilter :shiftSlotFilter />
<Tabs
:tabs
default="locations"
>
<template #locations>
<TableScheduleLocations :edit="accountStore.canEditPublic" />
</template>
<template #events>
<TableScheduleEvents :edit="true" />
</template>
<template #eventSlots>
<label>
Location Filter:
<select
v-model="locationFilter"
>
<option
:value="undefined"
:selected="locationFilter === undefined"
>&lt;All locations&gt;</option>
<option
v-for="location in schedule.locations.values()"
:key="location.id"
:value="location.id"
:disabled="location.deleted"
:selected="locationFilter === location.id"
>{{ location.name }}</option>
</select>
</label>
<TableScheduleEventSlots :edit="true" :locationId="locationFilter" :eventSlotFilter />
</template>
<template #roles>
<TableScheduleRoles :edit="true" />
</template>
<template #shifts>
<TableScheduleShifts :edit="true" :roleId="roleFilter" />
</template>
<template #shiftSlots>
<label>
Role Filter:
<select
v-model="roleFilter"
>
<option
:value="undefined"
:selected="roleFilter === undefined"
>&lt;All roles&gt;</option>
<option
v-for="role in schedule.roles.values()"
:key="role.id"
:value="role.id"
:disabled="role.deleted"
:selected="roleFilter === role.id"
>{{ role.name }}</option>
</select>
</label>
<TableScheduleShiftSlots :edit="true" :roleId="roleFilter" />
</template>
</Tabs>
<p v-if="schedule.modified">
Changes are not saved yet.
<button
@ -82,6 +94,15 @@ definePageMeta({
allowedAccountTypes: ["crew", "admin"],
});
const tabs = [
{ id: "locations", title: "Locations" },
{ id: "events", title: "Events" },
{ id: "eventSlots", title: "Event Slots" },
{ id: "roles", title: "Roles" },
{ id: "shifts", title: "Shifts" },
{ id: "shiftSlots", title: "Shifts Slots" },
];
const schedule = await useSchedule();
const { data: accounts } = await useAccounts();
const accountStore = useAccountStore();