Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions frontend/src/components/ConfigurationView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ function ConfigurationView({
libraryResetError,
libraryResetMessage,
isResettingLibrary,
isLibraryResetConfirmationOpen,
waitingValidationResetCount,
platformCatalogSyncError,
platformCatalogSyncMessage,
isSyncingPlatformCatalog,
Expand All @@ -63,6 +65,8 @@ function ConfigurationView({
onOpenCollectionShares,
onDownloadOds,
onResetLibrary,
onCancelLibraryReset,
onConfirmLibraryReset,
onSyncPlatformCatalog,
onReinitializeCollection,
onLogout,
Expand Down Expand Up @@ -256,8 +260,81 @@ function ConfigurationView({
</article>
) : null}
</section>

<LibraryResetConfirmationDialog
isOpen={isLibraryResetConfirmationOpen}
isResettingLibrary={isResettingLibrary}
waitingValidationCount={waitingValidationResetCount}
onCancel={onCancelLibraryReset}
onConfirm={onConfirmLibraryReset}
/>
</PageLayout>
);
}

/**
* 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 (
<div className="modalOverlay" role="presentation">
<section
className="adminResetConfirmationDialog"
role="dialog"
aria-modal="true"
aria-labelledby="admin-reset-confirmation-title"
>
<header>
<p className="eyebrow">Confirmation</p>
<h2 id="admin-reset-confirmation-title">Confirmer le reset Bibliotheque</h2>
</header>
<p>
ATTENTION : ce reset supprime et reconstruit toute la Bibliotheque globale a partir des
imports utilisateur.
</p>
{Number(waitingValidationCount) > 0 ? (
<p className="adminResetValidationWarning">
<strong>{waitingValidationCount} jeu(x)</strong>
{" "}
marque(s) comme en attente de validation seront automatiquement acceptes si le reset est
lance.
</p>
) : null}
<div className="formActions">
<button
className="secondaryButton"
type="button"
onClick={onCancel}
disabled={isResettingLibrary}
>
Annuler
</button>
<button
className="dangerButton"
type="button"
onClick={onConfirm}
disabled={isResettingLibrary}
>
Confirmer le reset
</button>
</div>
</section>
</div>
);
}

export default ConfigurationView;
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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}
/>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/hooks/app/useCloudCollectionViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
59 changes: 36 additions & 23 deletions frontend/src/hooks/library/useLibraryResetAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,52 @@ 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<void>} Met a jour les messages de resultat.
* @throws {void} Les erreurs sont converties en message lisible.
*/
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<void>} 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<void>} 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();
Expand All @@ -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;
27 changes: 27 additions & 0 deletions frontend/src/styles/admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
36 changes: 31 additions & 5 deletions frontend/tests/gameValidationWorkflow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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("<strong>{waitingValidationCount} jeu(x)</strong>"), 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", () => {
Expand Down
Loading