Some checks failed
Calque le pattern « ingrédient hors-catalogue » sur `TechStep` : cinq colonnes additives (`isPlaceholder`/`displayName`/`createdById`/`createdAt`/ `reviewedAt`), migration purement additive, FK `createdById → user_profiles` en `SET NULL`. Correction d'étape : `submitTechStepCorrectionSchema` accepte `correctedTechStepName` (mutuellement exclusif de `correctedTechStepId`) ; `submitTechStepCorrection` crée une `TechStep` placeholder dans la transaction et poursuit comme si son id avait été fourni. La correction s'applique immédiatement (`source: manual`) ; nouvel event analytics `tech_step.placeholder_proposed`. `TechStepView` gagne `isPlaceholder`/`displayName` ; `GET /reference/tech-steps` exclut les placeholders. Nouveau helper web `techStepLabel` (`displayName ?? t(catalog.techSteps.<key>)`), utilisé par `StepDescription` et le popover. Le popover a une échappatoire « Ma technique n'est pas dans la liste » (champ libre). Admin : `admin-tech-step-catalog.service.ts` + routes `GET/PATCH /admin/api/catalog/tech-step-placeholders` (liste plate — les propositions de technique sont rares) ; `CatalogPage` gagne un sélecteur Ingrédients / Techniques. Tests : Mocha (proposition placeholder, rejet id+nom, exclusion catalogue, liste + mark-reviewed admin) + Cypress (composant popover, e2e admin catalogue). Assertions `.techStep` existantes mises à jour pour les 2 nouveaux champs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
172 lines
4.1 KiB
SCSS
172 lines
4.1 KiB
SCSS
// =============================================================================
|
|
// CatalogPage — the off-catalog ingredient review: a pending/reviewed tab
|
|
// switch + "purge orphans" action, then one card per grouped placeholder.
|
|
// Mirrors CorrectionsPage's tab/card vocabulary so the admin app stays
|
|
// visually consistent.
|
|
// =============================================================================
|
|
|
|
.catalog-toolbar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--space-md);
|
|
margin-bottom: var(--space-md);
|
|
|
|
// Right-hand "purge orphans" button — a secondary/destructive action, so
|
|
// outlined rather than filled like the primary actions elsewhere.
|
|
> button {
|
|
padding: var(--space-xs) var(--space-md);
|
|
font-family: var(--font-body);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 600;
|
|
color: var(--color-text-muted);
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-base);
|
|
cursor: pointer;
|
|
|
|
&:hover {
|
|
border-color: var(--color-error);
|
|
color: var(--color-error);
|
|
}
|
|
&:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ingrédients / Techniques switch (issue #130) — sits above the
|
|
// pending/reviewed tabs; a bolder segmented control so the two levels read
|
|
// as distinct.
|
|
.catalog-kinds {
|
|
display: flex;
|
|
gap: var(--space-xs);
|
|
margin-bottom: var(--space-sm);
|
|
|
|
button {
|
|
padding: var(--space-xs) var(--space-md);
|
|
font-family: var(--font-display);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 700;
|
|
letter-spacing: 0.02em;
|
|
color: var(--color-text-muted);
|
|
background: none;
|
|
border: none;
|
|
border-bottom: 2.5px solid transparent;
|
|
cursor: pointer;
|
|
|
|
&.active {
|
|
color: var(--color-primary);
|
|
border-color: var(--color-primary);
|
|
}
|
|
}
|
|
}
|
|
|
|
.catalog-tabs {
|
|
display: flex;
|
|
gap: var(--space-xs);
|
|
margin-bottom: var(--space-md);
|
|
|
|
button {
|
|
padding: var(--space-sm) var(--space-md);
|
|
font-family: var(--font-body);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 600;
|
|
color: var(--color-text-muted);
|
|
background: none;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-base);
|
|
cursor: pointer;
|
|
|
|
&.active {
|
|
color: var(--color-primary);
|
|
border-color: var(--color-primary);
|
|
background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
|
|
}
|
|
}
|
|
}
|
|
|
|
.catalog-prune-message {
|
|
margin: 0 0 var(--space-md);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
.catalog-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.catalog-card {
|
|
padding: var(--space-md);
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-left: 4px solid var(--color-accent);
|
|
border-radius: var(--radius-md);
|
|
|
|
&__head {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
&__name {
|
|
font-size: var(--font-size-md);
|
|
margin: 0;
|
|
}
|
|
|
|
&__count {
|
|
flex-shrink: 0;
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 600;
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
&__variants,
|
|
&__recipes {
|
|
margin: var(--space-xs) 0 0;
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
&__foot {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--space-sm);
|
|
margin-top: var(--space-sm);
|
|
}
|
|
|
|
&__seen {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
&__foot button {
|
|
padding: var(--space-xs) var(--space-md);
|
|
font-family: var(--font-body);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 600;
|
|
color: var(--color-surface);
|
|
background: var(--color-primary);
|
|
border: none;
|
|
border-radius: var(--radius-base);
|
|
cursor: pointer;
|
|
|
|
&:hover {
|
|
background: var(--color-primary-hover);
|
|
}
|
|
&:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
}
|
|
}
|