feat(catalog): add reviewed bulk edits and complete category duplication
CI / check (push) Successful in 1m14s
CI / deploy (push) Successful in 1m11s

This commit is contained in:
Simo committed 2026-09-06 20:25:19 +02:00
1 parent 0bedc04692
commit 55a47949f1
26 files changed
+2504 -85

No files matched your search

+80 -1
View File
@@ -1944,7 +1944,86 @@
"searchFailed": "Search failed. Try again.",
"searching": "Searching...",
"category": "Category",
"offer": "Offer"
"offer": "Offer",
"duplicate": {
"title": "Duplicate category",
"description": "Copy this category, every descendant and all their offers.",
"name": "New category name",
"destination": "Destination category",
"disabled": "The copied root starts disabled and hidden. Descendants keep their settings; furniture definitions and assets are shared.",
"counts": "{pages} categories and {offers} offers",
"summary": "“{name}” under {destination}",
"cancel": "Cancel",
"working": "Working…",
"confirm": "Confirm duplication",
"preview": "Preview copy",
"saved": "Category and offers duplicated",
"savedPending": "Copy saved; hotel update pending",
"failed": "Duplication failed",
"root": "Catalog root",
"search": "Search categories…",
"empty": "No category found",
"suggested": "Suggested",
"all": "All categories",
"draftBlocked": "Save or discard category edits before duplicating."
}
},
"bulk": {
"previewCount": "{count} of {total} offers will change.",
"success": "Updated {count} offers.",
"selectOffer": "Select offer {name} (#{id})",
"pageEmpty": "No category found.",
"clearSelection": "Clear selection",
"disabledReason": "Save or reset pending edits and wait for current operations before bulk editing.",
"operation": "Operation",
"loadingDestinations": "Loading categories…",
"cancel": "Cancel",
"pagePlaceholder": "Select destination category…",
"noChanges": "The selected values already match. No changes to save.",
"costPoints": "Points price",
"chooseFields": "Check at least one field to change.",
"pageAll": "All categories",
"offer": "Offer",
"costCredits": "Credits",
"invalidPrice": "Enter a valid number. Set and add operations require whole numbers.",
"modes": {
"set": "Set to value",
"add": "Add or subtract",
"percent": "Change by percentage"
},
"fieldHint": "Only checked fields will change.",
"currencies": {
"0": "Duckets",
"5": "Diamonds",
"101": "Snowflakes",
"103": "Hearts",
"104": "Stars",
"105": "Shells"
},
"currency": "Points currency",
"button": "Bulk edit ({count})",
"description": "{count} selected offers. Preview changes before saving.",
"confirm": "Confirm and save {count} offers",
"beforeAfter": "Each cell shows the current value → the new value.",
"requestFailed": "The request failed. Refresh the preview before trying again.",
"priceHint": "Use negative additions or percentages to reduce prices. Percentage results round to the nearest integer; final prices cannot be negative.",
"selectedCount": "{count} offers selected across categories",
"value": "Value",
"unknownCurrency": "Type {type}",
"becomes": "becomes",
"title": "Edit selected offers",
"preview": "Preview changes",
"retryDestinations": "Retry loading categories",
"chooseDestination": "Select a destination category.",
"pageSuggested": "Suggested",
"previewTitle": "Review bulk changes",
"visibilityHint": "Visibility is controlled by the category. Moving offers makes them follow the destination category's visibility.",
"percentage": "Percentage (%)",
"destination": "Destination category",
"pageSearch": "Search categories…",
"back": "Edit changes",
"selectionLimit": "Select at most 500 offers for one operation.",
"refreshing": "Refreshing updated offers…"
}
},
"shop": {
+80 -1
View File
@@ -1863,7 +1863,86 @@
"searchFailed": "Ricerca non riuscita. Riprova.",
"searching": "Ricerca in corso...",
"category": "Categoria",
"offer": "Offerta"
"offer": "Offerta",
"duplicate": {
"title": "Duplica categoria",
"description": "Copia questa categoria, tutte le sottocategorie e le relative offerte.",
"name": "Nome della nuova categoria",
"destination": "Categoria di destinazione",
"disabled": "La radice copiata parte disabilitata e nascosta. Le sottocategorie mantengono le impostazioni; definizioni degli arredi e asset sono condivisi.",
"counts": "{pages} categorie e {offers} offerte",
"summary": "“{name}” sotto {destination}",
"cancel": "Annulla",
"working": "Operazione in corso…",
"confirm": "Conferma duplicazione",
"preview": "Anteprima copia",
"saved": "Categoria e offerte duplicate",
"savedPending": "Copia salvata; aggiornamento hotel in attesa",
"failed": "Duplicazione non riuscita",
"root": "Radice del catalogo",
"search": "Cerca categorie…",
"empty": "Nessuna categoria trovata",
"suggested": "Suggerite",
"all": "Tutte le categorie",
"draftBlocked": "Salva o annulla le modifiche alla categoria prima di duplicarla."
}
},
"bulk": {
"previewCount": "Verranno modificate {count} offerte su {total}.",
"success": "{count} offerte aggiornate.",
"selectOffer": "Seleziona offerta {name} (#{id})",
"pageEmpty": "Nessuna categoria trovata.",
"clearSelection": "Svuota selezione",
"disabledReason": "Salva o annulla le modifiche in sospeso e attendi la fine delle operazioni prima di modificare in blocco.",
"operation": "Operazione",
"loadingDestinations": "Caricamento categorie…",
"cancel": "Annulla",
"pagePlaceholder": "Seleziona la categoria di destinazione…",
"noChanges": "I valori selezionati corrispondono già. Nessuna modifica da salvare.",
"costPoints": "Prezzo in punti",
"chooseFields": "Seleziona almeno un campo da modificare.",
"pageAll": "Tutte le categorie",
"offer": "Offerta",
"costCredits": "Crediti",
"invalidPrice": "Inserisci un numero valido. Le operazioni di impostazione e aggiunta richiedono numeri interi.",
"modes": {
"set": "Imposta valore",
"add": "Aggiungi o sottrai",
"percent": "Varia in percentuale"
},
"fieldHint": "Verranno modificati solo i campi selezionati.",
"currencies": {
"0": "Duckets",
"5": "Diamanti",
"101": "Fiocchi di neve",
"103": "Cuori",
"104": "Stelle",
"105": "Conchiglie"
},
"currency": "Valuta dei punti",
"button": "Modifica in blocco ({count})",
"description": "{count} offerte selezionate. Controlla l'anteprima prima di salvare.",
"confirm": "Conferma e salva {count} offerte",
"beforeAfter": "Ogni cella mostra il valore attuale → il nuovo valore.",
"requestFailed": "Richiesta non riuscita. Aggiorna l'anteprima prima di riprovare.",
"priceHint": "Usa valori o percentuali negativi per ridurre i prezzi. Le percentuali vengono arrotondate all'intero più vicino; i prezzi finali non possono essere negativi.",
"selectedCount": "{count} offerte selezionate tra le categorie",
"value": "Valore",
"unknownCurrency": "Tipo {type}",
"becomes": "diventa",
"title": "Modifica le offerte selezionate",
"preview": "Anteprima modifiche",
"retryDestinations": "Riprova a caricare le categorie",
"chooseDestination": "Seleziona una categoria di destinazione.",
"pageSuggested": "Suggerite",
"previewTitle": "Controlla le modifiche in blocco",
"visibilityHint": "La visibilità dipende dalla categoria. Le offerte spostate seguono la visibilità della categoria di destinazione.",
"percentage": "Percentuale (%)",
"destination": "Categoria di destinazione",
"pageSearch": "Cerca categorie…",
"back": "Rivedi le modifiche",
"selectionLimit": "Seleziona al massimo 500 offerte per operazione.",
"refreshing": "Aggiornamento delle offerte modificate…"
}
},
"shop": {
+80 -1
View File
@@ -1693,7 +1693,86 @@
"searchFailed": "Zoeken mislukt. Probeer opnieuw.",
"searching": "Zoeken...",
"category": "Categorie",
"offer": "Aanbieding"
"offer": "Aanbieding",
"duplicate": {
"title": "Categorie dupliceren",
"description": "Kopieer deze categorie, alle subcategorieën en alle bijbehorende aanbiedingen.",
"name": "Naam van de nieuwe categorie",
"destination": "Doelcategorie",
"disabled": "De gekopieerde hoofdcategorie begint uitgeschakeld en verborgen. Subcategorieën behouden hun instellingen; meubeldefinities en bestanden worden gedeeld.",
"counts": "{pages} categorieën en {offers} aanbiedingen",
"summary": "‘{name}’ onder {destination}",
"cancel": "Annuleren",
"working": "Bezig…",
"confirm": "Dupliceren bevestigen",
"preview": "Kopie bekijken",
"saved": "Categorie en aanbiedingen gedupliceerd",
"savedPending": "Kopie opgeslagen; hotelupdate in afwachting",
"failed": "Dupliceren mislukt",
"root": "Catalogushoofdmap",
"search": "Categorieën zoeken…",
"empty": "Geen categorie gevonden",
"suggested": "Voorgesteld",
"all": "Alle categorieën",
"draftBlocked": "Sla categoriewijzigingen op of annuleer ze voordat je dupliceert."
}
},
"bulk": {
"previewCount": "{count} van {total} aanbiedingen worden gewijzigd.",
"success": "{count} aanbiedingen bijgewerkt.",
"selectOffer": "Selecteer aanbieding {name} (#{id})",
"pageEmpty": "Geen categorie gevonden.",
"clearSelection": "Selectie wissen",
"disabledReason": "Sla openstaande wijzigingen op of herstel ze en wacht tot lopende bewerkingen klaar zijn voordat je in bulk bewerkt.",
"operation": "Bewerking",
"loadingDestinations": "Categorieën laden…",
"cancel": "Annuleren",
"pagePlaceholder": "Selecteer de doelcategorie…",
"noChanges": "De geselecteerde waarden komen al overeen. Geen wijzigingen om op te slaan.",
"costPoints": "Prijs in punten",
"chooseFields": "Vink minstens één veld aan om te wijzigen.",
"pageAll": "Alle categorieën",
"offer": "Aanbieding",
"costCredits": "Credits",
"invalidPrice": "Voer een geldig getal in. Instellen en optellen vereisen gehele getallen.",
"modes": {
"set": "Waarde instellen",
"add": "Optellen of aftrekken",
"percent": "Procentueel wijzigen"
},
"fieldHint": "Alleen aangevinkte velden worden gewijzigd.",
"currencies": {
"0": "Duckets",
"5": "Diamanten",
"101": "Sneeuwvlokken",
"103": "Harten",
"104": "Sterren",
"105": "Schelpen"
},
"currency": "Puntenvaluta",
"button": "Bulk bewerken ({count})",
"description": "{count} aanbiedingen geselecteerd. Bekijk de wijzigingen voordat je opslaat.",
"confirm": "Bevestig en sla {count} aanbiedingen op",
"beforeAfter": "Elke cel toont de huidige waarde → de nieuwe waarde.",
"requestFailed": "Het verzoek is mislukt. Vernieuw het voorbeeld voordat je het opnieuw probeert.",
"priceHint": "Gebruik negatieve waarden of percentages om prijzen te verlagen. Procentuele resultaten worden afgerond op het dichtstbijzijnde gehele getal; eindprijzen mogen niet negatief zijn.",
"selectedCount": "{count} aanbiedingen geselecteerd in categorieën",
"value": "Waarde",
"unknownCurrency": "Type {type}",
"becomes": "wordt",
"title": "Geselecteerde aanbiedingen bewerken",
"preview": "Wijzigingen bekijken",
"retryDestinations": "Categorieën opnieuw laden",
"chooseDestination": "Selecteer een doelcategorie.",
"pageSuggested": "Voorgesteld",
"previewTitle": "Controleer de wijzigingen",
"visibilityHint": "De categorie bepaalt de zichtbaarheid. Verplaatste aanbiedingen volgen de zichtbaarheid van de doelcategorie.",
"percentage": "Percentage (%)",
"destination": "Doelcategorie",
"pageSearch": "Categorieën zoeken…",
"back": "Wijzigingen aanpassen",
"selectionLimit": "Selecteer maximaal 500 aanbiedingen per bewerking.",
"refreshing": "Bijgewerkte aanbiedingen vernieuwen…"
}
},
"shop": {