From 149bee383b18545481eaeb7dcf4714c53627ad78 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 27 May 2024 16:15:41 +0800 Subject: [PATCH 1/7] Updating hooks --- hosting/src/hooks/useTanamDocumentTypes.tsx | 7 ++- hosting/src/hooks/useTanamDocuments.tsx | 62 +++++++++++++++++++-- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/hosting/src/hooks/useTanamDocumentTypes.tsx b/hosting/src/hooks/useTanamDocumentTypes.tsx index cadc85e3..b0cdac40 100644 --- a/hosting/src/hooks/useTanamDocumentTypes.tsx +++ b/hosting/src/hooks/useTanamDocumentTypes.tsx @@ -62,7 +62,10 @@ export function useTanamDocumentTypes(): TanamDocumentTypeHook { * @return {SingleTanamDocumentTypeHook} Hook for single document type subscription */ export function useTanamDocumentType(documentTypeId?: string): SingleTanamDocumentTypeHook { - const {site, type: paramType} = useParams<{site: string; type: string}>() ?? {site: null, type: null}; + const {site, documentTypeId: paramType} = useParams<{site: string; documentTypeId: string}>() ?? { + site: null, + documentTypeId: null, + }; const typeId = documentTypeId ?? paramType; const [data, setData] = useState(null); const [error, setError] = useState(null); @@ -73,7 +76,7 @@ export function useTanamDocumentType(documentTypeId?: string): SingleTanamDocume return; } if (!typeId) { - setError(new Error("Document type ID parameter is missing")); + setData(null); return; } diff --git a/hosting/src/hooks/useTanamDocuments.tsx b/hosting/src/hooks/useTanamDocuments.tsx index 6f8e7333..6a731cee 100644 --- a/hosting/src/hooks/useTanamDocuments.tsx +++ b/hosting/src/hooks/useTanamDocuments.tsx @@ -1,6 +1,6 @@ import {firestore} from "@/firebase"; import {TanamDocument} from "@/models/TanamDocument"; -import {collection, onSnapshot, query, where} from "firebase/firestore"; +import {collection, doc, onSnapshot, query, where} from "firebase/firestore"; import {useParams} from "next/navigation"; import {useEffect, useState} from "react"; @@ -12,12 +12,15 @@ interface UseTanamDocumentsResult { /** * Hook to get a stream of documents of a specific content type * - * @param {string?} documentType Optional document type (default to content parameter from URL). + * @param {string?} documentTypeId Optional document type (default to content parameter from URL). * @return {UseTanamDocumentsResult} Hook for documents subscription */ -export function useTanamDocuments(documentType?: string): UseTanamDocumentsResult { - const {site, type: paramType} = useParams<{site: string; type: string}>() ?? {site: null, content: null}; - const type = documentType ?? paramType; +export function useTanamDocuments(documentTypeId?: string): UseTanamDocumentsResult { + const {site, documentTypeId: paramType} = useParams<{site: string; documentTypeId: string}>() ?? { + site: null, + documentTypeId: null, + }; + const type = documentTypeId ?? paramType; const [data, setData] = useState([]); const [error, setError] = useState(null); @@ -56,3 +59,52 @@ export function useTanamDocuments(documentType?: string): UseTanamDocumentsResul return {data, error}; } + +interface UseTanamDocumentResult { + data: TanamDocument | null; + error: Error | null; +} + +/** + * Hook to get a subscription for a single document + * + * @param {string?} documentId Optional document id (default to content parameter from URL). + * @return {UseTanamDocumentsResult} Hook for document subscription + */ +export function useTanamDocument(documentId?: string): UseTanamDocumentResult { + const {site, documentId: paramId} = useParams<{site: string; documentId: string}>() ?? {site: null, documentId: null}; + const id = documentId ?? paramId; + const [data, setData] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + if (!site) { + setError(new Error("Site parameter is missing")); + return; + } + if (!id) { + setError(new Error("Document id parameter is missing")); + return; + } + const docRef = doc(firestore, "tanam", site, "documents", id); + const unsubscribe = onSnapshot( + docRef, + (snapshot) => { + setData( + TanamDocument.fromJson({ + id: snapshot.id, + ...snapshot.data(), + }), + ); + }, + (err) => { + setError(err); + }, + ); + + // Cleanup subscription on unmount + return () => unsubscribe(); + }, [site, paramId]); + + return {data, error}; +} From e269e86578d55356d171fa86de17e6ba3b8a9dbc Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 27 May 2024 16:15:49 +0800 Subject: [PATCH 2/7] Updating models --- hosting/src/models/TanamDocumentField.ts | 43 ++++++++++++++++++++++++ hosting/src/models/TanamDocumentType.ts | 31 +++++++++++++---- 2 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 hosting/src/models/TanamDocumentField.ts diff --git a/hosting/src/models/TanamDocumentField.ts b/hosting/src/models/TanamDocumentField.ts new file mode 100644 index 00000000..669f696d --- /dev/null +++ b/hosting/src/models/TanamDocumentField.ts @@ -0,0 +1,43 @@ +/** + * Tanam document field model. + */ +export class TanamDocumentField { + /** + * Constructor. + * + * @param {string} key The key of the field + * @param {string} title The title of the field + * @param {string} type The type of the field + * @param {string[] | null} validators The validators for the field + */ + constructor( + public readonly key: string, + public readonly title: string, + public readonly type: string, + public readonly validators: string[] | null, + ) {} + + /** + * Static factory constructor. + * + * @param {any} json JSON representation of the field + * @return {TanamDocumentField} Document field instance + */ + static fromJson(json: any): TanamDocumentField { + return new TanamDocumentField(json.key, json.title, json.type, json.validators); + } + + /** + * Serialize to JSON for Firestore. + * + * @return {any} JSON representation of the document field + */ + toJson(): any { + return { + key: this.key, + title: this.title, + type: this.type, + validators: this.validators, + }; + } +} diff --git a/hosting/src/models/TanamDocumentType.ts b/hosting/src/models/TanamDocumentType.ts index d503092d..51cdb694 100644 --- a/hosting/src/models/TanamDocumentType.ts +++ b/hosting/src/models/TanamDocumentType.ts @@ -1,3 +1,5 @@ +import {TanamDocumentField} from "@/models/TanamDocumentField"; + /** * Tanam document type model. */ @@ -5,12 +7,18 @@ export class TanamDocumentType { /** * Constructor. * - * @param {string} id document type ID - * @param {string} title Document type title + * @param {string} id Document type ID + * @param {string} titleSingular Document type title in singular + * @param {string} titlePlural Document type title in plural + * @param {string} documentTitleField The document data field to use as the title + * @param {TanamDocumentField[]} fields Array of fields in the document type */ constructor( public readonly id: string, - public title: string, + public titleSingular: string, + public titlePlural: string, + public documentTitleField: string, + public fields: TanamDocumentField[], ) {} /** @@ -20,18 +28,29 @@ export class TanamDocumentType { * @return {TanamDocumentType} Document type instance */ static fromJson(json: any): TanamDocumentType { - return new TanamDocumentType(json.id, json.title); + const fields: TanamDocumentField[] = json.fields.map((field: any) => TanamDocumentField.fromJson(field)); + + return new TanamDocumentType( + json.id, + json.titleSingular || json.title, + json.titlePlural || json.title, + json.documentTitleField, + fields, + ); } /** - * Serialize to JSON for Firestore + * Serialize to JSON for Firestore. * * @return {any} JSON representation of the document type */ toJson(): any { return { id: this.id, - title: this.title, + titleSingular: this.titleSingular, + titlePlural: this.titlePlural, + documentTitleField: this.documentTitleField, + fields: this.fields.map((field) => field.toJson()), }; } } From 4fc7f68a43384b436134c3106be101af934fa090 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 27 May 2024 16:15:59 +0800 Subject: [PATCH 3/7] Renaming Notification compoenent --- hosting/src/components/common/{Alerts.tsx => Notification.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename hosting/src/components/common/{Alerts.tsx => Notification.tsx} (100%) diff --git a/hosting/src/components/common/Alerts.tsx b/hosting/src/components/common/Notification.tsx similarity index 100% rename from hosting/src/components/common/Alerts.tsx rename to hosting/src/components/common/Notification.tsx From 8c841541216693296890d016a54c3f0faaa81ff1 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 27 May 2024 16:16:09 +0800 Subject: [PATCH 4/7] Adding default value to date picker --- hosting/src/components/Form/DatePicker.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/hosting/src/components/Form/DatePicker.tsx b/hosting/src/components/Form/DatePicker.tsx index e017c3ac..1464f218 100644 --- a/hosting/src/components/Form/DatePicker.tsx +++ b/hosting/src/components/Form/DatePicker.tsx @@ -6,6 +6,7 @@ import {BaseOptions} from "flatpickr/dist/types/options"; interface DatePickerProps { label: string; placeholder: string; + defaultValue?: Date; onChange?: (date: Date) => void; styleType?: "default" | "static" | "withArrows"; disabled?: boolean; @@ -16,10 +17,18 @@ interface DatePickerProps { * @param {DatePickerProps} props - The properties for the date picker component. * @return {JSX.Element} The rendered date picker component. */ -export function DatePicker({label, placeholder, onChange, styleType = "default", disabled = false}: DatePickerProps) { +export function DatePicker({ + label, + placeholder, + defaultValue, + onChange, + styleType = "default", + disabled = false, +}: DatePickerProps) { const config = { mode: "single", dateFormat: "M j, Y", + defaultDate: defaultValue, onChange: (selectedDates: Date[]) => { if (onChange && selectedDates.length > 0) { onChange(selectedDates[0]); @@ -53,7 +62,7 @@ export function DatePicker({label, placeholder, onChange, styleType = "default", fp.destroy(); } }; - }, [onChange, styleType]); + }, [onChange, styleType, defaultValue]); return (
From 8d6c932b8ca1c7fdfa24310e517e4760d0668404 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 27 May 2024 16:16:20 +0800 Subject: [PATCH 5/7] Updating links in side bar --- hosting/src/components/Sidebar/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosting/src/components/Sidebar/index.tsx b/hosting/src/components/Sidebar/index.tsx index 5478df9c..7eb62d74 100644 --- a/hosting/src/components/Sidebar/index.tsx +++ b/hosting/src/components/Sidebar/index.tsx @@ -90,8 +90,8 @@ const Sidebar = ({sidebarOpen, setSidebarOpen}: SidebarProps) => { title="Content" isExpanded={pathname.includes("/content/")} menuItems={documentTypes.map((doc) => ({ - href: `/${site?.id}/content/${doc.id}`, - title: doc.title, + href: `/${site?.id}/document-type/${doc.id}`, + title: doc.titleSingular, }))} /> } title="Settings" /> From 459e8458c80a3de60aaca66b2f8c426985e79f00 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 27 May 2024 16:16:39 +0800 Subject: [PATCH 6/7] Moving document type listing page --- .../src/app/[site]/content/[type]/page.tsx | 47 --------------- .../document-type/[documentTypeId]/page.tsx | 59 +++++++++++++++++++ 2 files changed, 59 insertions(+), 47 deletions(-) delete mode 100644 hosting/src/app/[site]/content/[type]/page.tsx create mode 100644 hosting/src/app/[site]/document-type/[documentTypeId]/page.tsx diff --git a/hosting/src/app/[site]/content/[type]/page.tsx b/hosting/src/app/[site]/content/[type]/page.tsx deleted file mode 100644 index e1689a20..00000000 --- a/hosting/src/app/[site]/content/[type]/page.tsx +++ /dev/null @@ -1,47 +0,0 @@ -"use client"; -import React from "react"; -import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; -import {Table, TableRowActions, TableRowLabel} from "@/components/Table"; -import DefaultLayout from "@/components/Layouts/DefaultLayout"; -import {useTanamDocuments} from "@/hooks/useTanamDocuments"; -import Alerts from "@/components/common/Alerts"; - -import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; -import Loader from "../../../../components/common/Loader"; - -export default function ContentOverviewPage() { - const {data: documents, error: docsError} = useTanamDocuments(); - const {data: documentType} = useTanamDocumentType(); - return ( - - {documentType ? : } - - {docsError ? ( - - ) : ( - [ -
-
{document.id}
-
, -

- {document.createdAt.toDate().toUTCString()} -

, - , - console.log("View", document)} - onDelete={() => console.log("Delete", document)} - onDownload={() => console.log("Download", document)} - />, - ])} - /> - )} - - ); -} diff --git a/hosting/src/app/[site]/document-type/[documentTypeId]/page.tsx b/hosting/src/app/[site]/document-type/[documentTypeId]/page.tsx new file mode 100644 index 00000000..e8df35f7 --- /dev/null +++ b/hosting/src/app/[site]/document-type/[documentTypeId]/page.tsx @@ -0,0 +1,59 @@ +"use client"; +import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; +import DefaultLayout from "@/components/Layouts/DefaultLayout"; +import {Table, TableRowActions, TableRowLabel} from "@/components/Table"; +import Loader from "@/components/common/Loader"; +import Notification from "@/components/common/Notification"; +import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; +import {useTanamDocuments} from "@/hooks/useTanamDocuments"; +import {useTanamSite} from "@/hooks/useTanamSite"; +import {useRouter} from "next/navigation"; +import {Suspense} from "react"; + +export default function DocumentTypeDocumentsPage() { + const {data: documents, error: docsError} = useTanamDocuments(); + const {data: tanamSite} = useTanamSite(); + const {data: documentType} = useTanamDocumentType(); + const router = useRouter(); + + const handleView = (documentId: string) => { + router.push(`/${tanamSite?.id}/document/${documentId}`); + }; + + return ( + + }> + {documentType ? : } + + + {docsError ? ( + + ) : ( + }> +
[ +
+
{document.id}
+
, +

+ {document.createdAt.toDate().toUTCString()} +

, + , + handleView(document.id)} + onDelete={() => console.log("Delete", document)} + onDownload={() => console.log("Download", document)} + />, + ])} + /> + + )} + + ); +} From 61bbe6e6d7c423d989feeeee0b86ff1285cf3245 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 27 May 2024 16:16:48 +0800 Subject: [PATCH 7/7] Adding single document details page --- .../app/[site]/document/[documentId]/page.tsx | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 hosting/src/app/[site]/document/[documentId]/page.tsx diff --git a/hosting/src/app/[site]/document/[documentId]/page.tsx b/hosting/src/app/[site]/document/[documentId]/page.tsx new file mode 100644 index 00000000..2d7f7b98 --- /dev/null +++ b/hosting/src/app/[site]/document/[documentId]/page.tsx @@ -0,0 +1,96 @@ +"use client"; +import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; +import ContentCard from "@/components/Containers/ContentCard"; +import { + Checkbox, + DatePicker, + Dropdown, + FileUpload, + FormGroup, + Input, + RadioButton, + Switcher, + TextArea, +} from "@/components/Form"; +import DefaultLayout from "@/components/Layouts/DefaultLayout"; +import Loader from "@/components/common/Loader"; +import Notification from "@/components/common/Notification"; +import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; +import {useTanamDocument} from "@/hooks/useTanamDocuments"; +import {TanamDocumentField} from "@/models/TanamDocumentField"; +import {Timestamp} from "firebase/firestore"; +import {Suspense} from "react"; + +const DocumentDetailsPage = () => { + const {data: document, error: docError} = useTanamDocument(); + const {data: documentType, error: typeError} = useTanamDocumentType(document?.documentType); + const viewMode = true; + + const renderFormElement = (field: TanamDocumentField, value: any) => { + switch (field.type) { + case "input-text": + return ( + + + + ); + case "textbox-rich": + return ( + +