add sparse placement controls
This commit is contained in:
parent
6f2c49026a
commit
becd7573c6
3 changed files with 78 additions and 17 deletions
|
|
@ -6,7 +6,7 @@ import { API_BASE } from '@/utils/apiBase'
|
|||
|
||||
const isoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
|
||||
|
||||
export const workloadPlacementSchema = z.enum(['start', 'middle', 'end'])
|
||||
export const workloadPlacementSchema = z.enum(['spread', 'start', 'middle', 'end'])
|
||||
|
||||
export const scheduleSummarySchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import { useSetLayoutStore } from '@/stores/setLayout'
|
|||
|
||||
type ScheduleField = 'levelId' | 'startDate' | 'targetDate'
|
||||
type ScheduleFieldErrors = Partial<Record<ScheduleField, string>>
|
||||
type PackedWorkloadPlacement = Exclude<WorkloadPlacement, 'spread'>
|
||||
type PlacementContext = 'unavailable' | 'sparse' | 'heavy' | 'balanced'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
|
@ -26,43 +28,84 @@ const form = reactive({
|
|||
levelId: null as number | null,
|
||||
startDate: '',
|
||||
targetDate: '',
|
||||
workloadPlacement: 'middle' as WorkloadPlacement,
|
||||
})
|
||||
const sparseWorkloadPlacement = ref<WorkloadPlacement>('spread')
|
||||
const heavyWorkloadPlacement = ref<PackedWorkloadPlacement>('middle')
|
||||
const fieldErrors = ref<ScheduleFieldErrors>({})
|
||||
|
||||
const levels = computed(() =>
|
||||
(layout.value?.levels ?? []).filter((level) => level.elementCount > 0),
|
||||
)
|
||||
const selectedLevel = computed(() => levels.value.find((level) => level.id === form.levelId))
|
||||
const workloadPlacementOptions: Array<{
|
||||
value: WorkloadPlacement
|
||||
const packedWorkloadPlacementOptions: Array<{
|
||||
value: PackedWorkloadPlacement
|
||||
label: string
|
||||
}> = [
|
||||
{ value: 'start', label: 'At the start' },
|
||||
{ value: 'middle', label: 'In the middle' },
|
||||
{ value: 'end', label: 'At the end' },
|
||||
]
|
||||
const showWorkloadPlacement = computed(() => {
|
||||
const placementContext = computed<PlacementContext>(() => {
|
||||
if (
|
||||
selectedLevel.value === undefined ||
|
||||
form.startDate === '' ||
|
||||
form.targetDate === '' ||
|
||||
form.targetDate < form.startDate
|
||||
) {
|
||||
return false
|
||||
return 'unavailable'
|
||||
}
|
||||
|
||||
const startTime = Date.parse(`${form.startDate}T00:00:00Z`)
|
||||
const targetTime = Date.parse(`${form.targetDate}T00:00:00Z`)
|
||||
if (Number.isNaN(startTime) || Number.isNaN(targetTime)) {
|
||||
return false
|
||||
return 'unavailable'
|
||||
}
|
||||
|
||||
const millisecondsPerDay = 24 * 60 * 60 * 1000
|
||||
const dayCount = Math.round((targetTime - startTime) / millisecondsPerDay) + 1
|
||||
const assignmentCount = selectedLevel.value.elementCount
|
||||
|
||||
return assignmentCount > dayCount && assignmentCount % dayCount !== 0
|
||||
if (assignmentCount < dayCount) {
|
||||
return 'sparse'
|
||||
}
|
||||
|
||||
if (assignmentCount > dayCount && assignmentCount % dayCount !== 0) {
|
||||
return 'heavy'
|
||||
}
|
||||
|
||||
return 'balanced'
|
||||
})
|
||||
const showWorkloadPlacement = computed(
|
||||
() => placementContext.value === 'sparse' || placementContext.value === 'heavy',
|
||||
)
|
||||
const workloadPlacementOptions = computed<Array<{ value: WorkloadPlacement; label: string }>>(
|
||||
() => {
|
||||
if (placementContext.value === 'sparse') {
|
||||
return [{ value: 'spread', label: 'Spread evenly' }, ...packedWorkloadPlacementOptions]
|
||||
}
|
||||
|
||||
return packedWorkloadPlacementOptions
|
||||
},
|
||||
)
|
||||
const selectedWorkloadPlacement = computed<WorkloadPlacement>({
|
||||
get() {
|
||||
if (placementContext.value === 'sparse') {
|
||||
return sparseWorkloadPlacement.value
|
||||
}
|
||||
|
||||
return heavyWorkloadPlacement.value
|
||||
},
|
||||
set(value) {
|
||||
if (placementContext.value === 'sparse') {
|
||||
sparseWorkloadPlacement.value = value
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (value !== 'spread') {
|
||||
heavyWorkloadPlacement.value = value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const scheduleFormSchema = z
|
||||
|
|
@ -98,7 +141,10 @@ watch(
|
|||
|
||||
async function submit(): Promise<void> {
|
||||
fieldErrors.value = {}
|
||||
const result = scheduleFormSchema.safeParse(form)
|
||||
const result = scheduleFormSchema.safeParse({
|
||||
...form,
|
||||
workloadPlacement: selectedWorkloadPlacement.value,
|
||||
})
|
||||
if (!result.success) {
|
||||
const errors: ScheduleFieldErrors = {}
|
||||
for (const issue of result.error.issues) {
|
||||
|
|
@ -237,18 +283,28 @@ async function retry(): Promise<void> {
|
|||
data-workload-placement
|
||||
:disabled="creating"
|
||||
>
|
||||
<legend>Heavier days</legend>
|
||||
<p>
|
||||
<legend>
|
||||
{{ placementContext === 'sparse' ? 'Assignment days' : 'Heavier days' }}
|
||||
</legend>
|
||||
<p v-if="placementContext === 'sparse'">
|
||||
Spread assignments across the schedule or keep them on consecutive days.
|
||||
</p>
|
||||
<p v-else>
|
||||
Some days need one extra assignment. Choose where those days appear in the schedule.
|
||||
</p>
|
||||
<div class="workload-placement__options">
|
||||
<div
|
||||
class="workload-placement__options"
|
||||
:class="{
|
||||
'workload-placement__options--sparse': placementContext === 'sparse',
|
||||
}"
|
||||
>
|
||||
<label
|
||||
v-for="option in workloadPlacementOptions"
|
||||
:key="option.value"
|
||||
class="workload-placement__option"
|
||||
>
|
||||
<input
|
||||
v-model="form.workloadPlacement"
|
||||
v-model="selectedWorkloadPlacement"
|
||||
type="radio"
|
||||
name="workloadPlacement"
|
||||
:value="option.value"
|
||||
|
|
@ -381,6 +437,10 @@ h1 {
|
|||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
.workload-placement__options--sparse {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.workload-placement__option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue