diff --git a/confiture-rest-api/src/audits/dto/entities/page.dto.ts b/confiture-rest-api/src/audits/dto/entities/page.dto.ts
index 7ef98303a..4d9d08cce 100644
--- a/confiture-rest-api/src/audits/dto/entities/page.dto.ts
+++ b/confiture-rest-api/src/audits/dto/entities/page.dto.ts
@@ -1,5 +1,6 @@
export class PageDto {
id: number;
+ slug: string;
order: number;
name: string;
url: string;
diff --git a/confiture-rest-api/src/audits/prisma-selects.ts b/confiture-rest-api/src/audits/prisma-selects.ts
index f1fa4354b..60bec3cf1 100644
--- a/confiture-rest-api/src/audits/prisma-selects.ts
+++ b/confiture-rest-api/src/audits/prisma-selects.ts
@@ -4,7 +4,8 @@ export const PAGE_PRISMA_SELECT: Prisma.AuditedPageSelect = {
id: true,
order: true,
name: true,
- url: true
+ url: true,
+ slug: true
};
export const ENVIRONMENT_PRISMA_SELECT: Prisma.TestEnvironmentSelect = {
diff --git a/confiture-web-app/src/components/audit/AraTabsTwo.vue b/confiture-web-app/src/components/audit/AraTabsTwo.vue
new file mode 100644
index 000000000..71d64a1dd
--- /dev/null
+++ b/confiture-web-app/src/components/audit/AraTabsTwo.vue
@@ -0,0 +1,260 @@
+
+
+
+
+
+ -
+
+
+ {{ tab.label }}
+ {{ tabs[i].hiddenLabelSuffix }}
+
+
+
+
+
+
+
+
diff --git a/confiture-web-app/src/pages/audit/AuditGenerationPage.vue b/confiture-web-app/src/pages/audit/AuditGenerationPage.vue
index 93b3983e3..264f9c986 100644
--- a/confiture-web-app/src/pages/audit/AuditGenerationPage.vue
+++ b/confiture-web-app/src/pages/audit/AuditGenerationPage.vue
@@ -3,10 +3,9 @@ import { useResizeObserver } from "@vueuse/core";
import { computed, nextTick, ref, watch } from "vue";
import { onBeforeRouteLeave } from "vue-router";
-import AraTabs from "../../components/audit/AraTabs.vue";
+import AraTabsTwo, { TabItem } from "../../components/audit/AraTabsTwo.vue";
import AuditGenerationFilters from "../../components/audit/AuditGenerationFilters.vue";
import AuditGenerationHeader from "../../components/audit/AuditGenerationHeader.vue";
-import AuditGenerationPageCriteria from "../../components/audit/AuditGenerationPageCriteria.vue";
import PageMeta from "../../components/PageMeta";
import { SummaryCardThemes } from "../../components/SummaryCard.vue";
import BackLink from "../../components/ui/BackLink.vue";
@@ -14,14 +13,14 @@ import { useAuditStats } from "../../composables/useAuditStats";
import { useWrappedFetch } from "../../composables/useWrappedFetch";
import rgaa from "../../criteres.json";
import { CRITERIA_BY_AUDIT_TYPE } from "../../criteria";
-import { REFERENTIAL, StaticTabLabel } from "../../enums";
+import { REFERENTIAL, StaticTabLabel, TabSlug } from "../../enums";
import {
useAccountStore,
useAuditStore,
useFiltersStore,
useResultsStore
} from "../../store";
-import { AuditType, CriteriumResultStatus, TabData } from "../../types";
+import { AuditType, CriteriumResultStatus } from "../../types";
import { pluralize } from "../../utils";
const props = defineProps<{
@@ -192,33 +191,57 @@ const pageTitle = computed(() => {
return titleParts.join(" - ");
});
-const tabsData = computed((): TabData[] => {
- const transversePage = auditStore.currentAudit?.transverseElementsPage;
+// const tabsData = computed((): TabData[] => {
+// const transversePage = auditStore.currentAudit?.transverseElementsPage;
+// return [
+// ...(transversePage
+// ? [
+// {
+// label: StaticTabLabel.AUDIT_COMMON_ELEMENTS_TAB_LABEL,
+// diplayLabelSuffix: " (optionnel)",
+// icon: "fr-icon-layout-3-line",
+// component: AuditGenerationPageCriteria,
+// componentParams: {
+// page: transversePage,
+// auditUniqueId: props.uniqueId
+// }
+// }
+// ]
+// : []),
+// ...(auditStore.currentAudit?.pages.map((p) => ({
+// label: p.name,
+// hiddenLabelSuffix: resultsStore.isPageCompleted(p.id) ? " (entièrement évalué)" : undefined,
+// id: p.id,
+// icon: resultsStore.isPageCompleted(p.id) ? "fr-icon-check-line" : undefined,
+// component: AuditGenerationPageCriteria,
+// componentParams: {
+// page: p,
+// auditUniqueId: props.uniqueId
+// }
+// })) ?? [])
+// ];
+// });
+
+const tabsTwo = computed((): TabItem[] => {
return [
- ...(transversePage
+ ...(auditStore.currentAudit?.transverseElementsPage
? [
{
label: StaticTabLabel.AUDIT_COMMON_ELEMENTS_TAB_LABEL,
- diplayLabelSuffix: " (optionnel)",
icon: "fr-icon-layout-3-line",
- component: AuditGenerationPageCriteria,
- componentParams: {
- page: transversePage,
- auditUniqueId: props.uniqueId
- }
+ to: `/audits/${props.uniqueId}/generation/${TabSlug.AUDIT_COMMON_ELEMENTS_SLUG}`
}
]
: []),
...(auditStore.currentAudit?.pages.map((p) => ({
label: p.name,
- hiddenLabelSuffix: resultsStore.isPageCompleted(p.id) ? " (entièrement évalué)" : undefined,
- id: p.id,
- icon: resultsStore.isPageCompleted(p.id) ? "fr-icon-check-line" : undefined,
- component: AuditGenerationPageCriteria,
- componentParams: {
- page: p,
- auditUniqueId: props.uniqueId
- }
+ hiddenLabelSuffix: resultsStore.isPageCompleted(p.id)
+ ? " (entièrement évalué)"
+ : undefined,
+ to: `/audits/${props.uniqueId}/generation/${p.slug}`,
+ icon: resultsStore.isPageCompleted(p.id)
+ ? "fr-icon-check-line"
+ : undefined
})) ?? [])
];
});
@@ -249,30 +272,33 @@ function toggleFilters(doShow: boolean) {
}
// Note: here useWrappedFetch uses onMounted callback
-useWrappedFetch(async () => {
- resultsStore.$reset();
-
- await Promise.all([
- auditStore.fetchAuditIfNeeded(props.uniqueId),
- resultsStore.fetchResults(props.uniqueId)
- ]);
-
- // wait for rerender before getting ref
- await nextTick();
- stickyIndicator.value = auditGenerationHeaderRef.value?.stickyIndicator;
-
- // if the user navigated away during fetching, it’s possible the stickyIndicator is not in the DOM anymore
- if (stickyIndicator.value) {
- useResizeObserver(stickyIndicator.value, () => {
- stickyTop.value = `calc(${getComputedStyle(stickyIndicator!.value).top} + ${
- stickyIndicator!.value.clientHeight
- }px)`;
- });
+useWrappedFetch(
+ async () => {
+ resultsStore.$reset();
+
+ await Promise.all([
+ auditStore.fetchAuditIfNeeded(props.uniqueId)
+ // resultsStore.fetchResults(props.uniqueId)
+ ]);
+
+ // wait for rerender before getting ref
+ await nextTick();
+ stickyIndicator.value = auditGenerationHeaderRef.value?.stickyIndicator;
+
+ // if the user navigated away during fetching, it’s possible the stickyIndicator is not in the DOM anymore
+ if (stickyIndicator.value) {
+ useResizeObserver(stickyIndicator.value, () => {
+ stickyTop.value = `calc(${getComputedStyle(stickyIndicator!.value).top} + ${
+ stickyIndicator!.value.clientHeight
+ }px)`;
+ });
+ }
+ },
+ (newParams, oldParams) => {
+ // Only fetch data if uniqueId changes
+ return newParams.uniqueId !== oldParams.uniqueId;
}
-}, (newParams, oldParams) => {
- // Only fetch data if uniqueId changes
- return newParams.uniqueId !== oldParams.uniqueId;
-});
+);
onBeforeRouteLeave(() => {
auditStore.showAuditEmailAlert = false;
@@ -293,7 +319,10 @@ filterStore.$reset();
-
@@ -373,7 +410,9 @@ filterStore.$reset();
.fr-col-md-11 {
flex-grow: 1 !important;
- max-width: calc(100% - calc(var(--filters-column-width) + var(--gap))) !important; /* Sidebar width + gap */
+ max-width: calc(
+ 100% - calc(var(--filters-column-width) + var(--gap))
+ ) !important; /* Sidebar width + gap */
width: auto !important;
}
diff --git a/confiture-web-app/src/pages/audit/AuditPagePage.vue b/confiture-web-app/src/pages/audit/AuditPagePage.vue
new file mode 100644
index 000000000..1186274a5
--- /dev/null
+++ b/confiture-web-app/src/pages/audit/AuditPagePage.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
diff --git a/confiture-web-app/src/router.ts b/confiture-web-app/src/router.ts
index f66dc364b..9d10ea6a6 100644
--- a/confiture-web-app/src/router.ts
+++ b/confiture-web-app/src/router.ts
@@ -20,6 +20,7 @@ import AuditCreatePage from "./pages/audit/AuditCreatePage.vue";
import AuditDeclarationPage from "./pages/audit/AuditDeclarationPage.vue";
import AuditGenerationPage from "./pages/audit/AuditGenerationPage.vue";
import AuditOverviewPage from "./pages/audit/AuditOverviewPage.vue";
+import AuditPagePage from "./pages/audit/AuditPagePage.vue";
import AuditSettingsPage from "./pages/audit/AuditSettingsPage.vue";
import ChangelogPage from "./pages/ChangelogPage.vue";
import ErrorPage from "./pages/error/ErrorPage.vue";
@@ -226,7 +227,7 @@ const router = createRouter({
{
path: ":tabSlug",
name: "audit-generation-full",
- component: AraTabsPanel
+ component: AuditPagePage
}
],
meta: {
diff --git a/confiture-web-app/src/store/results.ts b/confiture-web-app/src/store/results.ts
index 3b6e9f2e8..58da2ae8b 100644
--- a/confiture-web-app/src/store/results.ts
+++ b/confiture-web-app/src/store/results.ts
@@ -4,11 +4,13 @@ import { defineStore } from "pinia";
import { api } from "../api";
import { CRITERIA_BY_AUDIT_TYPE, LINKED_CRITERIA } from "../criteria";
import {
+ ApiCriteriumResult,
AuditType,
CriterionResultUserImpact,
CriteriumResult,
CriteriumResultStatus,
- ExampleImageFile
+ ExampleImageFile,
+ GetPageWithResultsDto
} from "../types";
import { useAuditStore } from "./audit";
import { useFiltersStore } from "./filters";
@@ -26,7 +28,7 @@ interface ResultsStoreState {
data: {
[key: PageId]: {
[key: TopicNumber]: {
- [key: CriteriumNumber]: CriteriumResult;
+ [key: CriteriumNumber]: ApiCriteriumResult;
};
};
} | null;
@@ -237,6 +239,28 @@ export const useResultsStore = defineStore("results", {
this.data = data;
},
+ async fetchPageResults(auditId: string, pageSlug: string) {
+ const response = (await ky
+ .get(`/api/audits/${auditId}/pages/${pageSlug}`, {})
+ .json()) as GetPageWithResultsDto;
+
+ console.log(response);
+
+ const data: ResultsStoreState["data"] = {};
+
+ response.results.forEach((r) => {
+ if (!(r.pageId in data)) {
+ data[r.pageId] = {};
+ }
+
+ if (!(r.topic in data[r.pageId])) {
+ data[r.pageId][r.topic] = {};
+ }
+
+ data[r.pageId][r.topic][r.criterium] = r;
+ });
+ },
+
async updateResults(uniqueId: string, updates: CriteriumResult[]) {
if (!this.data) {
return;
@@ -309,15 +333,20 @@ export const useResultsStore = defineStore("results", {
// update linked criteria if any
const updatedCritResult = updates[0];
- const currentCritStatus = this.getCriteriumResult(updatedCritResult.pageId, updatedCritResult.topic, updatedCritResult.criterium)?.status;
- const topicAndCriterium: string =
- `${updatedCritResult.topic}.${updatedCritResult.criterium}`;
+ const currentCritStatus = this.getCriteriumResult(
+ updatedCritResult.pageId,
+ updatedCritResult.topic,
+ updatedCritResult.criterium
+ )?.status;
+ const topicAndCriterium: string = `${updatedCritResult.topic}.${updatedCritResult.criterium}`;
if (
- !(has(LINKED_CRITERIA, topicAndCriterium) &&
- updates.length === 1 &&
- // checks if result status has been updated
- updatedCritResult.status !== currentCritStatus)
+ !(
+ has(LINKED_CRITERIA, topicAndCriterium) &&
+ updates.length === 1 &&
+ // checks if result status has been updated
+ updatedCritResult.status !== currentCritStatus
+ )
) {
// nothing to do
return;
@@ -329,7 +358,7 @@ export const useResultsStore = defineStore("results", {
if (updatedCritResult.status === CriteriumResultStatus.NOT_APPLICABLE) {
// save previous status of linked criteria
- linkedCriteria.forEach(c => {
+ linkedCriteria.forEach((c) => {
const [topic, criterium] = c.split(".").map(Number);
setWith(
@@ -342,26 +371,44 @@ export const useResultsStore = defineStore("results", {
// apply status to linked criteria
if (auditStore.currentAudit) {
- linkedUpdates = linkedCriteria.filter(c => this.isInCriteriaList(c, auditStore.currentAudit!.auditType)).map((update) => {
- const [topic, criterium] = update.split(".").map(Number);
-
- return {
- ...this.getCriteriumResult(updatedCritResult.pageId, topic, criterium)!,
- status: CriteriumResultStatus.NOT_APPLICABLE,
- topic,
- criterium
- };
- });
+ linkedUpdates = linkedCriteria
+ .filter((c) =>
+ this.isInCriteriaList(c, auditStore.currentAudit!.auditType)
+ )
+ .map((update) => {
+ const [topic, criterium] = update.split(".").map(Number);
+
+ return {
+ ...this.getCriteriumResult(
+ updatedCritResult.pageId,
+ topic,
+ criterium
+ )!,
+ status: CriteriumResultStatus.NOT_APPLICABLE,
+ topic,
+ criterium
+ };
+ });
}
} else if (currentCritStatus === CriteriumResultStatus.NOT_APPLICABLE) {
// rollback old status on linked criteria
linkedCriteria.forEach((c) => {
const [topic, criterium] = c.split(".").map(Number);
- const u = this.previousLinkedCriteria[updatedCritResult.pageId]?.[topic]?.[criterium];
- const currentStatus = this.getCriteriumResult(updatedCritResult.pageId, topic, criterium)?.status;
-
- const addUpdate = u && (u.status === currentStatus || currentStatus === CriteriumResultStatus.NOT_APPLICABLE);
+ const u =
+ this.previousLinkedCriteria[updatedCritResult.pageId]?.[topic]?.[
+ criterium
+ ];
+ const currentStatus = this.getCriteriumResult(
+ updatedCritResult.pageId,
+ topic,
+ criterium
+ )?.status;
+
+ const addUpdate =
+ u &&
+ (u.status === currentStatus ||
+ currentStatus === CriteriumResultStatus.NOT_APPLICABLE);
if (addUpdate) {
linkedUpdates.push(u);
@@ -519,14 +566,20 @@ export const useResultsStore = defineStore("results", {
*/
isInCriteriaList(topicAndCriterium: string, auditType: AuditType): boolean {
const [topic, criterium] = topicAndCriterium.split(".").map(Number);
- return CRITERIA_BY_AUDIT_TYPE[auditType].some(cr => cr.topic === topic && cr.criterium === criterium);
+ return CRITERIA_BY_AUDIT_TYPE[auditType].some(
+ (cr) => cr.topic === topic && cr.criterium === criterium
+ );
},
/**
* @returns true if all page criteria are ≠ NOT_TESTED
*/
isPageCompleted(pageId: number): boolean {
- return this.allResults?.filter(r => r.pageId === pageId).every(r => r.status !== CriteriumResultStatus.NOT_TESTED) ?? false;
+ return (
+ this.allResults
+ ?.filter((r) => r.pageId === pageId)
+ .every((r) => r.status !== CriteriumResultStatus.NOT_TESTED) ?? false
+ );
},
/**
diff --git a/confiture-web-app/src/types/types.ts b/confiture-web-app/src/types/types.ts
index c8ea0496f..fbb499402 100644
--- a/confiture-web-app/src/types/types.ts
+++ b/confiture-web-app/src/types/types.ts
@@ -15,6 +15,7 @@ export interface AuditPage {
order: number;
name: string;
url: string;
+ slug: string;
}
export interface PageElements {
@@ -104,22 +105,24 @@ export type UpdateAuditRequestData = Omit & {
pages: Omit[];
};
-export type UpdateAuditStatementRequestData = paths["/audits/{editUniqueId}/statement"]["put"]["requestBody"]["content"]["application/json"];
+export type UpdateAuditStatementRequestData =
+ paths["/audits/{editUniqueId}/statement"]["put"]["requestBody"]["content"]["application/json"];
-export type CreateDebugAuditRequestData = paths["/debug/create-audit"]["post"]["requestBody"]["content"]["application/json"];
+export type CreateDebugAuditRequestData =
+ paths["/debug/create-audit"]["post"]["requestBody"]["content"]["application/json"];
-export enum CriteriumResultStatus {
- NOT_TESTED = "NOT_TESTED",
- COMPLIANT = "COMPLIANT",
- NOT_COMPLIANT = "NOT_COMPLIANT",
- NOT_APPLICABLE = "NOT_APPLICABLE"
-}
+export const CriteriumResultStatus = {
+ NOT_TESTED: "NOT_TESTED",
+ COMPLIANT: "COMPLIANT",
+ NOT_COMPLIANT: "NOT_COMPLIANT",
+ NOT_APPLICABLE: "NOT_APPLICABLE"
+};
-export enum CriterionResultUserImpact {
- MINOR = "MINOR",
- MAJOR = "MAJOR",
- BLOCKING = "BLOCKING"
-}
+export const CriterionResultUserImpact = {
+ MINOR: "MINOR",
+ MAJOR: "MAJOR",
+ BLOCKING: "BLOCKING"
+};
/** File attached to audit notes. */
export type NotesFile = components["schemas"]["NotesFileDto"];
@@ -134,16 +137,18 @@ export interface CriteriumResult {
pageId: number;
// DATA
- status: CriteriumResultStatus;
+ status: keyof typeof CriteriumResultStatus;
compliantComment: string | null;
notCompliantComment: string | null;
- userImpact: CriterionResultUserImpact | null;
+ userImpact: keyof typeof CriterionResultUserImpact | null;
notApplicableComment: string | null;
exampleImages: ExampleImageFile[];
quickWin: boolean;
}
+export type ApiCriteriumResult = components["schemas"]["CriterionResultDto"];
+
export enum StoreName {
AUDIT_STORE = "AUDIT_STORE",
RESULTS_STORE = "RESULTS_STORE"
@@ -170,3 +175,6 @@ export interface TabData {
component: object;
componentParams?: object;
}
+
+export type GetPageWithResultsDto =
+ components["schemas"]["GetPageWithResultsDto"];
diff --git a/confiture-web-app/src/utils.ts b/confiture-web-app/src/utils.ts
index 189fbcb8b..d0138c22f 100644
--- a/confiture-web-app/src/utils.ts
+++ b/confiture-web-app/src/utils.ts
@@ -5,6 +5,7 @@ import { noop } from "lodash-es";
import baseSlugify from "slugify";
import { FileListFile } from "./components/ui/FileList.vue";
+import router from "./router";
import {
AuditReport,
AuditStatus,
@@ -57,7 +58,7 @@ const FORMATTED_USER_IMPACT = {
* Format a criterion result user impact type string into French.
*/
export function formatUserImpact(
- userImpact: CriterionResultUserImpact | `${CriterionResultUserImpact}`
+ userImpact: keyof typeof CriterionResultUserImpact
): string {
return FORMATTED_USER_IMPACT[userImpact];
}
@@ -73,7 +74,7 @@ const FORMATTED_STATUS = {
* Format a criterion result status type string into French.
*/
export function formatStatus(
- status: CriteriumResultStatus | `${CriteriumResultStatus}`
+ status: keyof typeof CriteriumResultStatus
): string {
return FORMATTED_STATUS[status];
}
@@ -244,8 +245,10 @@ export function isTiptapDocumentEmpty(
return false;
}
- const containsImage = jsonString.includes("\"type\":\"image\"");
- const containsText = jsonString.matchAll(/"text":"(?[^"]+)?"/g).some(it => it.groups?.textContent.trim());
+ const containsImage = jsonString.includes('"type":"image"');
+ const containsText = jsonString
+ .matchAll(/"text":"(?[^"]+)?"/g)
+ .some((it) => it.groups?.textContent.trim());
return !containsImage && !containsText;
}
@@ -266,13 +269,15 @@ export function scrollToHash(hash: string) {
const initalTabIndex = hashEl.getAttribute("tabindex");
hashEl.setAttribute("tabindex", "-1");
hashEl.focus();
- initalTabIndex ? hashEl.setAttribute("tabindex", initalTabIndex) : hashEl.removeAttribute("tabindex");
+ initalTabIndex
+ ? hashEl.setAttribute("tabindex", initalTabIndex)
+ : hashEl.removeAttribute("tabindex");
hashEl.scrollIntoView();
}
}
export function sleep(ms: number) {
- return new Promise(resolve => setTimeout(resolve, ms));
+ return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
@@ -299,3 +304,16 @@ export function isImage(file: File | FileListFile) {
return file.mimetype.startsWith("image");
}
}
+
+export function showErrorPage(httpStatusCode?: number) {
+ const route = router.currentRoute;
+ router.replace({
+ name: "Error",
+ params: { pathMatch: route.value.path.substring(1).split("/") },
+ query: route.value.query,
+ hash: route.value.hash,
+ state: {
+ errorStatus: httpStatusCode
+ }
+ });
+}