Admin Page work - Still blank/broken

This commit is contained in:
Eric Gullickson
2025-11-06 16:29:11 -06:00
parent 858cf31d38
commit 5630979adf
38 changed files with 7373 additions and 924 deletions

View File

@@ -0,0 +1,151 @@
import { z } from 'zod';
import {
CatalogLevel,
CatalogRow,
CatalogSelectionContext,
} from './catalogShared';
import {
CatalogMake,
CatalogModel,
CatalogYear,
CatalogTrim,
CatalogEngine,
} from '../types/admin.types';
export type CatalogFormValues = {
name?: string;
makeId?: string;
modelId?: string;
year?: number;
yearId?: string;
trimId?: string;
displacement?: string;
cylinders?: number;
fuel_type?: string;
};
export const makeSchema = z.object({
name: z.string().min(1, 'Name is required'),
});
export const modelSchema = z.object({
name: z.string().min(1, 'Name is required'),
makeId: z.string().min(1, 'Select a make'),
});
export const yearSchema = z.object({
modelId: z.string().min(1, 'Select a model'),
year: z
.coerce.number()
.int()
.min(1900, 'Enter a valid year')
.max(2100, 'Enter a valid year'),
});
export const trimSchema = z.object({
name: z.string().min(1, 'Name is required'),
yearId: z.string().min(1, 'Select a year'),
});
export const engineSchema = z.object({
name: z.string().min(1, 'Name is required'),
trimId: z.string().min(1, 'Select a trim'),
displacement: z.string().optional(),
cylinders: z
.preprocess(
(value) =>
value === '' || value === null || value === undefined
? undefined
: Number(value),
z
.number()
.int()
.positive('Cylinders must be positive')
.optional()
),
fuel_type: z.string().optional(),
});
export const getSchemaForLevel = (level: CatalogLevel) => {
switch (level) {
case 'makes':
return makeSchema;
case 'models':
return modelSchema;
case 'years':
return yearSchema;
case 'trims':
return trimSchema;
case 'engines':
return engineSchema;
default:
return makeSchema;
}
};
export const buildDefaultValues = (
level: CatalogLevel,
mode: 'create' | 'edit',
entity: CatalogRow | undefined,
context: CatalogSelectionContext
): CatalogFormValues => {
if (mode === 'edit' && entity) {
switch (level) {
case 'makes':
return { name: (entity as CatalogMake).name };
case 'models':
return {
name: (entity as CatalogModel).name,
makeId: (entity as CatalogModel).makeId,
};
case 'years':
return {
modelId: (entity as CatalogYear).modelId,
year: (entity as CatalogYear).year,
};
case 'trims':
return {
name: (entity as CatalogTrim).name,
yearId: (entity as CatalogTrim).yearId,
};
case 'engines':
return {
name: (entity as CatalogEngine).name,
trimId: (entity as CatalogEngine).trimId,
displacement: (entity as CatalogEngine).displacement ?? undefined,
cylinders: (entity as CatalogEngine).cylinders ?? undefined,
fuel_type: (entity as CatalogEngine).fuel_type ?? undefined,
};
default:
return {};
}
}
switch (level) {
case 'models':
return {
name: '',
makeId: context.make?.id ?? '',
};
case 'years':
return {
modelId: context.model?.id ?? '',
year: undefined,
};
case 'trims':
return {
name: '',
yearId: context.year?.id ?? '',
};
case 'engines':
return {
name: '',
trimId: context.trim?.id ?? '',
displacement: '',
fuel_type: '',
};
case 'makes':
default:
return { name: '' };
}
};

View File

