use explicit scheduling levels
This commit is contained in:
parent
dfca13936a
commit
8e38b8f7e8
3 changed files with 33 additions and 34 deletions
|
|
@ -51,7 +51,7 @@ export type ScheduleSummary = z.infer<typeof scheduleSummarySchema>
|
||||||
export type ScheduleDetail = z.infer<typeof scheduleDetailSchema>
|
export type ScheduleDetail = z.infer<typeof scheduleDetailSchema>
|
||||||
export type CreateScheduleInput = {
|
export type CreateScheduleInput = {
|
||||||
setId: number
|
setId: number
|
||||||
elementKind: string
|
levelId: number
|
||||||
startDate: string
|
startDate: string
|
||||||
targetDate: string
|
targetDate: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export const setElementNodeSchema = z.object({
|
||||||
id: z.number().int().positive(),
|
id: z.number().int().positive(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
kind: z.string().min(1),
|
kind: z.string().min(1),
|
||||||
|
levelId: z.number().int().positive(),
|
||||||
get children() {
|
get children() {
|
||||||
return z.array(setElementNodeSchema)
|
return z.array(setElementNodeSchema)
|
||||||
},
|
},
|
||||||
|
|
@ -18,6 +19,14 @@ export const setLayoutResponseSchema = z.object({
|
||||||
id: z.number().int().positive(),
|
id: z.number().int().positive(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
}),
|
}),
|
||||||
|
levels: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number().int().positive(),
|
||||||
|
kind: z.string().min(1),
|
||||||
|
depth: z.number().int().nonnegative(),
|
||||||
|
elementCount: z.number().int().nonnegative(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
elements: z.array(setElementNodeSchema),
|
elements: z.array(setElementNodeSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import { z } from 'zod'
|
||||||
|
|
||||||
import AuthenticatedHeader from '@/components/AuthenticatedHeader.vue'
|
import AuthenticatedHeader from '@/components/AuthenticatedHeader.vue'
|
||||||
import { useSchedulesStore } from '@/stores/schedules'
|
import { useSchedulesStore } from '@/stores/schedules'
|
||||||
import { useSetLayoutStore, type SetElementNode } from '@/stores/setLayout'
|
import { useSetLayoutStore } from '@/stores/setLayout'
|
||||||
|
|
||||||
type ScheduleField = 'elementKind' | 'startDate' | 'targetDate'
|
type ScheduleField = 'levelId' | 'startDate' | 'targetDate'
|
||||||
type ScheduleFieldErrors = Partial<Record<ScheduleField, string>>
|
type ScheduleFieldErrors = Partial<Record<ScheduleField, string>>
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
@ -19,34 +19,24 @@ const { layout, loading, error, notFound } = storeToRefs(setLayoutStore)
|
||||||
const { creating, createError } = storeToRefs(schedulesStore)
|
const { creating, createError } = storeToRefs(schedulesStore)
|
||||||
const currentSetId = ref<number | null>(null)
|
const currentSetId = ref<number | null>(null)
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
elementKind: '',
|
levelId: null as number | null,
|
||||||
startDate: '',
|
startDate: '',
|
||||||
targetDate: '',
|
targetDate: '',
|
||||||
})
|
})
|
||||||
const fieldErrors = ref<ScheduleFieldErrors>({})
|
const fieldErrors = ref<ScheduleFieldErrors>({})
|
||||||
|
|
||||||
const levels = computed(() => {
|
const levels = computed(() =>
|
||||||
const counts = new Map<string, number>()
|
(layout.value?.levels ?? [])
|
||||||
|
.filter((level) => level.elementCount > 0)
|
||||||
function countNodes(nodes: SetElementNode[]): void {
|
.map((level) => ({
|
||||||
for (const node of nodes) {
|
...level,
|
||||||
counts.set(node.kind, (counts.get(node.kind) ?? 0) + 1)
|
label: humanizeKind(level.kind),
|
||||||
countNodes(node.children)
|
})),
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
countNodes(layout.value?.elements ?? [])
|
|
||||||
|
|
||||||
return [...counts.entries()].map(([kind, count]) => ({
|
|
||||||
kind,
|
|
||||||
count,
|
|
||||||
label: humanizeKind(kind),
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
const scheduleFormSchema = z
|
const scheduleFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
elementKind: z.string().min(1, 'Choose a level to schedule.'),
|
levelId: z.number('Choose a level to schedule.').int().positive(),
|
||||||
startDate: z.string().min(1, 'Choose a start date.'),
|
startDate: z.string().min(1, 'Choose a start date.'),
|
||||||
targetDate: z.string().min(1, 'Choose a target date.'),
|
targetDate: z.string().min(1, 'Choose a target date.'),
|
||||||
})
|
})
|
||||||
|
|
@ -82,7 +72,7 @@ async function submit(): Promise<void> {
|
||||||
for (const issue of result.error.issues) {
|
for (const issue of result.error.issues) {
|
||||||
const field = issue.path[0]
|
const field = issue.path[0]
|
||||||
if (
|
if (
|
||||||
(field === 'elementKind' || field === 'startDate' || field === 'targetDate') &&
|
(field === 'levelId' || field === 'startDate' || field === 'targetDate') &&
|
||||||
errors[field] === undefined
|
errors[field] === undefined
|
||||||
) {
|
) {
|
||||||
errors[field] = issue.message
|
errors[field] = issue.message
|
||||||
|
|
@ -95,10 +85,10 @@ async function submit(): Promise<void> {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentSetId.value === null ||
|
currentSetId.value === null ||
|
||||||
!levels.value.some((level) => level.kind === result.data.elementKind)
|
!levels.value.some((level) => level.id === result.data.levelId)
|
||||||
) {
|
) {
|
||||||
fieldErrors.value = {
|
fieldErrors.value = {
|
||||||
elementKind: 'Choose an available level to schedule.',
|
levelId: 'Choose an available level to schedule.',
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
@ -166,18 +156,18 @@ function humanizeKind(kind: string): string {
|
||||||
<label for="schedule-level">Level</label>
|
<label for="schedule-level">Level</label>
|
||||||
<select
|
<select
|
||||||
id="schedule-level"
|
id="schedule-level"
|
||||||
v-model="form.elementKind"
|
v-model="form.levelId"
|
||||||
:aria-invalid="fieldErrors.elementKind !== undefined"
|
:aria-invalid="fieldErrors.levelId !== undefined"
|
||||||
:aria-describedby="fieldErrors.elementKind ? 'schedule-level-error' : undefined"
|
:aria-describedby="fieldErrors.levelId ? 'schedule-level-error' : undefined"
|
||||||
:disabled="creating"
|
:disabled="creating"
|
||||||
>
|
>
|
||||||
<option value="">Choose a level</option>
|
<option :value="null">Choose a level</option>
|
||||||
<option v-for="level in levels" :key="level.kind" :value="level.kind">
|
<option v-for="level in levels" :key="level.id" :value="level.id">
|
||||||
{{ level.label }} ({{ level.count }})
|
{{ level.label }} ({{ level.elementCount }})
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<p v-if="fieldErrors.elementKind" id="schedule-level-error" class="field-error">
|
<p v-if="fieldErrors.levelId" id="schedule-level-error" class="field-error">
|
||||||
{{ fieldErrors.elementKind }}
|
{{ fieldErrors.levelId }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue