From c587b4f5d142acdbe9b8ee73cd0c881c273b6024 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 10:31:07 -0700 Subject: [PATCH 01/25] :cherry_blossom: Add press enter keyboard shortcut to move down to next field in SectionTable.vue --- .../SectionTable.vue | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue b/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue index 6c785f64..6ebccb36 100644 --- a/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue +++ b/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue @@ -40,9 +40,11 @@ @@ -53,9 +55,11 @@ @@ -109,10 +113,13 @@ diff --git a/web/src/modules/centre/pages/CentreDashboardWorksheetsMonthlyWorksheetPage.vue b/web/src/modules/centre/pages/CentreDashboardWorksheetsMonthlyWorksheetPage.vue index ade91273..36e9be32 100644 --- a/web/src/modules/centre/pages/CentreDashboardWorksheetsMonthlyWorksheetPage.vue +++ b/web/src/modules/centre/pages/CentreDashboardWorksheetsMonthlyWorksheetPage.vue @@ -14,66 +14,28 @@ color="primary" size="small" class="mt-3" + :loading="isInitializing" @click="initializeWorksheetsForFiscalYear" >Add worksheets for {{ fiscalYear }} -
- Save - -

{{ dateName }} {{ calendarYear }}

- - mdi-content-copy Replicate Estimates - - -
-

{{ sectionName }}

