From bf7dda2def34bdcaf2a083f79cd9894fd1caab55 Mon Sep 17 00:00:00 2001 From: Nishit Suwal <81785002+NSUWAL123@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:00:31 +0545 Subject: [PATCH] fix(mapper): task comment events for mapper frontend (#1871) * fix(activitiesPanel): optional chaining add * fix(events): commentTask func add for commenting tasks * fix(editor): ts types add, import path fix * fix(activities): replace dynamic data with static * fix(comment): replace dynamic data with static * fix(+page): comment eventCard and uncomment more component * fix(more): props fix, filter taskEvents & comments into seperate variable * fix(more/editor): style fix * fix(activities): zoomToTask functionality add * fix(types): TaskEventType type add * fix: ts types add * fix(comment): add comment editor if task selected only * fix(main): taskLayer opacity decrease, geojson property key change to state * fix(editor): uplift state of editor if setEditorRef present * fix(comment): clear editor content functionality add * fix(more): if no events i.e.activities or comments then show a message --- .../ProjectDetailsV2/ActivitiesPanel.tsx | 2 +- .../lib/components/dialog-task-actions.svelte | 2 +- .../src/lib/components/editor/editor.svelte | 26 +++-- src/mapper/src/lib/components/map/main.svelte | 16 ++- .../src/lib/components/more/activities.svelte | 52 +++++++-- .../src/lib/components/more/comment.svelte | 101 +++++++++++++----- .../src/lib/components/more/index.svelte | 52 +++++++-- src/mapper/src/lib/db/events.ts | 7 +- src/mapper/src/lib/types.ts | 12 +++ .../src/routes/[projectId]/+page.svelte | 36 +++---- src/mapper/src/store/tasks.svelte.ts | 7 +- 11 files changed, 228 insertions(+), 85 deletions(-) diff --git a/src/frontend/src/components/ProjectDetailsV2/ActivitiesPanel.tsx b/src/frontend/src/components/ProjectDetailsV2/ActivitiesPanel.tsx index 33b598a500..55e59d318d 100644 --- a/src/frontend/src/components/ProjectDetailsV2/ActivitiesPanel.tsx +++ b/src/frontend/src/components/ProjectDetailsV2/ActivitiesPanel.tsx @@ -32,7 +32,7 @@ const ActivitiesPanel = ({ defaultTheme, state, params, map }: activitiesPanelTy setAllActivities(projectTaskActivityList.length); let finalTaskEvents: projectTaskActivity[] = taskHistories.filter((task) => { - return task.username.replace(/\s+/g, '').toString().includes(searchText.toString()); + return task?.username?.replace(/\s+/g, '')?.toString().includes(searchText.toString()); }); if (searchText != '') { setTaskHistories(finalTaskEvents); diff --git a/src/mapper/src/lib/components/dialog-task-actions.svelte b/src/mapper/src/lib/components/dialog-task-actions.svelte index f17a2a8638..577c47d6c0 100644 --- a/src/mapper/src/lib/components/dialog-task-actions.svelte +++ b/src/mapper/src/lib/components/dialog-task-actions.svelte @@ -4,7 +4,7 @@ type Props = { isTaskActionModalOpen: boolean; - toggleTaskActionModal: (value: boolean) => {}; + toggleTaskActionModal: (value: boolean) => void; selectedTab: string; projectId: number; }; diff --git a/src/mapper/src/lib/components/editor/editor.svelte b/src/mapper/src/lib/components/editor/editor.svelte index 97e8dce2ac..ee18e2f922 100644 --- a/src/mapper/src/lib/components/editor/editor.svelte +++ b/src/mapper/src/lib/components/editor/editor.svelte @@ -1,14 +1,21 @@ - @@ -38,7 +46,7 @@ {/if} -
+
diff --git a/src/mapper/src/lib/components/map/main.svelte b/src/mapper/src/lib/components/map/main.svelte index e5c726d2f6..2372a4db9b 100644 --- a/src/mapper/src/lib/components/map/main.svelte +++ b/src/mapper/src/lib/components/map/main.svelte @@ -39,11 +39,12 @@ interface Props { projectOutlineCoords: Position[][]; entitiesUrl: string; - toggleTaskActionModal: (value: boolean) => {}; + toggleTaskActionModal: (value: boolean) => void; projectId: number; + setMapRef: (map: maplibregl.Map | undefined) => void; } - let { projectOutlineCoords, entitiesUrl, toggleTaskActionModal, projectId }: Props = $props(); + let { projectOutlineCoords, entitiesUrl, toggleTaskActionModal, projectId, setMapRef }: Props = $props(); const taskStore = getTaskStore(); const projectSetupStepStore = getProjectSetupStepStore(); @@ -58,6 +59,13 @@ projectSetupStep = +projectSetupStepStore.projectSetupStep; }); + // set the map ref to parent component + $effect(() => { + if (map) { + setMapRef(map); + } + }); + // Fit the map bounds to the project area $effect(() => { if (map && projectOutlineCoords) { @@ -160,7 +168,7 @@ '#40ac8c', '#c5fbf5', // default color if no match is found ], - 'fill-opacity': hoverStateFilter(0.1, 0), + 'fill-opacity': hoverStateFilter(0.3, 0), }} beforeLayerType="symbol" manageHoverState @@ -193,7 +201,7 @@ 'case', ['==', ['get', 'state'], 'LOCKED_FOR_MAPPING'], 'LOCKED_FOR_MAPPING', - ['==', ['get', 'status'], 'LOCKED_FOR_VALIDATION'], + ['==', ['get', 'state'], 'LOCKED_FOR_VALIDATION'], 'LOCKED_FOR_VALIDATION', '', ], diff --git a/src/mapper/src/lib/components/more/activities.svelte b/src/mapper/src/lib/components/more/activities.svelte index 450b2c3419..b70409d052 100644 --- a/src/mapper/src/lib/components/more/activities.svelte +++ b/src/mapper/src/lib/components/more/activities.svelte @@ -1,5 +1,16 @@ -
@@ -7,8 +18,14 @@ {#each Array.from({ length: 5 }) as _, index} {/each} + {:else if taskEvents?.length === 0} +
+

+ {taskStore?.selectedTaskId ? `No activities yet on task ${taskStore?.selectedTaskId}` : 'No activities yet'} +

+
{:else} - {#each Array.from({ length: 5 }) as _, index} + {#each taskEvents as event}
-

Localadmin

+

{event?.username}

-

#2

+

#{event?.task_id}

-

2024-10-21 11:42

+

+ + {event?.created_at?.split(' ')[0]} + + + {event?.created_at?.split(' ')[1]?.split('.')[0]} + +

- svcfmtm updated status to MAPPED + {event?.username} updated status to {event?.state}

- { + if (e.key === 'Enter') { + zoomToTask(event?.task_id); + } + }} + role="button" + tabindex="0" + onclick={() => { + zoomToTask(event?.task_id); + }} + name="map" + class="!text-[1rem] text-[#484848] hover:text-red-600 cursor-pointer duration-200" >
{/each} {/if}
- - diff --git a/src/mapper/src/lib/components/more/comment.svelte b/src/mapper/src/lib/components/more/comment.svelte index e9ddd9f22f..b377a22afc 100644 --- a/src/mapper/src/lib/components/more/comment.svelte +++ b/src/mapper/src/lib/components/more/comment.svelte @@ -1,16 +1,39 @@ -
-
+
{#if false} {#each Array.from({ length: 5 }) as _, index} {/each} + {:else if comments?.length === 0} +
+

+ {taskStore?.selectedTaskId ? `No comments yet on task ${taskStore?.selectedTaskId}` : 'No comments yet'} +

+
{:else} - {#each Array.from({ length: 5 }) as _, index} + {#each comments as comment}
-

Localadmin

+

{comment?.username}

-

#2

+

#{comment?.task_id}

-

2024-10-21 11:42

+

+ + {comment?.created_at?.split(' ')[0]} + + + {comment?.created_at?.split(' ')[1]?.split('.')[0]} + +

- This is a comment

'} /> +
{/each} {/if}
- -
- { - // to-do: store state to post comment - }} - /> -
- CLEAR - COMMENT + {#if taskStore.selectedTaskId} +
+ { + currentComment = editorText; + }} + setEditorRef={(editor) => { + editorRef = editor; + }} + /> +
+ { + editorRef?.commands.clearContent(true); + }} + onkeydown={() => {}} + role="button" + tabindex="0" + variant="default" + size="small" + class="secondary col-span-2 sm:col-span-1">CLEAR + { + commentTask(projectId, taskStore.selectedTaskId, currentComment); + editorRef?.commands.clearContent(true); + }} + onkeydown={() => {}} + role="button" + tabindex="0">COMMENT +
-
+ {/if}
- - diff --git a/src/mapper/src/lib/components/more/index.svelte b/src/mapper/src/lib/components/more/index.svelte index 9d2cc7ed08..e95566cb31 100644 --- a/src/mapper/src/lib/components/more/index.svelte +++ b/src/mapper/src/lib/components/more/index.svelte @@ -2,6 +2,8 @@ import Editor from '$lib/components/editor/editor.svelte'; import Comment from '$lib/components/more/comment.svelte'; import Activities from '$lib/components/more/activities.svelte'; + import { getTaskStore } from '$store/tasks.svelte.ts'; + import type { ProjectData, TaskEventType } from '$lib/types'; type stackType = '' | 'Comment' | 'Instructions' | 'Activities'; const stackGroup: { icon: string; title: stackType }[] = [ @@ -19,16 +21,47 @@ }, ]; - let activeStack: stackType = $state('Activities'); - let { instructions } = $props(); + type Props = { + projectData: ProjectData; + zoomToTask: (taskId: number) => void; + } + + let { projectData, zoomToTask }: Props = $props(); + const taskStore = getTaskStore(); + + let activeStack: stackType = $state(''); + let taskEvents: TaskEventType[] = $state([]); + let comments: TaskEventType[] = $state([]); + + $effect(() => { + if (!(taskStore?.events?.length > 0)) return; + + // if a task is selected, then apply filter to the events list + if (taskStore?.selectedTaskId) { + taskEvents = taskStore?.events?.filter( + (event: TaskEventType) => event.event !== 'COMMENT' && event.task_id === taskStore?.selectedTaskId, + ); + comments = taskStore?.events?.filter( + (event: TaskEventType) => event.event === 'COMMENT' && event.task_id === taskStore?.selectedTaskId, + ); + } else { + taskEvents = taskStore?.events?.filter((event: TaskEventType) => event.event !== 'COMMENT'); + comments = taskStore?.events?.filter((event: TaskEventType) => event.event === 'COMMENT'); + } + }); -
+
{#if activeStack === ''} {#each stackGroup as stack}
(activeStack = stack.title)} + onkeydown={(e) => { + if (e.key === 'Enter') activeStack = stack.title; + }} + tabindex="0" + role="button" >
@@ -41,24 +74,29 @@ {#if activeStack !== ''} -
+
(activeStack = '')} + onkeydown={(e: KeyboardEvent) => { + if (e.key === 'Enter') activeStack = ''; + }} + tabindex="0" + role="button" >

{activeStack}

{/if} {#if activeStack === 'Comment'} - + {/if} {#if activeStack === 'Instructions'} - + {/if} {#if activeStack === 'Activities'} - + {/if}
diff --git a/src/mapper/src/lib/db/events.ts b/src/mapper/src/lib/db/events.ts index 9985570f21..5b8493de37 100644 --- a/src/mapper/src/lib/db/events.ts +++ b/src/mapper/src/lib/db/events.ts @@ -10,7 +10,7 @@ async function add_event( taskId: number, // userId: number, eventType: TaskEvent, - // comment: string = '', + comment: string | null = null, // ): Promise { ): Promise { // const eventId = uuidv4() @@ -18,6 +18,7 @@ async function add_event( event_id: uuidv4(), event: eventType, task_id: taskId, + comment: comment, }; const resp = await fetch(`${API_URL}/tasks/${taskId}/event/?project_id=${projectId}`, { method: 'POST', @@ -62,6 +63,10 @@ export async function resetTask(/* db, */ projectId: number, taskId: number): Pr await add_event(/* db, */ projectId, taskId, TaskEventEnum.BAD); } +export async function commentTask(/* db, */ projectId: number, taskId: number, comment: string): Promise { + await add_event(/* db, */ projectId, taskId, 'COMMENT', comment); +} + // async function finishTask(db, projectId: number, taskId: number, userId: number): Promise { // // const query = ` // // WITH last AS ( diff --git a/src/mapper/src/lib/types.ts b/src/mapper/src/lib/types.ts index 72609f5273..20fbe4965a 100644 --- a/src/mapper/src/lib/types.ts +++ b/src/mapper/src/lib/types.ts @@ -112,3 +112,15 @@ export type NewEvent = { task_id: number; comment?: string | null; }; + +export type TaskEventType = { + comment: string | null; + created_at: string; + event: TaskEvent | 'COMMENT'; + event_id: string; + project_id: number; + state: TaskStatus | null; + task_id: number; + user_id: number; + username: string; +}; diff --git a/src/mapper/src/routes/[projectId]/+page.svelte b/src/mapper/src/routes/[projectId]/+page.svelte index aa6e37ac12..ab4b09de8b 100644 --- a/src/mapper/src/routes/[projectId]/+page.svelte +++ b/src/mapper/src/routes/[projectId]/+page.svelte @@ -10,23 +10,13 @@ import type { MapLibre } from 'svelte-maplibre'; import SlTabGroup from '@shoelace-style/shoelace/dist/components/tab-group/tab-group.component.js'; - import Error from './+error.svelte'; - - import EventCard from '$lib/components/event-card.svelte'; + // import EventCard from '$lib/components/event-card.svelte'; import BottomSheet from '$lib/components/bottom-sheet.svelte'; import TaskActionDialog from '$lib/components/task-action-dialog.svelte'; import MapComponent from '$lib/components/map/main.svelte'; import DialogTaskActions from '$lib/components/dialog-task-actions.svelte'; - import type { ProjectData, ProjectTask, ZoomToTaskEventDetail } from '$lib/types'; - import { - mapTask, - finishTask, - resetTask, - // validateTask, - // goodTask, - // commentTask, - } from '$lib/db/events'; + import type { ProjectTask, ZoomToTaskEventDetail } from '$lib/types'; import { generateQrCode, downloadQrCode } from '$lib/utils/qrcode'; import { convertDateToTimeAgo } from '$lib/utils/datetime'; import { getTaskStore, getTaskEventStream } from '$store/tasks.svelte.ts'; @@ -47,7 +37,7 @@ let { data }: Props = $props(); // $effect: ({ electric, project } = data) - let mapComponent: MapLibre; + let mapComponent: maplibregl.Map | undefined = $state(undefined); let tabGroup: SlTabGroup; let selectedTab: string = $state('map'); let isTaskActionModalOpen = $state(false); @@ -70,8 +60,7 @@ let qrCodeData = $derived(generateQrCode(data.project.name, data.project.odk_token, 'REPLACE_ME_WITH_A_USERNAME')); - function zoomToTask(event: CustomEvent) { - const taskId = event.detail.taskId; + function zoomToTask(taskId: number) { const taskObj = data.project.tasks.find((task: ProjectTask) => task.id === taskId); if (!taskObj) return; @@ -81,9 +70,9 @@ const taskPolygon = polygon(taskObj.outline.coordinates); const taskBuffer = buffer(taskPolygon, 5, { units: 'meters' }); - if (taskBuffer && mapComponent.map) { + if (taskBuffer && mapComponent) { const taskBbox: [number, number, number, number] = bbox(taskBuffer) as [number, number, number, number]; - mapComponent.map.fitBounds(taskBbox, { duration: 500 }); + mapComponent.fitBounds(taskBbox, { duration: 500 }); } // Open the map tab @@ -138,7 +127,9 @@
{ + mapComponent = map; + }} toggleTaskActionModal={(value) => { isTaskActionModalOpen = value; }} @@ -159,7 +150,7 @@ {#if selectedTab !== 'map'} tabGroup.show('map')}> {#if selectedTab === 'events'} - {#if taskStore.events.length > 0} + - - + zoomToTask(taskId)} /> {/if} {#if selectedTab === 'offline'} Coming soon! @@ -213,7 +203,7 @@ class="z-9999 fixed bottom-0 left-0 right-0" placement="bottom" no-scroll-controls - onsl-tab-show={(e) => { + onsl-tab-show={(e: CustomEvent<{ name: string }>) => { selectedTab = e.detail.name; if ( e.detail.name !== 'qrcode' && diff --git a/src/mapper/src/store/tasks.svelte.ts b/src/mapper/src/store/tasks.svelte.ts index e6843a031a..74a8250528 100644 --- a/src/mapper/src/store/tasks.svelte.ts +++ b/src/mapper/src/store/tasks.svelte.ts @@ -2,12 +2,12 @@ import { ShapeStream, Shape } from '@electric-sql/client'; import type { ShapeData, Row } from '@electric-sql/client'; import type { GeoJSON } from 'geosjon'; -import type { ProjectTask } from '$lib/types'; +import type { ProjectTask, TaskEventType } from '$lib/types'; let taskEventShape: Shape; let featcol = $state({ type: 'FeatureCollection', features: [] }); let latestEvent = $state(null); -let events = $state([]); +let events: TaskEventType[] = $state([]); let selectedTaskId: number | null = $state(null); let selectedTask: any = $state(null); let selectedTaskState: string = $state(''); @@ -57,8 +57,7 @@ function getTaskStore() { async function getLatestStatePerTask() { const taskEventData: ShapeData = await taskEventShape.value; - const taskEventRows = Array.from(taskEventData.values()); - + const taskEventRows = Array.from(taskEventData.values()) as TaskEventType[]; // Update the events in taskStore events = taskEventRows;