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
+41 -118
View File
@@ -15,11 +15,12 @@ module.exports = contentDisposition
module.exports.parse = parse
/**
* TextDecoder instance for UTF-8 decoding when decodeURIComponent fails due to invalid byte sequences.
* @type {TextDecoder}
* Module dependencies.
* @private
*/
const utf8Decoder = new TextDecoder('utf-8')
var basename = require('path').basename
var Buffer = require('safe-buffer').Buffer
/**
* RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")
@@ -28,6 +29,14 @@ const utf8Decoder = new TextDecoder('utf-8')
var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g // eslint-disable-line no-control-regex
/**
* RegExp to match percent encoding escape.
* @private
*/
var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/
var HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g
/**
* RegExp to match non-latin1 characters.
* @private
@@ -191,7 +200,7 @@ function createparams (filename, fallback) {
var hasFallback = typeof fallbackName === 'string' && fallbackName !== name
// set extended filename parameter
if (hasFallback || !isQuotedString || hasHexEscape(name)) {
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
params['filename*'] = name
}
@@ -254,41 +263,32 @@ function format (obj) {
*/
function decodefield (str) {
const match = EXT_VALUE_REGEXP.exec(str)
var match = EXT_VALUE_REGEXP.exec(str)
if (!match) {
throw new TypeError('invalid extended field value')
}
const charset = match[1].toLowerCase()
const encoded = match[2]
var charset = match[1].toLowerCase()
var encoded = match[2]
var value
// to binary string
var binary = encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)
switch (charset) {
case 'iso-8859-1':
{
const binary = decodeHexEscapes(encoded)
return getlatin1(binary)
}
value = getlatin1(binary)
break
case 'utf-8':
case 'utf8':
{
try {
return decodeURIComponent(encoded)
} catch {
// Failed to decode with decodeURIComponent, fallback to lenient decoding which replaces invalid UTF-8 byte sequences with the Unicode replacement character
// TODO: Consider removing in the next major version to be more strict about invalid percent-encodings
const binary = decodeHexEscapes(encoded)
const bytes = new Uint8Array(binary.length)
for (let idx = 0; idx < binary.length; idx++) {
bytes[idx] = binary.charCodeAt(idx)
}
return utf8Decoder.decode(bytes)
}
}
value = Buffer.from(binary, 'binary').toString('utf8')
break
default:
throw new TypeError('unsupported charset in extended field')
}
throw new TypeError('unsupported charset in extended field')
return value
}
/**
@@ -384,6 +384,19 @@ function parse (string) {
return new ContentDisposition(type, params)
}
/**
* Percent decode a single character.
*
* @param {string} str
* @param {string} hex
* @return {string}
* @private
*/
function pdecode (str, hex) {
return String.fromCharCode(parseInt(hex, 16))
}
/**
* Percent encode a single character.
*
@@ -444,93 +457,3 @@ function ContentDisposition (type, parameters) {
this.type = type
this.parameters = parameters
}
/**
* Return the last portion of a path
*
* @param {string} path
* @returns {string}
*/
function basename (path) {
const normalized = path.replaceAll('\\', '/')
let end = normalized.length
while (end > 0 && normalized[end - 1] === '/') {
end--
}
if (end === 0) {
return ''
}
let start = end - 1
while (start >= 0 && normalized[start] !== '/') {
start--
}
return normalized.slice(start + 1, end)
}
/**
* Check if a character is a hex digit [0-9A-Fa-f]
*
* @param {string} char
* @return {boolean}
* @private
*/
function isHexDigit (char) {
const code = char.charCodeAt(0)
return (
(code >= 48 && code <= 57) || // 0-9
(code >= 65 && code <= 70) || // A-F
(code >= 97 && code <= 102) // a-f
)
}
/**
* Check if a string contains percent encoding escapes.
*
* @param {string} str
* @return {boolean}
* @private
*/
function hasHexEscape (str) {
const maxIndex = str.length - 3
let lastIndex = -1
while ((lastIndex = str.indexOf('%', lastIndex + 1)) !== -1 && lastIndex <= maxIndex) {
if (isHexDigit(str[lastIndex + 1]) && isHexDigit(str[lastIndex + 2])) {
return true
}
}
return false
}
/**
* Decode hex escapes in a string (e.g., %20 -> space)
*
* @param {string} str
* @return {string}
* @private
*/
function decodeHexEscapes (str) {
const firstEscape = str.indexOf('%')
if (firstEscape === -1) return str
let result = str.slice(0, firstEscape)
for (let idx = firstEscape; idx < str.length; idx++) {
if (
str[idx] === '%' &&
idx + 2 < str.length &&
isHexDigit(str[idx + 1]) &&
isHexDigit(str[idx + 2])
) {
result += String.fromCharCode(Number.parseInt(str[idx + 1] + str[idx + 2], 16))
idx += 2
} else {
result += str[idx]
}
}
return result
}