- - -
-
+ /> diff --git a/web/src/use/use-funding-submission-line-json.ts b/web/src/use/use-funding-submission-line-json.ts new file mode 100644 index 00000000..d07b6847 --- /dev/null +++ b/web/src/use/use-funding-submission-line-json.ts @@ -0,0 +1,59 @@ +import { type Ref, reactive, toRefs, unref, watch } from "vue" +import { isNil } from "lodash" + +import fundingSubmissionLineJsonsApi, { + type FundingSubmissionLineJsonAsDetailed, +} from "@/api/funding-submission-line-jsons-api" + +export { type FundingSubmissionLineJsonAsDetailed } + +export function useFundingSubmissionLineJsonAsDetailed(id: Ref) { + const state = reactive<{ + fundingSubmissionLineJson: FundingSubmissionLineJsonAsDetailed | null + isLoading: boolean + isErrored: boolean + }>({ + fundingSubmissionLineJson: null, + isLoading: false, + isErrored: false, + }) + + async function fetch(): Promise { + const staticId = unref(id) + if (isNil(staticId)) { + throw new Error("id is required") + } + + state.isLoading = true + try { + const { fundingSubmissionLineJson } = await fundingSubmissionLineJsonsApi.get(staticId) + state.isErrored = false + state.fundingSubmissionLineJson = fundingSubmissionLineJson + return fundingSubmissionLineJson + } catch (error) { + console.error("Failed to fetch funding submission line json:", error) + state.isErrored = true + throw error + } finally { + state.isLoading = false + } + } + + watch( + () => unref(id), + async (newId) => { + if (isNil(newId)) return + + await fetch() + }, + { immediate: true } + ) + + return { + ...toRefs(state), + fetch, + refresh: fetch, + } +} + +export default useFundingSubmissionLineJsonAsDetailed From 8fcb529a8aff4a73456ab107c9d7e78ccce064e3 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 11:28:10 -0700 Subject: [PATCH 07/25] :truck: Standardize location of MonthlyWorksheetPage component. Pattern is pages/url/segment/ComponentName.vue e.g. /child-care-centres/1/2024-25/worksheets/may -> pages/child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage.vue Note: /child-care-centres/1/2024-25/worksheets/may should probably be /child-care-centres/some-center-slug/fiscal-years/2024-25/worksheets/may --- .../modules/centre/pages/CentreDashboardWorksheetsPage.vue | 4 ++-- web/src/modules/centre/routes.ts | 4 ++-- .../fiscal-years/worksheets/MonthlyWorksheetPage.vue} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename web/src/{modules/centre/pages/CentreDashboardWorksheetsMonthlyWorksheetPage.vue => pages/child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage.vue} (100%) diff --git a/web/src/modules/centre/pages/CentreDashboardWorksheetsPage.vue b/web/src/modules/centre/pages/CentreDashboardWorksheetsPage.vue index b78187a7..21989ce2 100644 --- a/web/src/modules/centre/pages/CentreDashboardWorksheetsPage.vue +++ b/web/src/modules/centre/pages/CentreDashboardWorksheetsPage.vue @@ -15,7 +15,7 @@ v-for="{ id, month } in fiscalPeriods" :key="id" :to="{ - name: 'CentreDashboardWorksheetsMonthlyWorksheetPage', + name: 'child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage', params: { centreId, fiscalYearSlug, @@ -67,7 +67,7 @@ watchEffect(async () => { } await router.push({ - name: "CentreDashboardWorksheetsMonthlyWorksheetPage", + name: "child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage", params: { centreId: props.centreId, fiscalYearSlug: props.fiscalYearSlug, diff --git a/web/src/modules/centre/routes.ts b/web/src/modules/centre/routes.ts index 5dcc3982..49ff5b94 100644 --- a/web/src/modules/centre/routes.ts +++ b/web/src/modules/centre/routes.ts @@ -84,10 +84,10 @@ const routes: RouteRecordRaw[] = [ children: [ { path: ":month", - name: "CentreDashboardWorksheetsMonthlyWorksheetPage", + name: "child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage", component: () => import( - "@/modules/centre/pages/CentreDashboardWorksheetsMonthlyWorksheetPage.vue" + "@/pages/child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage.vue" ), props: (route) => ({ centreId: parseInt(route.params.centreId as string), diff --git a/web/src/modules/centre/pages/CentreDashboardWorksheetsMonthlyWorksheetPage.vue b/web/src/pages/child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage.vue similarity index 100% rename from web/src/modules/centre/pages/CentreDashboardWorksheetsMonthlyWorksheetPage.vue rename to web/src/pages/child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage.vue From a655e54e16be1da18e8a18b42e43dd6eb08872bc Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 11:43:17 -0700 Subject: [PATCH 08/25] :pencil: Add pages readme, to describe a new convention for pages. --- web/src/pages/README.md | 60 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 web/src/pages/README.md diff --git a/web/src/pages/README.md b/web/src/pages/README.md new file mode 100644 index 00000000..8603d318 --- /dev/null +++ b/web/src/pages/README.md @@ -0,0 +1,60 @@ +# Pages + +Any component that is directly routeable is considered a page. +Pages are to be placed in the `pages` directory with a patch matching their URL. +The name of the page component should end in `Page`. + +The current naming conventions for pages is pretty random, but over time it will hopefully become more consistent and similar to this example. + +## Constraints + +1. It should take minimal effort to figure out where to add a new page component, and what to call if for a given user-centric URL. +2. It should be easy to add page variations. e.g. edit vs. non-edit +3. Route names must be unique. +4. It should be easy to find a component given the vue-router route name. +5. It should be easy to find the page component for a given URL. + +## Directory Structure + +For example + +```bash +src/ +├── pages/ +│ ├── child-care-centres/ +│ │ ├── CentreDashboardPage.vue +│ │ ├── worksheets/ +│ │ │ ├── MonthlyWorksheetsPage.vue +│ │ │ ├── MonthlyWorksheetPage.vue +``` + +## Routes + +For example + +```ts +const routes = [ + { + path: "/child-care-centres/:centreSlug/:fiscalYearSlug?", + name: "child-care-centres/CentreDashboardPage", + component: () => import("@/pages/child-care-centres/CentreDashboardPage.vue"), + props: true, + children: [ + { + path: "worksheets", + name: "child-care-centres/worksheets/MonthlyWorksheetsPage", + component: () => import("@/pages/child-care-centres/worksheets/MonthlyWorksheetsPage.vue"), + children: [ + { + path: ":month", + name: "child-care-centres/worksheets/MonthlyWorksheetPage", + component: () => + import("@/pages/child-care-centres/worksheets/MonthlyWorksheetPage.vue"), + props: true, + }, + ], + }, + ], + }, +] +``` From c95f63e9b1469bb1b4e8cae8b9929460e16e760d Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 11:43:39 -0700 Subject: [PATCH 09/25] :truck: Standardize the location and route name for the monthly worksheet page. Why? To make it easier to find page components. --- .../modules/centre/pages/CentreDashboardWorksheetsPage.vue | 4 ++-- web/src/modules/centre/routes.ts | 6 ++---- .../{fiscal-years => }/worksheets/MonthlyWorksheetPage.vue | 0 3 files changed, 4 insertions(+), 6 deletions(-) rename web/src/pages/child-care-centres/{fiscal-years => }/worksheets/MonthlyWorksheetPage.vue (100%) diff --git a/web/src/modules/centre/pages/CentreDashboardWorksheetsPage.vue b/web/src/modules/centre/pages/CentreDashboardWorksheetsPage.vue index 21989ce2..74406e26 100644 --- a/web/src/modules/centre/pages/CentreDashboardWorksheetsPage.vue +++ b/web/src/modules/centre/pages/CentreDashboardWorksheetsPage.vue @@ -15,7 +15,7 @@ v-for="{ id, month } in fiscalPeriods" :key="id" :to="{ - name: 'child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage', + name: 'child-care-centres/worksheets/MonthlyWorksheetPage', params: { centreId, fiscalYearSlug, @@ -67,7 +67,7 @@ watchEffect(async () => { } await router.push({ - name: "child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage", + name: "child-care-centres/worksheets/MonthlyWorksheetPage", params: { centreId: props.centreId, fiscalYearSlug: props.fiscalYearSlug, diff --git a/web/src/modules/centre/routes.ts b/web/src/modules/centre/routes.ts index 49ff5b94..bffdf835 100644 --- a/web/src/modules/centre/routes.ts +++ b/web/src/modules/centre/routes.ts @@ -84,11 +84,9 @@ const routes: RouteRecordRaw[] = [ children: [ { path: ":month", - name: "child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage", + name: "child-care-centres/worksheets/MonthlyWorksheetPage", component: () => - import( - "@/pages/child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage.vue" - ), + import("@/pages/child-care-centres/worksheets/MonthlyWorksheetPage.vue"), props: (route) => ({ centreId: parseInt(route.params.centreId as string), fiscalYearSlug: route.params.fiscalYearSlug, diff --git a/web/src/pages/child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage.vue b/web/src/pages/child-care-centres/worksheets/MonthlyWorksheetPage.vue similarity index 100% rename from web/src/pages/child-care-centres/fiscal-years/worksheets/MonthlyWorksheetPage.vue rename to web/src/pages/child-care-centres/worksheets/MonthlyWorksheetPage.vue From 79297baad6889b55e5b4e3600518972af0e44be2 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 11:45:22 -0700 Subject: [PATCH 10/25] :art: Add key to v-for loop in SectionTable.vue My linter now tells me about this kind of stuff :tada:! --- .../SectionTable.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue b/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue index 80d05593..e5a6c08f 100644 --- a/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue +++ b/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue @@ -30,6 +30,7 @@ {{ line.lineName }} From 55cbbc0e4c043ba197509932240b64c90d581ce9 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 13:26:00 -0700 Subject: [PATCH 11/25] :recycle: Basic go to next and previous section. --- .../FundingSubmissionLineJsonEditSheet.vue | 26 ++++++++- .../SectionTable.vue | 54 ++++++++++++++----- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue index 991935bd..14f4a91e 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue @@ -34,6 +34,8 @@ @@ -51,7 +53,9 @@ import fundingSubmissionLineJsonsApi, { import { useNotificationStore } from "@/store/NotificationStore" import useFundingSubmissionLineJson from "@/use/use-funding-submission-line-json" -import SectionTable from "@/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue" +import SectionTable, { + ColumnNames, +} from "@/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue" const FIRST_FISCAL_MONTH_NAME = "April" const notificationStore = useNotificationStore() @@ -140,4 +144,24 @@ function propagateUpdatesAsNeeded( sectionTables.value[2].refreshLineTotals(section2Line) } } + +function goToNextSection(sectionIndex: number, columnName: ColumnNames) { + if (sectionIndex < sections.value.length - 1) { + const nextIndex = sectionIndex + 1 + const nextSection = sectionTables.value[nextIndex] + if (isNil(nextSection)) return + + nextSection.focusOnFirstInColumn(columnName) + } +} + +function goToPreviousSection(sectionIndex: number, columnName: ColumnNames) { + if (sectionIndex > 0) { + const previousIndex = sectionIndex - 1 + const previousSection = sectionTables.value[previousIndex] + if (isNil(previousSection)) return + + previousSection.focusOnLastInColumn(columnName) + } +} diff --git a/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue b/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue index e5a6c08f..a5ed4c26 100644 --- a/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue +++ b/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue @@ -45,8 +45,8 @@ v-model.number="line.estimatedChildOccupancyRate" density="compact" hide-details - @keydown.enter="goToLowerField('estimatesFields', lineIndex)" - @keydown.shift.enter="goToHigherField('estimatesFields', lineIndex)" + @keydown.enter="focusOnNextInColumn('estimates', lineIndex)" + @keydown.shift.enter="focusOnPreviousInColumn('estimates', lineIndex)" @change="changeLineAndPropagate(line, lineIndex)" > @@ -61,8 +61,8 @@ v-model.number="line.actualChildOccupancyRate" density="compact" hide-details - @keydown.enter="goToLowerField('actualsFields', lineIndex)" - @keydown.shift.enter="goToHigherField('actualsFields', lineIndex)" + @keydown.enter="focusOnNextInColumn('actuals', lineIndex)" + @keydown.shift.enter="focusOnPreviousInColumn('actuals', lineIndex)" @change="changeLineAndPropagate(line, lineIndex)" > @@ -116,27 +116,31 @@ From 80500597aec6514ac47322e26cef178700f56112 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 13:18:07 -0700 Subject: [PATCH 12/25] :truck: Simplify location of funding submission line json component. Move component to a model based location for easier re-use. --- .../FundingSubmissionLineJsonEditSheet.vue | 4 ++-- .../FundingSubmissionLineJsonSectionTable.vue} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename web/src/{modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue => components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue} (100%) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue index 14f4a91e..6a17c145 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue @@ -54,8 +54,8 @@ import { useNotificationStore } from "@/store/NotificationStore" import useFundingSubmissionLineJson from "@/use/use-funding-submission-line-json" import SectionTable, { - ColumnNames, -} from "@/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue" + type ColumnNames, +} from "@/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue" const FIRST_FISCAL_MONTH_NAME = "April" const notificationStore = useNotificationStore() diff --git a/web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue similarity index 100% rename from web/src/modules/centre/components/centre-dashboard-worksheets-tab-monthly-worksheet-tab/SectionTable.vue rename to web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue From e014e5f942d48cfa3067866e40f301e41bcdf2ee Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 13:27:41 -0700 Subject: [PATCH 13/25] :japanese_castle: Merge event checks so you don't bounce at the top and bottom. --- .../FundingSubmissionLineJsonSectionTable.vue | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue index a5ed4c26..d6438007 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue @@ -45,8 +45,7 @@ v-model.number="line.estimatedChildOccupancyRate" density="compact" hide-details - @keydown.enter="focusOnNextInColumn('estimates', lineIndex)" - @keydown.shift.enter="focusOnPreviousInColumn('estimates', lineIndex)" + @keydown="changeFocusInColumn($event, 'estimates', lineIndex)" @change="changeLineAndPropagate(line, lineIndex)" > @@ -61,8 +60,7 @@ v-model.number="line.actualChildOccupancyRate" density="compact" hide-details - @keydown.enter="focusOnNextInColumn('actuals', lineIndex)" - @keydown.shift.enter="focusOnPreviousInColumn('actuals', lineIndex)" + @keydown="changeFocusInColumn($event, 'actuals', lineIndex)" @change="changeLineAndPropagate(line, lineIndex)" > @@ -156,6 +154,14 @@ function changeLineAndPropagate(line: FundingLineValue, lineIndex: number) { emit("lineChanged", { line, lineIndex }) } +function changeFocusInColumn(event: KeyboardEvent, columnName: ColumnNames, lineIndex: number) { + if (event.key === "Enter" && !event.shiftKey) { + focusOnNextInColumn(columnName, lineIndex) + } else if (event.key === "Enter" && event.shiftKey) { + focusOnPreviousInColumn(columnName, lineIndex) + } +} + function focusOnNextInColumn(columnName: ColumnNames, lineIndex: number) { const fields = fieldsMap[columnName] From 0c99d2c652ce518aed32a2fadaa1dbfdcfd71aa3 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 13:58:33 -0700 Subject: [PATCH 14/25] :sparkles: Basic arrow key navigation. NOTE: navigation only triggers when at the beginning or end of input text. --- .../FundingSubmissionLineJsonSectionTable.vue | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue index d6438007..5aafa806 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue @@ -155,10 +155,24 @@ function changeLineAndPropagate(line: FundingLineValue, lineIndex: number) { } function changeFocusInColumn(event: KeyboardEvent, columnName: ColumnNames, lineIndex: number) { + const input = event.target as HTMLInputElement | null + if (event.key === "Enter" && !event.shiftKey) { focusOnNextInColumn(columnName, lineIndex) } else if (event.key === "Enter" && event.shiftKey) { focusOnPreviousInColumn(columnName, lineIndex) + } else if (event.key === "ArrowUp") { + focusOnPreviousInColumn(columnName, lineIndex) + } else if (event.key === "ArrowDown") { + focusOnNextInColumn(columnName, lineIndex) + } else if (event.key === "ArrowLeft" && !isNil(input) && input.selectionStart === 0) { + focusOnPreviousColumn(columnName, lineIndex) + } else if ( + event.key === "ArrowRight" && + !isNil(input) && + input.selectionEnd === input.value.length + ) { + focusOnNextColumn(columnName, lineIndex) } } @@ -190,6 +204,18 @@ function focusOnPreviousInColumn(columnName: ColumnNames, lineIndex: number) { } } +function focusOnNextColumn(columnName: ColumnNames, lineIndex: number) { + if (columnName === "actuals") return + + focusOnNextInColumn("actuals", lineIndex - 1) +} + +function focusOnPreviousColumn(columnName: ColumnNames, lineIndex: number) { + if (columnName === "estimates") return + + focusOnNextInColumn("estimates", lineIndex - 1) +} + function focusOnFirstInColumn(columnName: ColumnNames) { const fields = fieldsMap[columnName] const field = fields[0] From 65fff3beb7f8d393c626fac577dfa862ed37de3f Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 14:03:55 -0700 Subject: [PATCH 15/25] :japanese_castle: Select text after focusing on the next/previous field. NOTE: this only works for "enter" due to the nature of the arrow events. --- .../FundingSubmissionLineJsonSectionTable.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue index 5aafa806..db3de36e 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue @@ -185,6 +185,7 @@ function focusOnNextInColumn(columnName: ColumnNames, lineIndex: number) { if (isNil(nextField)) return nextField.focus() + nextField.select() } else { emit("focusOnNextInColumn", columnName) } @@ -199,6 +200,7 @@ function focusOnPreviousInColumn(columnName: ColumnNames, lineIndex: number) { if (isNil(previousField)) return previousField.focus() + previousField.select() } else { emit("focusOnPreviousInColumn", columnName) } @@ -222,6 +224,7 @@ function focusOnFirstInColumn(columnName: ColumnNames) { if (isNil(field)) return field.focus() + field.select() } function focusOnLastInColumn(columnName: ColumnNames) { @@ -230,6 +233,7 @@ function focusOnLastInColumn(columnName: ColumnNames) { if (isNil(field)) return field.focus() + field.select() } defineExpose({ From 6f7e3b34f7397460a2e94331530fbeea4ccd1fe8 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 14:06:47 -0700 Subject: [PATCH 16/25] :art: Use more descriptive event names for some focus events. --- .../FundingSubmissionLineJsonEditSheet.vue | 4 ++-- .../FundingSubmissionLineJsonSectionTable.vue | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue index 6a17c145..d32cde40 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue @@ -34,8 +34,8 @@ diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue index db3de36e..0a581aca 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue @@ -128,8 +128,8 @@ const props = defineProps<{ const emit = defineEmits<{ lineChanged: [{ line: FundingLineValue; lineIndex: number }] - focusOnNextInColumn: [column: ColumnNames] - focusOnPreviousInColumn: [column: ColumnNames] + focusBeyondLastInColumn: [column: ColumnNames] + focusBeyondFirstInColumn: [column: ColumnNames] }>() const estimatesFields = ref([]) @@ -187,7 +187,7 @@ function focusOnNextInColumn(columnName: ColumnNames, lineIndex: number) { nextField.focus() nextField.select() } else { - emit("focusOnNextInColumn", columnName) + emit("focusBeyondLastInColumn", columnName) } } @@ -202,7 +202,7 @@ function focusOnPreviousInColumn(columnName: ColumnNames, lineIndex: number) { previousField.focus() previousField.select() } else { - emit("focusOnPreviousInColumn", columnName) + emit("focusBeyondFirstInColumn", columnName) } } From 1d12466ea1640823adf4f0c12077d666baa75de7 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 14:13:01 -0700 Subject: [PATCH 17/25] :cherry_blossom: Make focus follow the cursor when navigating with the keyboard. --- .../FundingSubmissionLineJsonSectionTable.vue | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue index 0a581aca..3c9fd7fb 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue @@ -184,8 +184,7 @@ function focusOnNextInColumn(columnName: ColumnNames, lineIndex: number) { const nextField = fields[nextIndex] if (isNil(nextField)) return - nextField.focus() - nextField.select() + focusOnField(nextField) } else { emit("focusBeyondLastInColumn", columnName) } @@ -199,8 +198,7 @@ function focusOnPreviousInColumn(columnName: ColumnNames, lineIndex: number) { const previousField = fields[previousIndex] if (isNil(previousField)) return - previousField.focus() - previousField.select() + focusOnField(previousField) } else { emit("focusBeyondFirstInColumn", columnName) } @@ -223,8 +221,7 @@ function focusOnFirstInColumn(columnName: ColumnNames) { const field = fields[0] if (isNil(field)) return - field.focus() - field.select() + focusOnField(field) } function focusOnLastInColumn(columnName: ColumnNames) { @@ -232,8 +229,17 @@ function focusOnLastInColumn(columnName: ColumnNames) { const field = fields[fields.length - 1] if (isNil(field)) return + focusOnField(field) +} + +function focusOnField(field: HTMLInputElement) { field.focus() field.select() + field.scrollIntoView({ + behavior: "smooth", // Scroll smoothly + block: "center", // Center vertically in the viewport + inline: "center", // Center horizontally if needed + }) } defineExpose({ From c25d33ee9a8bbcf28e25903a6b3f06219a13202f Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 14:27:19 -0700 Subject: [PATCH 18/25] :japanese_castle: Make arrow key navigation trigger focus. Arrow navigation on a selected text now drops the selection and moves to the beginning or end of the text. --- .../FundingSubmissionLineJsonSectionTable.vue | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue index 3c9fd7fb..bf09e3fb 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue @@ -156,22 +156,30 @@ function changeLineAndPropagate(line: FundingLineValue, lineIndex: number) { function changeFocusInColumn(event: KeyboardEvent, columnName: ColumnNames, lineIndex: number) { const input = event.target as HTMLInputElement | null + const isTextFullySelected = + input?.selectionStart === 0 && input.selectionEnd === input.value.length if (event.key === "Enter" && !event.shiftKey) { focusOnNextInColumn(columnName, lineIndex) } else if (event.key === "Enter" && event.shiftKey) { focusOnPreviousInColumn(columnName, lineIndex) } else if (event.key === "ArrowUp") { + event.preventDefault() focusOnPreviousInColumn(columnName, lineIndex) } else if (event.key === "ArrowDown") { + event.preventDefault() focusOnNextInColumn(columnName, lineIndex) - } else if (event.key === "ArrowLeft" && !isNil(input) && input.selectionStart === 0) { + } else if (event.key === "ArrowLeft" && isTextFullySelected) { + event.preventDefault() + input.setSelectionRange(0, 0) + } else if (event.key === "ArrowLeft" && input?.selectionStart === 0) { + event.preventDefault() focusOnPreviousColumn(columnName, lineIndex) - } else if ( - event.key === "ArrowRight" && - !isNil(input) && - input.selectionEnd === input.value.length - ) { + } else if (event.key === "ArrowRight" && isTextFullySelected) { + event.preventDefault() + input.setSelectionRange(input.value.length, input.value.length) + } else if (event.key === "ArrowRight" && input?.selectionEnd === input?.value.length) { + event.preventDefault() focusOnNextColumn(columnName, lineIndex) } } From 77a9e6de5254413adb5ae0a14bd2d19595a0683a Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 14:35:24 -0700 Subject: [PATCH 19/25] :bug: Fix styling of headers in worksheets. This style was missed when splitting the edit sheet out from the main page. --- .../FundingSubmissionLineJsonEditSheet.vue | 12 ++++++++++++ .../worksheets/MonthlyWorksheetPage.vue | 12 +----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue index d32cde40..81dd4aea 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue @@ -165,3 +165,15 @@ function goToPreviousSection(sectionIndex: number, columnName: ColumnNames) { } } + + diff --git a/web/src/pages/child-care-centres/worksheets/MonthlyWorksheetPage.vue b/web/src/pages/child-care-centres/worksheets/MonthlyWorksheetPage.vue index 36e9be32..4ca14976 100644 --- a/web/src/pages/child-care-centres/worksheets/MonthlyWorksheetPage.vue +++ b/web/src/pages/child-care-centres/worksheets/MonthlyWorksheetPage.vue @@ -81,14 +81,4 @@ async function initializeWorksheetsForFiscalYear() { } - + From 5ea67f48f15b1cfffcdab54c775066433d9cf01d Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 15:56:39 -0700 Subject: [PATCH 20/25] :art: Move notification store definition nearer to usage. --- .../FundingSubmissionLineJsonEditSheet.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue index 81dd4aea..63cd7261 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue @@ -58,7 +58,6 @@ import SectionTable, { } from "@/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue" const FIRST_FISCAL_MONTH_NAME = "April" -const notificationStore = useNotificationStore() const props = defineProps<{ fundingSubmissionLineJsonId: number @@ -93,6 +92,7 @@ const sections = computed<{ sectionName: string; lines: FundingLineValue[] }[]>( }) }) const sectionTables = ref[]>([]) +const notificationStore = useNotificationStore() async function saveFundingSubmissionLineJson() { isSaving.value = true From be40d227c5456d00523f0a62f70183a75fba0bdb Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 15:57:49 -0700 Subject: [PATCH 21/25] :cherry_blossom: Add keyboard shortcuts modal. Why? To make it easier for users to figure out the keyboard shortcuts available. --- .../common/KeyboardShortcutsModal.vue | 51 +++++++++++++++++++ .../FundingSubmissionLineJsonEditSheet.vue | 33 +++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 web/src/components/common/KeyboardShortcutsModal.vue diff --git a/web/src/components/common/KeyboardShortcutsModal.vue b/web/src/components/common/KeyboardShortcutsModal.vue new file mode 100644 index 00000000..a350d5cb --- /dev/null +++ b/web/src/components/common/KeyboardShortcutsModal.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue index 63cd7261..c490e2c4 100644 --- a/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue +++ b/web/src/components/funding-submission-line-jsons/FundingSubmissionLineJsonEditSheet.vue @@ -3,7 +3,10 @@ v-if="isLoading" type="table" /> - + -

{{ sectionName }}

+

+ {{ sectionName }} + + + mdi-keyboard + +

+
@@ -53,6 +67,7 @@ import fundingSubmissionLineJsonsApi, { import { useNotificationStore } from "@/store/NotificationStore" import useFundingSubmissionLineJson from "@/use/use-funding-submission-line-json" +import KeyboardShortcutsModal from "@/components/common/KeyboardShortcutsModal.vue" import SectionTable, { type ColumnNames, } from "@/components/funding-submission-line-jsons/FundingSubmissionLineJsonSectionTable.vue" @@ -164,6 +179,20 @@ function goToPreviousSection(sectionIndex: number, columnName: ColumnNames) { previousSection.focusOnLastInColumn(columnName) } } + +const keyboardShortcutsModal = ref | null>(null) + +function activateKeyboardShortcutsModalIfCorrectEvent(event: KeyboardEvent) { + if (event.ctrlKey && event.key === "/") { + showKeyboardShortcutsModal() + } +} + +function showKeyboardShortcutsModal() { + if (isNil(keyboardShortcutsModal.value)) return + + keyboardShortcutsModal.value.open() +} From a863e27e41d5bc2543ced8d457cd01922b394a12 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 16:33:12 -0700 Subject: [PATCH 23/25] :heavy_plus_sign: Add vueuse/router, because useRouteQuery is amazing. It's good for modals state and table queries stored in the url. --- web/package-lock.json | 110 ++++++++++++++++++++++++++++++++++++++++++ web/package.json | 1 + 2 files changed, 111 insertions(+) diff --git a/web/package-lock.json b/web/package-lock.json index 62908553..5c363d22 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@auth0/auth0-vue": "^2.0.0", "@mdi/font": "^7.1.96", + "@vueuse/router": "^11.1.0", "apexcharts": "^3.37.1", "axios": "^1.3.3", "lodash": "^4.17.21", @@ -1379,6 +1380,82 @@ "vuetify": "^3.0.0" } }, + "node_modules/@vueuse/router": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/router/-/router-11.1.0.tgz", + "integrity": "sha512-OjTNIOzX5jD1HDzmDApMke2QsCZ+gWKaydfndKJ3j9ttn41Pr11cQbSY6ZBp+bNjctAR+jhQBV/DGtL3iKpuHg==", + "dependencies": { + "@vueuse/shared": "11.1.0", + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue-router": ">=4.0.0-rc.1" + } + }, + "node_modules/@vueuse/router/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/shared": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.1.0.tgz", + "integrity": "sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==", + "dependencies": { + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -5004,6 +5081,39 @@ "upath": "^2.0.1" } }, + "@vueuse/router": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/router/-/router-11.1.0.tgz", + "integrity": "sha512-OjTNIOzX5jD1HDzmDApMke2QsCZ+gWKaydfndKJ3j9ttn41Pr11cQbSY6ZBp+bNjctAR+jhQBV/DGtL3iKpuHg==", + "requires": { + "@vueuse/shared": "11.1.0", + "vue-demi": ">=0.14.10" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "requires": {} + } + } + }, + "@vueuse/shared": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.1.0.tgz", + "integrity": "sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==", + "requires": { + "vue-demi": ">=0.14.10" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "requires": {} + } + } + }, "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", diff --git a/web/package.json b/web/package.json index 9af12df9..0bf48872 100644 --- a/web/package.json +++ b/web/package.json @@ -13,6 +13,7 @@ "dependencies": { "@auth0/auth0-vue": "^2.0.0", "@mdi/font": "^7.1.96", + "@vueuse/router": "^11.1.0", "apexcharts": "^3.37.1", "axios": "^1.3.3", "lodash": "^4.17.21", From 77595f9045e8c09f3d638f5098a180b1fdeee458 Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 4 Oct 2024 16:33:58 -0700 Subject: [PATCH 24/25] :cherry_blossom: Add modal state to url. Why? Accessibility and ease of debugging and texting. --- .../components/common/KeyboardShortcutsModal.vue | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/web/src/components/common/KeyboardShortcutsModal.vue b/web/src/components/common/KeyboardShortcutsModal.vue index c802dd58..6648586d 100644 --- a/web/src/components/common/KeyboardShortcutsModal.vue +++ b/web/src/components/common/KeyboardShortcutsModal.vue @@ -2,6 +2,7 @@
@@ -18,7 +19,6 @@ - @@ -153,9 +153,16 @@