diff --git a/frontend/src/components/ConfigurationView.jsx b/frontend/src/components/ConfigurationView.jsx index 34f7271..dd2ef4b 100644 --- a/frontend/src/components/ConfigurationView.jsx +++ b/frontend/src/components/ConfigurationView.jsx @@ -44,6 +44,8 @@ function ConfigurationView({ libraryResetError, libraryResetMessage, isResettingLibrary, + isLibraryResetConfirmationOpen, + waitingValidationResetCount, platformCatalogSyncError, platformCatalogSyncMessage, isSyncingPlatformCatalog, @@ -63,6 +65,8 @@ function ConfigurationView({ onOpenCollectionShares, onDownloadOds, onResetLibrary, + onCancelLibraryReset, + onConfirmLibraryReset, onSyncPlatformCatalog, onReinitializeCollection, onLogout, @@ -256,8 +260,81 @@ function ConfigurationView({ ) : null} + + ); } +/** + * Affiche la confirmation du reset Bibliotheque. + * + * @param {Object} props - Etat de la pop-up et callbacks de confirmation. + * @returns {import("react").JSX.Element|null} Pop-up de confirmation ou rien. + * @throws {void} Ne leve pas d'exception pendant le rendu React. + */ +function LibraryResetConfirmationDialog({ + isOpen, + isResettingLibrary, + waitingValidationCount, + onCancel, + onConfirm, +}) { + if (!isOpen) { + return null; + } + + return ( +
+
+
+

Confirmation

+

Confirmer le reset Bibliotheque

+
+

+ ATTENTION : ce reset supprime et reconstruit toute la Bibliotheque globale a partir des + imports utilisateur. +

+ {Number(waitingValidationCount) > 0 ? ( +

+ {waitingValidationCount} jeu(x) + {" "} + marque(s) comme en attente de validation seront automatiquement acceptes si le reset est + lance. +

+ ) : null} +
+ + +
+
+
+ ); +} + export default ConfigurationView; diff --git a/frontend/src/components/appViewSwitchConfigurationRenderer.jsx b/frontend/src/components/appViewSwitchConfigurationRenderer.jsx index 52a1da7..5e75c3b 100644 --- a/frontend/src/components/appViewSwitchConfigurationRenderer.jsx +++ b/frontend/src/components/appViewSwitchConfigurationRenderer.jsx @@ -33,6 +33,8 @@ function renderConfigurationView(props, pageLayoutProps) { libraryResetError={props.libraryResetError} libraryResetMessage={props.libraryResetMessage} isResettingLibrary={props.isResettingLibrary} + isLibraryResetConfirmationOpen={props.isLibraryResetConfirmationOpen} + waitingValidationResetCount={props.waitingValidationResetCount} platformCatalogSyncError={props.platformCatalogSyncError} platformCatalogSyncMessage={props.platformCatalogSyncMessage} isSyncingPlatformCatalog={props.isSyncingPlatformCatalog} @@ -45,6 +47,8 @@ function renderConfigurationView(props, pageLayoutProps) { onOpenCollectionShares={props.openCollectionShares} onDownloadOds={props.downloadOdsFile} onResetLibrary={props.resetLibrary} + onCancelLibraryReset={props.cancelLibraryReset} + onConfirmLibraryReset={props.confirmLibraryReset} onSyncPlatformCatalog={props.syncPlatformCatalog} onReinitializeCollection={props.reinitializeCollection} /> diff --git a/frontend/src/hooks/app/useCloudCollectionViewModel.js b/frontend/src/hooks/app/useCloudCollectionViewModel.js index 7b42afa..84b7f6b 100644 --- a/frontend/src/hooks/app/useCloudCollectionViewModel.js +++ b/frontend/src/hooks/app/useCloudCollectionViewModel.js @@ -280,6 +280,8 @@ function useCloudCollectionViewModel() { libraryResetError: libraryResetAction.libraryResetError, libraryResetMessage: libraryResetAction.libraryResetMessage, isResettingLibrary: libraryResetAction.isResettingLibrary, + isLibraryResetConfirmationOpen: libraryResetAction.isLibraryResetConfirmationOpen, + waitingValidationResetCount: libraryResetAction.waitingValidationCount, adminLibraryImportError: adminLibraryCsvImportAction.adminLibraryImportError, adminLibraryImportResult: adminLibraryCsvImportAction.adminLibraryImportResult, isImportingAdminLibrary: adminLibraryCsvImportAction.isImportingAdminLibrary, @@ -326,6 +328,8 @@ function useCloudCollectionViewModel() { closeHomeSearch: homePage.closeHomeSearch, downloadOdsFile: odsDownload.downloadOdsFile, resetLibrary: libraryResetAction.resetLibrary, + cancelLibraryReset: libraryResetAction.cancelLibraryReset, + confirmLibraryReset: libraryResetAction.confirmLibraryReset, importAdminLibraryCsv: adminLibraryCsvImportAction.importAdminLibraryCsv, selectAdminLibraryImportFile: adminLibraryCsvImportAction.selectAdminLibraryImportFile, syncPlatformCatalog: platformCatalogSyncAction.syncPlatformCatalog, diff --git a/frontend/src/hooks/library/useLibraryResetAction.js b/frontend/src/hooks/library/useLibraryResetAction.js index 7edc9be..92a3cb7 100644 --- a/frontend/src/hooks/library/useLibraryResetAction.js +++ b/frontend/src/hooks/library/useLibraryResetAction.js @@ -30,13 +30,14 @@ function useLibraryResetAction(options = {}) { const [libraryResetMessage, setLibraryResetMessage] = useState(""); const [libraryResetError, setLibraryResetError] = useState(""); const [isResettingLibrary, setIsResettingLibrary] = useState(false); + const [isLibraryResetConfirmationOpen, setIsLibraryResetConfirmationOpen] = useState(false); const waitingValidationCount = Math.max( 0, Number.parseInt(options.waitingValidationCount, 10) || 0 ); /** - * Demande confirmation puis appelle le backend de reset Bibliotheque. + * Ouvre la confirmation du reset Bibliotheque. * * @returns {Promise} Met a jour les messages de resultat. * @throws {void} Les erreurs sont converties en message lisible. @@ -44,11 +45,37 @@ function useLibraryResetAction(options = {}) { const resetLibrary = async () => { setLibraryResetMessage(""); setLibraryResetError(""); - const confirmed = window.confirm(buildLibraryResetConfirmationMessage(waitingValidationCount)); - if (!confirmed) { - return; - } + setIsLibraryResetConfirmationOpen(true); + }; + + /** + * Confirme le reset apres affichage de la pop-up de confirmation. + * + * @returns {Promise} Lance le reset apres fermeture de la pop-up. + * @throws {void} Les erreurs sont converties en message lisible. + */ + const confirmLibraryReset = async () => { + setIsLibraryResetConfirmationOpen(false); + await launchLibraryReset(); + }; + + /** + * Annule la demande de reset depuis la pop-up de confirmation. + * + * @returns {void} Ferme la pop-up sans appeler le backend. + * @throws {void} Ne leve pas d'exception. + */ + const cancelLibraryReset = () => { + setIsLibraryResetConfirmationOpen(false); + }; + /** + * Appelle le backend de reset Bibliotheque apres confirmation. + * + * @returns {Promise} Met a jour les messages de resultat. + * @throws {void} Les erreurs sont converties en message lisible. + */ + const launchLibraryReset = async () => { try { setIsResettingLibrary(true); await LibraryAdminApi.resetLibrary(); @@ -67,29 +94,15 @@ function useLibraryResetAction(options = {}) { }; return { + cancelLibraryReset, + confirmLibraryReset, + isLibraryResetConfirmationOpen, isResettingLibrary, libraryResetError, libraryResetMessage, resetLibrary, + waitingValidationCount, }; } -/** - * Construit le message de confirmation du reset Bibliotheque. - * - * @param {number} waitingValidationCount - Nombre de jeux actuellement en attente. - * @returns {string} Message de confirmation affiche a l'administrateur. - * @throws {void} Ne leve pas d'exception. - */ -function buildLibraryResetConfirmationMessage(waitingValidationCount = 0) { - const baseMessage = ( - "ATTENTION : ce reset supprime et reconstruit toute la Bibliotheque globale a partir des imports utilisateur." - ); - const waitingMessage = Number(waitingValidationCount) > 0 - ? ` ${waitingValidationCount} jeu(x) en attente seront valides automatiquement par le reset.` - : ""; - return `${baseMessage}${waitingMessage} Confirmer le lancement ?`; -} - -export { buildLibraryResetConfirmationMessage }; export default useLibraryResetAction; diff --git a/frontend/src/styles/admin.css b/frontend/src/styles/admin.css index 13108f2..9dfb1b8 100644 --- a/frontend/src/styles/admin.css +++ b/frontend/src/styles/admin.css @@ -77,6 +77,33 @@ border-color: #991b1b; } +.adminResetConfirmationDialog { + background: #ffffff; + border: 1px solid #fecaca; + border-radius: 8px; + box-shadow: 0 24px 60px rgba(15, 23, 42, 0.28); + display: grid; + gap: 1rem; + padding: 1.25rem; + width: min(100%, 34rem); +} + +.adminResetConfirmationDialog h2 { + color: #111827; + font-size: 1.25rem; + margin: 0; +} + +.adminResetConfirmationDialog p { + color: #334155; + margin: 0; +} + +.adminResetValidationWarning strong { + color: #b91c1c; + font-weight: 900; +} + .usersTableSection { margin-top: 1.5rem; } diff --git a/frontend/tests/gameValidationWorkflow.test.js b/frontend/tests/gameValidationWorkflow.test.js index 569bbbc..0bd999f 100644 --- a/frontend/tests/gameValidationWorkflow.test.js +++ b/frontend/tests/gameValidationWorkflow.test.js @@ -14,7 +14,6 @@ import BackendRouteAccessService from "../src/services/BackendRouteAccessService import LibraryAdminApi from "../src/services/LibraryAdminApi.js"; import LibraryApi from "../src/services/LibraryApi.js"; import { getLibraryGameColumns } from "../src/hooks/library/libraryGameColumns.js"; -import { buildLibraryResetConfirmationMessage } from "../src/hooks/library/useLibraryResetAction.js"; class MemoryStorage { /** Initialise un stockage navigateur en memoire. */ @@ -107,11 +106,38 @@ test("derive la permission de gestion validation jeux depuis les routes admin", assert.equal(userPermissions.canManageGameValidation, false); }); -test("ajoute l'avertissement de validation automatique dans la confirmation reset", () => { - const message = buildLibraryResetConfirmationMessage(4); +test("declare la pop-up de confirmation reset permanente avec compteur colore", () => { + const hookSource = readFileSync( + new URL("../src/hooks/library/useLibraryResetAction.js", import.meta.url), + "utf8" + ); + const configurationSource = readFileSync( + new URL("../src/components/ConfigurationView.jsx", import.meta.url), + "utf8" + ); + const rendererSource = readFileSync( + new URL("../src/components/appViewSwitchConfigurationRenderer.jsx", import.meta.url), + "utf8" + ); + const adminStyleSource = readFileSync( + new URL("../src/styles/admin.css", import.meta.url), + "utf8" + ); - assert.equal(message.includes("4 jeu(x) en attente"), true); - assert.equal(message.includes("valides automatiquement"), true); + assert.equal(hookSource.includes("isLibraryResetConfirmationOpen"), true); + assert.equal(hookSource.includes("setIsLibraryResetConfirmationOpen(true)"), true); + assert.equal(hookSource.includes("window.confirm"), false); + assert.equal(configurationSource.includes("LibraryResetConfirmationDialog"), true); + assert.equal(configurationSource.includes("ATTENTION : ce reset supprime"), true); + assert.equal(configurationSource.includes("Number(waitingValidationCount) > 0"), true); + assert.equal(configurationSource.includes("{waitingValidationCount} jeu(x)"), true); + assert.equal(configurationSource.includes("seront automatiquement acceptes"), true); + assert.equal(configurationSource.includes("Annuler"), true); + assert.equal(configurationSource.includes("Confirmer le reset"), true); + assert.equal(rendererSource.includes("onCancelLibraryReset"), true); + assert.equal(rendererSource.includes("onConfirmLibraryReset"), true); + assert.equal(adminStyleSource.includes(".adminResetValidationWarning strong"), true); + assert.equal(adminStyleSource.includes("font-weight: 900"), true); }); test("declare les controles admin de filtre, selection et badge Bibliotheque", () => {