feat(planning): grille hebdomadaire complète avec API et filtres

- Connexion API via proxy Angular (résolution CORS, base path /api)
- Import CSS ng-zorro global pour les modales et composants
- Filtres Camion/Show câblés sur l'affichage de la grille
- Camions affichés via TrucksService (linkés au show du même créneau)
- Panneau de détails : spectacles + camions du jour sélectionné
- Modale de création de spectacle stylisée avec fond et centrage
- Positionnement précis des events à la minute dans leur créneau
- Auto-scroll vers l'heure courante au chargement
- Ligne "maintenant" sur la colonne du jour actuel
- Régénération des services OpenAPI (nouveaux noms de types)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:36:03 +02:00
parent 150b97cd2e
commit 654b297e2e
3131 changed files with 149304 additions and 104334 deletions
+120 -149
View File
@@ -79,9 +79,7 @@ export class JSONSchemaGenerator {
else if (regexes.length > 1) {
result.schema.allOf = [
...regexes.map((regex) => ({
...(this.target === "draft-7" || this.target === "draft-4" || this.target === "openapi-3.0"
? { type: "string" }
: {}),
...(this.target === "draft-7" ? { type: "string" } : {}),
pattern: regex.source,
})),
];
@@ -96,36 +94,22 @@ export class JSONSchemaGenerator {
json.type = "integer";
else
json.type = "number";
if (typeof exclusiveMinimum === "number") {
if (this.target === "draft-4" || this.target === "openapi-3.0") {
json.minimum = exclusiveMinimum;
json.exclusiveMinimum = true;
}
else {
json.exclusiveMinimum = exclusiveMinimum;
}
}
if (typeof exclusiveMinimum === "number")
json.exclusiveMinimum = exclusiveMinimum;
if (typeof minimum === "number") {
json.minimum = minimum;
if (typeof exclusiveMinimum === "number" && this.target !== "draft-4") {
if (typeof exclusiveMinimum === "number") {
if (exclusiveMinimum >= minimum)
delete json.minimum;
else
delete json.exclusiveMinimum;
}
}
if (typeof exclusiveMaximum === "number") {
if (this.target === "draft-4" || this.target === "openapi-3.0") {
json.maximum = exclusiveMaximum;
json.exclusiveMaximum = true;
}
else {
json.exclusiveMaximum = exclusiveMaximum;
}
}
if (typeof exclusiveMaximum === "number")
json.exclusiveMaximum = exclusiveMaximum;
if (typeof maximum === "number") {
json.maximum = maximum;
if (typeof exclusiveMaximum === "number" && this.target !== "draft-4") {
if (typeof exclusiveMaximum === "number") {
if (exclusiveMaximum <= maximum)
delete json.maximum;
else
@@ -154,13 +138,7 @@ export class JSONSchemaGenerator {
break;
}
case "null": {
if (this.target === "openapi-3.0") {
_json.type = "string";
_json.nullable = true;
_json.enum = [null];
}
else
_json.type = "null";
_json.type = "null";
break;
}
case "any": {
@@ -248,19 +226,10 @@ export class JSONSchemaGenerator {
}
case "union": {
const json = _json;
// Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
// because the discriminator field ensures mutual exclusivity between options in JSON Schema
const isDiscriminated = def.discriminator !== undefined;
const options = def.options.map((x, i) => this.process(x, {
json.anyOf = def.options.map((x, i) => this.process(x, {
...params,
path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
path: [...params.path, "anyOf", i],
}));
if (isDiscriminated) {
json.oneOf = options;
}
else {
json.anyOf = options;
}
break;
}
case "intersection": {
@@ -284,42 +253,32 @@ export class JSONSchemaGenerator {
case "tuple": {
const json = _json;
json.type = "array";
const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
const restPath = this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
const prefixItems = def.items.map((x, i) => this.process(x, {
...params,
path: [...params.path, prefixPath, i],
}));
const rest = def.rest
? this.process(def.rest, {
...params,
path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
})
: null;
const prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
if (this.target === "draft-2020-12") {
json.prefixItems = prefixItems;
if (rest) {
json.items = rest;
}
}
else if (this.target === "openapi-3.0") {
json.items = {
anyOf: prefixItems,
};
if (rest) {
json.items.anyOf.push(rest);
}
json.minItems = prefixItems.length;
if (!rest) {
json.maxItems = prefixItems.length;
}
}
else {
json.items = prefixItems;
if (rest) {
}
if (def.rest) {
const rest = this.process(def.rest, {
...params,
path: [...params.path, "items"],
});
if (this.target === "draft-2020-12") {
json.items = rest;
}
else {
json.additionalItems = rest;
}
}
// additionalItems
if (def.rest) {
json.items = this.process(def.rest, {
...params,
path: [...params.path, "items"],
});
}
// length
const { minimum, maximum } = schema._zod.bag;
if (typeof minimum === "number")
@@ -331,12 +290,7 @@ export class JSONSchemaGenerator {
case "record": {
const json = _json;
json.type = "object";
if (this.target === "draft-7" || this.target === "draft-2020-12") {
json.propertyNames = this.process(def.keyType, {
...params,
path: [...params.path, "propertyNames"],
});
}
json.propertyNames = this.process(def.keyType, { ...params, path: [...params.path, "propertyNames"] });
json.additionalProperties = this.process(def.valueType, {
...params,
path: [...params.path, "additionalProperties"],
@@ -396,12 +350,7 @@ export class JSONSchemaGenerator {
else if (vals.length === 1) {
const val = vals[0];
json.type = val === null ? "null" : typeof val;
if (this.target === "draft-4" || this.target === "openapi-3.0") {
json.enum = [val];
}
else {
json.const = val;
}
json.const = val;
}
else {
if (vals.every((v) => typeof v === "number"))
@@ -456,13 +405,7 @@ export class JSONSchemaGenerator {
}
case "nullable": {
const inner = this.process(def.innerType, params);
if (this.target === "openapi-3.0") {
result.ref = def.innerType;
_json.nullable = true;
}
else {
_json.anyOf = [inner, { type: "null" }];
}
_json.anyOf = [inner, { type: "null" }];
break;
}
case "nonoptional": {
@@ -552,12 +495,6 @@ export class JSONSchemaGenerator {
}
break;
}
case "function": {
if (this.unrepresentable === "throw") {
throw new Error("Function types cannot be represented in JSON Schema");
}
break;
}
default: {
def;
}
@@ -709,8 +646,7 @@ export class JSONSchemaGenerator {
flattenRef(ref, params);
// merge referenced schema into current
const refSchema = this.seen.get(ref).schema;
if (refSchema.$ref &&
(params.target === "draft-7" || params.target === "draft-4" || params.target === "openapi-3.0")) {
if (refSchema.$ref && params.target === "draft-7") {
schema.allOf = schema.allOf ?? [];
schema.allOf.push(refSchema);
}
@@ -737,14 +673,7 @@ export class JSONSchemaGenerator {
else if (this.target === "draft-7") {
result.$schema = "http://json-schema.org/draft-07/schema#";
}
else if (this.target === "draft-4") {
result.$schema = "http://json-schema.org/draft-04/schema#";
}
else if (this.target === "openapi-3.0") {
// OpenAPI 3.0 schema objects should not include a $schema property
}
else {
// @ts-ignore
console.warn(`Invalid target: ${this.target}`);
}
if (params.external?.uri) {
@@ -824,55 +753,97 @@ function isTransforming(_schema, _ctx) {
if (ctx.seen.has(_schema))
return false;
ctx.seen.add(_schema);
const def = _schema._zod.def;
if (def.type === "transform")
return true;
if (def.type === "array")
return isTransforming(def.element, ctx);
if (def.type === "set")
return isTransforming(def.valueType, ctx);
if (def.type === "lazy")
return isTransforming(def.getter(), ctx);
if (def.type === "promise" ||
def.type === "optional" ||
def.type === "nonoptional" ||
def.type === "nullable" ||
def.type === "readonly" ||
def.type === "default" ||
def.type === "prefault") {
return isTransforming(def.innerType, ctx);
}
if (def.type === "intersection") {
return isTransforming(def.left, ctx) || isTransforming(def.right, ctx);
}
if (def.type === "record" || def.type === "map") {
return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);
}
if (def.type === "pipe") {
return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);
}
if (def.type === "object") {
for (const key in def.shape) {
if (isTransforming(def.shape[key], ctx))
return true;
const schema = _schema;
const def = schema._zod.def;
switch (def.type) {
case "string":
case "number":
case "bigint":
case "boolean":
case "date":
case "symbol":
case "undefined":
case "null":
case "any":
case "unknown":
case "never":
case "void":
case "literal":
case "enum":
case "nan":
case "file":
case "template_literal":
return false;
case "array": {
return isTransforming(def.element, ctx);
}
return false;
}
if (def.type === "union") {
for (const option of def.options) {
if (isTransforming(option, ctx))
return true;
case "object": {
for (const key in def.shape) {
if (isTransforming(def.shape[key], ctx))
return true;
}
return false;
}
return false;
}
if (def.type === "tuple") {
for (const item of def.items) {
if (isTransforming(item, ctx))
return true;
case "union": {
for (const option of def.options) {
if (isTransforming(option, ctx))
return true;
}
return false;
}
if (def.rest && isTransforming(def.rest, ctx))
case "intersection": {
return isTransforming(def.left, ctx) || isTransforming(def.right, ctx);
}
case "tuple": {
for (const item of def.items) {
if (isTransforming(item, ctx))
return true;
}
if (def.rest && isTransforming(def.rest, ctx))
return true;
return false;
}
case "record": {
return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);
}
case "map": {
return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);
}
case "set": {
return isTransforming(def.valueType, ctx);
}
// inner types
case "promise":
case "optional":
case "nonoptional":
case "nullable":
case "readonly":
return isTransforming(def.innerType, ctx);
case "lazy":
return isTransforming(def.getter(), ctx);
case "default": {
return isTransforming(def.innerType, ctx);
}
case "prefault": {
return isTransforming(def.innerType, ctx);
}
case "custom": {
return false;
}
case "transform": {
return true;
return false;
}
case "pipe": {
return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);
}
case "success": {
return false;
}
case "catch": {
return false;
}
default:
def;
}
return false;
throw new Error(`Unknown schema type: ${def.type}`);
}