Admin Page work - Still blank/broken
This commit is contained in:
151
frontend/src/features/admin/catalog/catalogSchemas.ts
Normal file
151
frontend/src/features/admin/catalog/catalogSchemas.ts
Normal 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: '' };
|
||||
}
|
||||
};
|
||||
157
frontend/src/features/admin/catalog/catalogShared.ts
Normal file
157
frontend/src/features/admin/catalog/catalogShared.ts
Normal 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 '';
|
||||
};
|
||||
Reference in New Issue
Block a user