@@ -0,0 +1,157 @@
import {
CatalogEngine,
CatalogMake,
CatalogModel,
CatalogTrim,
CatalogYear,
} from '../types/admin.types';
export type CatalogLevel = 'makes' | 'models' | 'years' | 'trims' | 'engines';
export type CatalogRow =
| CatalogMake
| CatalogModel
| CatalogYear
| CatalogTrim
| CatalogEngine;
export interface CatalogSelectionContext {
level: CatalogLevel;
make?: CatalogMake;
model?: CatalogModel;
year?: CatalogYear;
trim?: CatalogTrim;
}
export const LEVEL_LABEL: Record<CatalogLevel, string> = {
makes: 'Makes',
models: 'Models',
years: 'Years',
trims: 'Trims',
engines: 'Engines',
};
export const LEVEL_SINGULAR_LABEL: Record<CatalogLevel, string> = {
makes: 'Make',
models: 'Model',
years: 'Year',
trims: 'Trim',
engines: 'Engine',
};
export const NEXT_LEVEL: Record<CatalogLevel, CatalogLevel | null> = {
makes: 'models',
models: 'years',
years: 'trims',
trims: 'engines',
engines: null,
};
export const pluralize = (count: number, singular: string): string =>
`${count} ${singular}${count === 1 ? '' : 's'}`;
export const getCascadeSummary = (
level: CatalogLevel,
selectedItems: CatalogRow[],
modelsByMake: Map<string, CatalogModel[]>,
yearsByModel: Map<string, CatalogYear[]>,
trimsByYear: Map<string, CatalogTrim[]>,
enginesByTrim: Map<string, CatalogEngine[]>
): string => {
if (selectedItems.length === 0) {
return '';
}
if (level === 'engines') {
return 'Deleting engines will remove their configuration details.';
}
let modelCount = 0;
let yearCount = 0;
let trimCount = 0;
let engineCount = 0;
if (level === 'makes') {
selectedItems.forEach((item) => {
const make = item as CatalogMake;
const makeModels = modelsByMake.get(make.id) ?? [];
modelCount += makeModels.length;
makeModels.forEach((model) => {
const modelYears = yearsByModel.get(model.id) ?? [];
yearCount += modelYears.length;
modelYears.forEach((year) => {
const yearTrims = trimsByYear.get(year.id) ?? [];
trimCount += yearTrims.length;
yearTrims.forEach((trim) => {
const trimEngines = enginesByTrim.get(trim.id) ?? [];
engineCount += trimEngines.length;
});
});
});
});
return `Deleting ${selectedItems.length} ${LEVEL_LABEL.makes.toLowerCase()} will also remove ${pluralize(
modelCount,
'model'
)}, ${pluralize(yearCount, 'year')}, ${pluralize(
trimCount,
'trim'
)}, and ${pluralize(engineCount, 'engine')}.`;
}
if (level === 'models') {
selectedItems.forEach((item) => {
const model = item as CatalogModel;
const modelYears = yearsByModel.get(model.id) ?? [];
yearCount += modelYears.length;
modelYears.forEach((year) => {
const yearTrims = trimsByYear.get(year.id) ?? [];
trimCount += yearTrims.length;
yearTrims.forEach((trim) => {
const trimEngines = enginesByTrim.get(trim.id) ?? [];
engineCount += trimEngines.length;
});
});
});
return `Deleting ${selectedItems.length} ${LEVEL_LABEL.models.toLowerCase()} will also remove ${pluralize(
yearCount,
'year'
)}, ${pluralize(trimCount, 'trim')}, and ${pluralize(
engineCount,
'engine'
)}.`;
}
if (level === 'years') {
selectedItems.forEach((item) => {
const year = item as CatalogYear;
const yearTrims = trimsByYear.get(year.id) ?? [];
trimCount += yearTrims.length;
yearTrims.forEach((trim) => {
const trimEngines = enginesByTrim.get(trim.id) ?? [];
engineCount += trimEngines.length;
});
});
return `Deleting ${selectedItems.length} ${LEVEL_LABEL.years.toLowerCase()} will also remove ${pluralize(
trimCount,
'trim'
)} and ${pluralize(engineCount, 'engine')}.`;
}
if (level === 'trims') {
selectedItems.forEach((item) => {
const trim = item as CatalogTrim;
const trimEngines = enginesByTrim.get(trim.id) ?? [];
engineCount += trimEngines.length;
});
return `Deleting ${selectedItems.length} ${LEVEL_LABEL.trims.toLowerCase()} will also remove ${pluralize(
engineCount,
'engine'
)}.`;
}
return '';
};