diff --git a/.eslintrc.js b/.eslintrc.js index 87c63fac4..1554564c4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -53,7 +53,7 @@ module.exports = { } ], "no-unused-vars": "off", - "react-hooks/exhaustive-deps": "warn", + "react-hooks/exhaustive-deps": "error", "prettier/prettier": [ "error", {}, diff --git a/package.json b/package.json index 19c8ecd99..a781cfd09 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "nookies": "^2.5.2", "prettier-plugin-tailwindcss": "^0.2.2", "query-string": "^7.1.1", + "quill": "2.0.3", "ra-input-rich-text": "^4.12.2", "react": "18.2.0", "react-admin": "^4.7.4", diff --git a/public/images/Impact Story - Landing Page.png b/public/images/Impact Story - Landing Page.png new file mode 100644 index 000000000..9813ef1f2 Binary files /dev/null and b/public/images/Impact Story - Landing Page.png differ diff --git a/public/images/impact-story-1.png b/public/images/impact-story-1.png new file mode 100644 index 000000000..c63d41ceb Binary files /dev/null and b/public/images/impact-story-1.png differ diff --git a/public/images/impact-story-2.png b/public/images/impact-story-2.png new file mode 100644 index 000000000..f3f1d2324 Binary files /dev/null and b/public/images/impact-story-2.png differ diff --git a/public/images/mask.png b/public/images/mask.png new file mode 100644 index 000000000..d669a3cbb Binary files /dev/null and b/public/images/mask.png differ diff --git a/public/images/maskRight.png b/public/images/maskRight.png new file mode 100644 index 000000000..8b04f7acf Binary files /dev/null and b/public/images/maskRight.png differ diff --git a/src/admin/apiProvider/authProvider.ts b/src/admin/apiProvider/authProvider.ts index 0b44e787e..05c3aabde 100644 --- a/src/admin/apiProvider/authProvider.ts +++ b/src/admin/apiProvider/authProvider.ts @@ -1,9 +1,11 @@ -import { AuthProvider } from "react-admin"; +import { AuthProvider, UserIdentity } from "react-admin"; import { loadLogin, logout } from "@/connections/Login"; import { loadMyUser } from "@/connections/User"; import Log from "@/utils/log"; +export type TMUserIdentity = UserIdentity & { primaryRole: string }; + export const authProvider: AuthProvider = { login: async () => { Log.error("Admin app does not support direct login"); @@ -29,7 +31,7 @@ export const authProvider: AuthProvider = { const { user } = await loadMyUser(); if (user == null) throw "No user logged in."; - return { id: user.uuid, fullName: user.fullName ?? undefined, primaryRole: user.primaryRole }; + return { id: user.uuid, fullName: user.fullName ?? undefined, primaryRole: user.primaryRole } as TMUserIdentity; }, // get the user permissions (optional) diff --git a/src/admin/apiProvider/dataProviders/fundingProgrammeDataProvider.ts b/src/admin/apiProvider/dataProviders/fundingProgrammeDataProvider.ts index f7da66c1e..e53ef68a4 100644 --- a/src/admin/apiProvider/dataProviders/fundingProgrammeDataProvider.ts +++ b/src/admin/apiProvider/dataProviders/fundingProgrammeDataProvider.ts @@ -152,7 +152,6 @@ export const fundingProgrammeDataProvider: FundingDataProvider = { } }); } - await handleUploads(params, uploadKeys, { //@ts-ignore uuid: resp.data.uuid, diff --git a/src/admin/apiProvider/dataProviders/impactStoriesDataProvider.ts b/src/admin/apiProvider/dataProviders/impactStoriesDataProvider.ts new file mode 100644 index 000000000..87e4203ea --- /dev/null +++ b/src/admin/apiProvider/dataProviders/impactStoriesDataProvider.ts @@ -0,0 +1,115 @@ +import lo from "lodash"; +import { DataProvider } from "react-admin"; + +import { + DeleteV2AdminImpactStoriesIdError, + fetchDeleteV2AdminImpactStoriesId, + fetchGetV2AdminImpactStories, + fetchGetV2AdminImpactStoriesId, + fetchPostV2AdminImpactStories, + fetchPostV2AdminImpactStoriesBulkDelete, + fetchPutV2AdminImpactStoriesId, + GetV2AdminImpactStoriesError, + GetV2AdminImpactStoriesIdError, + PostV2AdminImpactStoriesBulkDeleteError, + PostV2AdminImpactStoriesError, + PutV2AdminImpactStoriesIdError +} from "@/generated/apiComponents"; + +import { getFormattedErrorForRA } from "../utils/error"; +import { apiListResponseToRAListResult, raListParamsToQueryParams } from "../utils/listing"; +import { handleUploads } from "../utils/upload"; + +// @ts-ignore +export const impactStoriesDataProvider: DataProvider = { + async getList(_, params) { + try { + const response = await fetchGetV2AdminImpactStories({ + queryParams: raListParamsToQueryParams(params, []) + }); + return apiListResponseToRAListResult(response); + } catch (err) { + throw getFormattedErrorForRA(err as GetV2AdminImpactStoriesError); + } + }, + // @ts-ignore + async getOne(_, params) { + try { + const list = await fetchGetV2AdminImpactStoriesId({ + pathParams: { id: params.id } + }); + const response = { data: list }; + //@ts-ignore + return { data: { ...response.data, id: response.data.id } }; + } catch (err) { + throw getFormattedErrorForRA(err as GetV2AdminImpactStoriesIdError); + } + }, + //@ts-ignore + async create(__, params) { + const uploadKeys = ["thumbnail"]; + const body: any = lo.omit(params.data, uploadKeys); + try { + const response = await fetchPostV2AdminImpactStories({ + body: body + }); + // @ts-expect-error + const uuid = response.data.uuid as string; + await handleUploads(params, uploadKeys, { + uuid, + model: "impact-story" + }); + // @ts-expect-error + return { data: { ...response.data, id: response.id } }; + } catch (err) { + throw getFormattedErrorForRA(err as PostV2AdminImpactStoriesError); + } + }, + //@ts-ignore + async update(__, params) { + const uuid = params.id as string; + const uploadKeys = ["thumbnail"]; + const body = lo.omit(params.data, uploadKeys); + + try { + await handleUploads(params, uploadKeys, { + uuid, + model: "impact-story" + }); + + const response = await fetchPutV2AdminImpactStoriesId({ + body, + pathParams: { id: uuid } + }); + + console.log("Params", params.data); + // @ts-expect-error + return { data: { ...response.data, id: response.data.uuid } }; + } catch (err) { + throw getFormattedErrorForRA(err as PutV2AdminImpactStoriesIdError); + } + }, + + //@ts-ignore + async delete(__, params) { + try { + await fetchDeleteV2AdminImpactStoriesId({ + pathParams: { id: params.id as string } + }); + return { data: { id: params.id } }; + } catch (err) { + throw getFormattedErrorForRA(err as DeleteV2AdminImpactStoriesIdError); + } + }, + // @ts-ignore + async deleteMany(_, params) { + try { + await fetchPostV2AdminImpactStoriesBulkDelete({ + body: { uuids: params.ids.map(String) } + }); + return { data: params.ids }; + } catch (err) { + throw getFormattedErrorForRA(err as PostV2AdminImpactStoriesBulkDeleteError); + } + } +}; diff --git a/src/admin/apiProvider/dataProviders/index.ts b/src/admin/apiProvider/dataProviders/index.ts index 3ca5cfc3f..328aee823 100644 --- a/src/admin/apiProvider/dataProviders/index.ts +++ b/src/admin/apiProvider/dataProviders/index.ts @@ -8,6 +8,7 @@ import { applicationDataProvider } from "./applicationDataProvider"; import { auditDataProvider } from "./auditDataProvider"; import { formDataProvider } from "./formDataProvider"; import { fundingProgrammeDataProvider } from "./fundingProgrammeDataProvider"; +import { impactStoriesDataProvider } from "./impactStoriesDataProvider"; import { nurseryDataProvider } from "./nurseryDataProvider"; import { nurseryReportDataProvider } from "./nurseryReportDataProvider"; import { organisationDataProvider } from "./organisationDataProvider"; @@ -70,6 +71,9 @@ export const dataProvider = combineDataProviders(resource => { case modules.audit.ResourceName: return auditDataProvider; + case modules.impactStories.ResourceName: + return impactStoriesDataProvider; + default: throw new Error(`Unknown resource: ${resource}`); } diff --git a/src/admin/apiProvider/dataProviders/organisationDataProvider.ts b/src/admin/apiProvider/dataProviders/organisationDataProvider.ts index c59a8f835..6ff056a43 100644 --- a/src/admin/apiProvider/dataProviders/organisationDataProvider.ts +++ b/src/admin/apiProvider/dataProviders/organisationDataProvider.ts @@ -107,7 +107,6 @@ export const organisationDataProvider: OrganisationDataProvider = { const uuid = params.id as string; const uploadKeys = ["logo", "cover", "legal_registration", "reference", "additional"]; const body = lo.omit(params.data, uploadKeys); - await handleUploads(params, uploadKeys, { uuid, model: "organisation" diff --git a/src/admin/components/Actions/ListActionsImpactStories.tsx b/src/admin/components/Actions/ListActionsImpactStories.tsx new file mode 100644 index 000000000..986021379 --- /dev/null +++ b/src/admin/components/Actions/ListActionsImpactStories.tsx @@ -0,0 +1,19 @@ +import DownloadIcon from "@mui/icons-material/GetApp"; +import { Button, CreateButton, FilterButton, TopToolbar } from "react-admin"; +import { When } from "react-if"; + +interface ListActionsProps { + onExport?: () => void; +} + +const ListActionsImpactStories = (props: ListActionsProps) => ( + + + + - } - > - {title} - {progressMessage ?? "Running 0 out of 0 polygons (0%)"} - - - ); -}; - -export default DelayedJobsProgressAlert; diff --git a/src/admin/components/App.tsx b/src/admin/components/App.tsx index 2a07ea914..fb936d702 100644 --- a/src/admin/components/App.tsx +++ b/src/admin/components/App.tsx @@ -36,6 +36,7 @@ const App = () => { edit={modules.user.Edit} create={modules.user.Create} icon={() => } + recordRepresentation={record => `${record?.first_name} ${record?.last_name}`} /> { show={modules.organisation.Show} edit={modules.organisation.Edit} icon={() => } + recordRepresentation={record => record?.name} /> { show={modules.pitch.Show} edit={modules.pitch.Edit} icon={() => } + recordRepresentation={record => record?.project_name} /> { create={modules.fundingProgramme.Create} icon={() => } options={{ label: "Funding Programmes" }} + recordRepresentation={record => `Funding Programme "${record?.name}"`} /> { list={modules.application.List} show={modules.application.Show} icon={() => } + recordRepresentation={record => `${record?.id}`} /> { edit={modules.form.Edit} icon={() => } create={modules.form.Create} + recordRepresentation={record => record?.project_name} /> )} @@ -96,6 +102,7 @@ const App = () => { show={modules.project.Show} edit={modules.project.Edit} icon={() => } + recordRepresentation={record => record?.name ?? ""} /> { show={modules.site.Show} edit={modules.site.Edit} icon={() => } + recordRepresentation={record => record?.name ?? ""} /> { show={modules.nursery.Show} edit={modules.nursery.Edit} icon={() => } + recordRepresentation={record => record?.name ?? ""} /> { show={modules.task.Show} icon={SummarizeIcon} options={{ label: "Tasks" }} + recordRepresentation={record => record?.project?.name} /> { edit={modules.projectReport.Edit} icon={() => } options={{ label: "Project Reports" }} + recordRepresentation={record => record?.title} /> { edit={modules.siteReport.Edit} icon={() => } options={{ label: "Site Reports" }} + recordRepresentation={record => record?.title} /> { edit={modules.nurseryReport.Edit} icon={() => } options={{ label: "Nursery Reports" }} + recordRepresentation={record => record?.title} /> {isAdmin && ( <> @@ -153,6 +166,14 @@ const App = () => { /> )} + } + options={{ label: "Impact Stories" }} + /> ); diff --git a/src/admin/components/AppBar.tsx b/src/admin/components/AppBar.tsx index 574d689df..d691ae61f 100644 --- a/src/admin/components/AppBar.tsx +++ b/src/admin/components/AppBar.tsx @@ -1,14 +1,28 @@ -import { Typography } from "@mui/material"; -import { AppBar as RaAppBar, AppBarProps, Link } from "react-admin"; +import { AppBar as RaAppBar, AppBarProps, Link, Logout, MenuItemLink, UserMenu } from "react-admin"; -export const AppBar = (props: AppBarProps) => ( - -
- -
- -
+import Icon, { IconNames } from "@/components/extensive/Icon/Icon"; +export const AppBar = (props: AppBarProps) => { + const CustomUserMenu = (props: any) => ( + + } + onClick={() => { + window.location.href = "/dashboard"; + }} + /> + + + ); - - -); + return ( + }> +
+ +
+ +
+ + ); +}; diff --git a/src/admin/components/AppMenu.tsx b/src/admin/components/AppMenu.tsx index fc99bb471..30867b678 100644 --- a/src/admin/components/AppMenu.tsx +++ b/src/admin/components/AppMenu.tsx @@ -60,6 +60,9 @@ const AppMenu = () => {
+
+ +
); }; diff --git a/src/admin/components/Dialogs/FrameworkSelectionDialog.tsx b/src/admin/components/Dialogs/FrameworkSelectionDialog.tsx index 8d09ab237..49fd06ce8 100644 --- a/src/admin/components/Dialogs/FrameworkSelectionDialog.tsx +++ b/src/admin/components/Dialogs/FrameworkSelectionDialog.tsx @@ -84,7 +84,7 @@ export function useFrameworkExport(entity: EntityName, choices: any[]) { setModalOpen(false); }, - [entity, choices] + [entity, role] ); return { @@ -95,7 +95,7 @@ export function useFrameworkExport(entity: EntityName, choices: any[]) { } else { onExport(choices[0].id); } - }, [choices]), + }, [choices, onExport]), frameworkDialogProps: { open: modalOpen, onCancel: useCallback(() => setModalOpen(false), []), diff --git a/src/admin/components/EntityEdit/EntityEdit.tsx b/src/admin/components/EntityEdit/EntityEdit.tsx index a7f271d0c..a98f3cbb3 100644 --- a/src/admin/components/EntityEdit/EntityEdit.tsx +++ b/src/admin/components/EntityEdit/EntityEdit.tsx @@ -1,4 +1,5 @@ import { notFound } from "next/navigation"; +import { useMemo } from "react"; import { useCreatePath, useResourceContext } from "react-admin"; import { useNavigate, useParams } from "react-router-dom"; @@ -7,7 +8,7 @@ import WizardForm from "@/components/extensive/WizardForm"; import LoadingContainer from "@/components/generic/Loading/LoadingContainer"; import EntityProvider from "@/context/entity.provider"; import FrameworkProvider, { Framework } from "@/context/framework.provider"; -import { GetV2FormsENTITYUUIDResponse, useGetV2FormsENTITYUUID } from "@/generated/apiComponents"; +import { GetV2FormsENTITYUUIDResponse, useGetV2ENTITYUUID, useGetV2FormsENTITYUUID } from "@/generated/apiComponents"; import { normalizedFormData } from "@/helpers/customForms"; import { pluralEntityNameToSingular } from "@/helpers/entity"; import { useFormUpdate } from "@/hooks/useFormUpdate"; @@ -44,6 +45,8 @@ export const EntityEdit = () => { isError: loadError } = useGetV2FormsENTITYUUID({ pathParams: { entity: entityName, uuid: entityUUID } }); + const { data: entityValue } = useGetV2ENTITYUUID({ pathParams: { entity: entityName, uuid: entityUUID } }); + // @ts-ignore const formData = (formResponse?.data ?? {}) as GetV2FormsENTITYUUIDResponse; @@ -67,6 +70,15 @@ export const EntityEdit = () => { return notFound(); } + const bannerTitle = useMemo(() => { + if (entityName === "site-reports") { + return `${entityValue?.data?.site?.name} ${title}`; + } else if (entityName === "nursery-reports") { + return `${entityValue?.data?.nursery?.name} ${title}`; + } + return title; + }, [entityName, entityValue, title]); + return (
@@ -80,7 +92,7 @@ export const EntityEdit = () => { formStatus={isSuccess ? "saved" : isUpdating ? "saving" : undefined} onSubmit={() => navigate(createPath({ resource, id, type: "show" }))} defaultValues={defaultValues} - title={title} + title={bannerTitle} tabOptions={{ markDone: true, disableFutureTabs: true diff --git a/src/admin/components/Fields/ChipFieldArray.tsx b/src/admin/components/Fields/ChipFieldArray.tsx new file mode 100644 index 000000000..a48b74a4c --- /dev/null +++ b/src/admin/components/Fields/ChipFieldArray.tsx @@ -0,0 +1,38 @@ +import classNames from "classnames"; +import React from "react"; +import { ArrayField, ArrayFieldProps, ChipField, FunctionField, SingleFieldList } from "react-admin"; + +interface ChipFieldArrayProps extends Omit { + data: { id: string; label: string; className?: string }[]; + emptyText?: string; +} + +const ChipFieldArray: React.FC = ({ data, emptyText, ...props }) => { + if (!data.length) { + return ( +
+ {emptyText ?? "Not Provided"} +
+ ); + } + + return ( + + + + record ? ( + + ) : null + } + /> + + + ); +}; + +export default ChipFieldArray; diff --git a/src/admin/components/Fields/MapField.tsx b/src/admin/components/Fields/MapField.tsx index b110c5163..e282d4564 100644 --- a/src/admin/components/Fields/MapField.tsx +++ b/src/admin/components/Fields/MapField.tsx @@ -34,16 +34,17 @@ const MapField = ({ source, emptyText = "Not Provided" }: MapFieldProps) => { } ); - const setBbboxAndZoom = async () => { - if (projectPolygon?.project_polygon?.poly_uuid) { - const bbox = await fetchGetV2TerrafundPolygonBboxUuid({ - pathParams: { uuid: projectPolygon.project_polygon?.poly_uuid } - }); - const bounds: any = bbox.bbox; - setPolygonBbox(bounds); - } - }; useEffect(() => { + const setBbboxAndZoom = async () => { + if (projectPolygon?.project_polygon?.poly_uuid) { + const bbox = await fetchGetV2TerrafundPolygonBboxUuid({ + pathParams: { uuid: projectPolygon.project_polygon?.poly_uuid } + }); + const bounds: any = bbox.bbox; + setPolygonBbox(bounds); + } + }; + const getDataProjectPolygon = async () => { if (!projectPolygon?.project_polygon) { setPolygonDataMap({ [FORM_POLYGONS]: [] }); diff --git a/src/admin/components/ResourceTabs/AuditLogTab/AuditLogTab.tsx b/src/admin/components/ResourceTabs/AuditLogTab/AuditLogTab.tsx index 0dcc8dc09..9d365b30a 100644 --- a/src/admin/components/ResourceTabs/AuditLogTab/AuditLogTab.tsx +++ b/src/admin/components/ResourceTabs/AuditLogTab/AuditLogTab.tsx @@ -57,6 +57,7 @@ const AuditLogTab: FC = ({ label, entity, ...rest }) => { useEffect(() => { refetch(); loadEntityList(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [buttonToggle]); const isSite = buttonToggle === AuditLogButtonStates.SITE; diff --git a/src/admin/components/ResourceTabs/ChangeRequestsTab/ChangeRequestsTab.tsx b/src/admin/components/ResourceTabs/ChangeRequestsTab/ChangeRequestsTab.tsx index 67fba8d43..7a2225294 100644 --- a/src/admin/components/ResourceTabs/ChangeRequestsTab/ChangeRequestsTab.tsx +++ b/src/admin/components/ResourceTabs/ChangeRequestsTab/ChangeRequestsTab.tsx @@ -56,7 +56,10 @@ const ChangeRequestsTab: FC = ({ label, entity, singularEntity, ...rest // @ts-ignore const form = currentValues?.data?.form; - const formSteps = useMemo(() => (form == null ? [] : getCustomFormSteps(form, t, undefined, framework)), [form, t]); + const formSteps = useMemo( + () => (form == null ? [] : getCustomFormSteps(form, t, undefined, framework)), + [form, framework, t] + ); const formChanges = useFormChanges(current, changes, formSteps ?? []); const numFieldsAffected = useMemo( () => diff --git a/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/QuickActions.tsx b/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/QuickActions.tsx index 896d1e261..de47cee3a 100644 --- a/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/QuickActions.tsx +++ b/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/QuickActions.tsx @@ -27,9 +27,14 @@ const QuickActions: FC = () => { } }).then((response: any) => { if (entity === "shapefiles") { - const jsonString = JSON.stringify(response, null, 2); - const fileBlob = new Blob([jsonString], { type: "application/geo+json" }); - downloadFileBlob(fileBlob, `${record.name}_polygons.geojson`); + const exportName = `${record.name}_polygons.geojson`; + if (response instanceof Blob) { + downloadFileBlob(response, exportName); + } else { + const jsonString = JSON.stringify(response, null, 2); + const fileBlob = new Blob([jsonString], { type: "application/geo+json" }); + downloadFileBlob(fileBlob, exportName); + } } else { downloadFileBlob(response, `${record.name} ${entity.replace("-reports", "")} reports.csv`); } diff --git a/src/admin/components/ResourceTabs/InformationTab/components/ReportInformationAside/HighLevelMetrics.tsx b/src/admin/components/ResourceTabs/InformationTab/components/ReportInformationAside/HighLevelMetrics.tsx index 54cfd7036..34dbfbf40 100644 --- a/src/admin/components/ResourceTabs/InformationTab/components/ReportInformationAside/HighLevelMetrics.tsx +++ b/src/admin/components/ResourceTabs/InformationTab/components/ReportInformationAside/HighLevelMetrics.tsx @@ -56,7 +56,7 @@ const HighLevelMetics: FC = () => { diff --git a/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx b/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx index 36599b0a1..67d7bebb5 100644 --- a/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx +++ b/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx @@ -656,7 +656,7 @@ const DataCard = ({ if (selectPolygonFromMap?.isOpen) { setSelectPolygonFromMap?.({ isOpen: false, uuid: "" }); } - }, [selectPolygonFromMap]); + }, [selectPolygonFromMap, setSelectPolygonFromMap]); const dateRunIndicator = polygonsIndicator?.[polygonsIndicator.length - 1] ? format(new Date(polygonsIndicator?.[polygonsIndicator.length - 1]?.created_at!), "dd/MM/yyyy") diff --git a/src/admin/components/ResourceTabs/MonitoredTab/components/MonitoredCharts.tsx b/src/admin/components/ResourceTabs/MonitoredTab/components/MonitoredCharts.tsx index db9f8afb7..53fd944ff 100644 --- a/src/admin/components/ResourceTabs/MonitoredTab/components/MonitoredCharts.tsx +++ b/src/admin/components/ResourceTabs/MonitoredTab/components/MonitoredCharts.tsx @@ -6,13 +6,15 @@ import { When } from "react-if"; import SimpleBarChart from "@/pages/dashboard/charts/SimpleBarChart"; import GraphicIconDashboard from "@/pages/dashboard/components/GraphicIconDashboard"; import SecDashboard from "@/pages/dashboard/components/SecDashboard"; -import { TOTAL_HECTARES_UNDER_RESTORATION_TOOLTIP } from "@/pages/dashboard/constants/tooltips"; import EcoRegionDoughnutChart from "./EcoRegionDoughnutChart"; import { LoadingState } from "./MonitoredLoading"; import { NoDataState } from "./NoDataState"; import TreeLossBarChart from "./TreesLossBarChart"; +const TOTAL_HECTARES_UNDER_RESTORATION_TOOLTIP = + "Total land area measured in hectares with active restoration interventions, tallied by the total area of polygons submitted by projects."; + const ChartContainer = ({ children, isLoading, diff --git a/src/admin/components/ResourceTabs/MonitoredTab/hooks/useMonitoredData.ts b/src/admin/components/ResourceTabs/MonitoredTab/hooks/useMonitoredData.ts index 00d374bd9..4a96ac3b4 100644 --- a/src/admin/components/ResourceTabs/MonitoredTab/hooks/useMonitoredData.ts +++ b/src/admin/components/ResourceTabs/MonitoredTab/hooks/useMonitoredData.ts @@ -234,16 +234,16 @@ export const useMonitoredData = (entity?: EntityName, entity_uuid?: string) => { ? totalPolygonsApproved : totalPolygonsApproved! - Object?.keys(dataToMissingPolygonVerify ?? {})?.length; - const verifySlug = async (slug: string) => - fetchGetV2IndicatorsEntityUuidSlugVerify({ - pathParams: { - entity: entity!, - uuid: entity_uuid!, - slug: slug! - } - }); - useEffect(() => { + const verifySlug = async (slug: string) => + fetchGetV2IndicatorsEntityUuidSlugVerify({ + pathParams: { + entity: entity!, + uuid: entity_uuid!, + slug: slug! + } + }); + const fetchSlugs = async () => { setIsLoadingVerify(true); const slugVerify = await Promise.all(SLUGS_INDICATORS.map(verifySlug)); @@ -269,13 +269,13 @@ export const useMonitoredData = (entity?: EntityName, entity_uuid?: string) => { }); }; setAnalysisToSlug(slugToAnalysis); - await setDropdownAnalysisOptions(updateTitleDropdownOptions); + setDropdownAnalysisOptions(updateTitleDropdownOptions); setIsLoadingVerify(false); }; if (modalOpened(ModalId.MODAL_RUN_ANALYSIS)) { fetchSlugs(); } - }, [entity]); + }, [entity, entity_uuid, modalOpened]); return { polygonsIndicator: filteredPolygons, diff --git a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/PolygonDrawer.tsx b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/PolygonDrawer.tsx index 213efaaa2..8c69703e0 100644 --- a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/PolygonDrawer.tsx +++ b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/PolygonDrawer.tsx @@ -170,6 +170,7 @@ const PolygonDrawer = ({ showLoader(); getValidations({ queryParams: { uuid: polygonSelected } }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [checkPolygonValidation]); useEffect(() => { @@ -195,7 +196,7 @@ const PolygonDrawer = ({ setSelectedPolygonData({}); setStatusSelectedPolygon(""); } - }, [polygonSelected, sitePolygonData]); + }, [polygonSelected, setStatusSelectedPolygon, sitePolygonData]); useEffect(() => { if (openEditNewPolygon) { setButtonToogle(true); @@ -230,7 +231,7 @@ const PolygonDrawer = ({ fetchCriteriaValidation(); setSelectPolygonVersion(selectedPolygonData); - }, [buttonToogle, selectedPolygonData]); + }, [buttonToogle, polygonSelected, selectedPolygonData]); const { data: polygonVersions, @@ -252,13 +253,13 @@ const PolygonDrawer = ({ setIsLoadingDropdown(false); }; onLoading(); - }, [isOpenPolygonDrawer]); + }, [isOpenPolygonDrawer, refetchPolygonVersions]); useEffect(() => { if (selectedPolygonData && isEmpty(selectedPolygonData as SitePolygon) && isEmpty(polygonSelected)) { setSelectedPolygonData(selectPolygonVersion); } - }, [selectPolygonVersion]); + }, [polygonSelected, selectPolygonVersion, selectedPolygonData]); const runFixPolygonOverlaps = () => { if (polygonSelected) { diff --git a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/components/PolygonValidation.tsx b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/components/PolygonValidation.tsx index 1cb2c8a8b..6bf203020 100644 --- a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/components/PolygonValidation.tsx +++ b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/components/PolygonValidation.tsx @@ -99,7 +99,7 @@ const PolygonValidation = (props: ICriteriaCheckProps) => { Last check at {formattedDate(lastValidationDate)} -
+
{menu.map(item => (
diff --git a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/components/VersionHistory.tsx b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/components/VersionHistory.tsx index bb8af6a2c..058d852c3 100644 --- a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/components/VersionHistory.tsx +++ b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/components/VersionHistory.tsx @@ -53,7 +53,7 @@ const VersionHistory = ({ setStatusSelectedPolygon?: any; data: GetV2SitePolygonUuidVersionsResponse | []; isLoadingVersions: boolean; - refetch: () => void; + refetch: () => Promise; isLoadingDropdown: boolean; setIsLoadingDropdown: Dispatch>; setPolygonFromMap: Dispatch>; @@ -72,6 +72,7 @@ const VersionHistory = ({ useEffect(() => { refetch(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectPolygonVersion]); useEffect(() => { @@ -79,6 +80,7 @@ const VersionHistory = ({ uploadFiles(); setSaveFlags(false); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [files, saveFlags]); const getFileType = (file: UploadedFile) => { @@ -305,7 +307,7 @@ const VersionHistory = ({ }; reloadVersionList(); } - }, [polygonFromMap]); + }, [polygonFromMap, refetch, setIsLoadingDropdown]); return (
diff --git a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonItem.tsx b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonItem.tsx index 6a431a492..d95c7d50f 100644 --- a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonItem.tsx +++ b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonItem.tsx @@ -66,7 +66,7 @@ const PolygonItem = ({ } else if (criteriaData?.criteria_list && criteriaData.criteria_list.length === 0) { setValidationStatus("notChecked"); } - }, [polygonMap]); + }, [polygonMap, uuid]); const handleCheckboxClick = () => { onCheckboxChange(uuid, !isChecked); diff --git a/src/admin/components/ResourceTabs/PolygonReviewTab/index.tsx b/src/admin/components/ResourceTabs/PolygonReviewTab/index.tsx index 7550902de..e40e77e37 100644 --- a/src/admin/components/ResourceTabs/PolygonReviewTab/index.tsx +++ b/src/admin/components/ResourceTabs/PolygonReviewTab/index.tsx @@ -3,7 +3,7 @@ import Box from "@mui/material/Box"; import LinearProgress from "@mui/material/LinearProgress"; import { useT } from "@transifex/react"; import { LngLatBoundsLike } from "mapbox-gl"; -import { FC, useEffect, useState } from "react"; +import { FC, useCallback, useEffect, useState } from "react"; import { TabbedShowLayout, TabProps, useShowContext } from "react-admin"; import { Else, If, Then } from "react-if"; @@ -167,16 +167,39 @@ const PolygonReviewTab: FC = props => { const { openNotification } = useNotificationContext(); + const onSave = (geojson: any, record: any) => { + storePolygon(geojson, record, refetch, setPolygonFromMap, refreshEntity); + }; + const mapFunctions = useMap(onSave); + + const flyToPolygonBounds = useCallback( + async (uuid: string) => { + const bbox: PolygonBboxResponse = await fetchGetV2TerrafundPolygonBboxUuid({ pathParams: { uuid } }); + const bboxArray = bbox?.bbox; + const { map } = mapFunctions; + if (bboxArray && map?.current) { + const bounds: LngLatBoundsLike = [ + [bboxArray[0], bboxArray[1]], + [bboxArray[2], bboxArray[3]] + ]; + map.current.fitBounds(bounds, { + padding: 100, + linear: false + }); + } else { + Log.error("Bounding box is not in the expected format"); + } + }, + [mapFunctions] + ); + useEffect(() => { if (selectPolygonFromMap?.uuid) { setPolygonFromMap(selectPolygonFromMap); flyToPolygonBounds(selectPolygonFromMap.uuid); } - }, [polygonList]); - const onSave = (geojson: any, record: any) => { - storePolygon(geojson, record, refetch, setPolygonFromMap, refreshEntity); - }; - const mapFunctions = useMap(onSave); + }, [flyToPolygonBounds, polygonList, selectPolygonFromMap]); + const { data: sitePolygonData, refetch, @@ -234,24 +257,6 @@ const PolygonReviewTab: FC = props => { const { openModal, closeModal } = useModalContext(); - const flyToPolygonBounds = async (uuid: string) => { - const bbox: PolygonBboxResponse = await fetchGetV2TerrafundPolygonBboxUuid({ pathParams: { uuid } }); - const bboxArray = bbox?.bbox; - const { map } = mapFunctions; - if (bboxArray && map?.current) { - const bounds: LngLatBoundsLike = [ - [bboxArray[0], bboxArray[1]], - [bboxArray[2], bboxArray[3]] - ]; - map.current.fitBounds(bounds, { - padding: 100, - linear: false - }); - } else { - Log.error("Bounding box is not in the expected format"); - } - }; - const deletePolygon = (uuid: string) => { fetchDeleteV2TerrafundPolygonUuid({ pathParams: { uuid } }) .then((response: DeletePolygonProps | undefined) => { @@ -288,6 +293,7 @@ const PolygonReviewTab: FC = props => { uploadFiles(); setSaveFlags(false); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [files, saveFlags]); useEffect(() => { @@ -295,22 +301,22 @@ const PolygonReviewTab: FC = props => { openNotification("error", t("Error uploading file"), t(errorMessage)); setErrorMessage(null); } - }, [errorMessage]); + }, [errorMessage, openNotification, t]); useEffect(() => { setPolygonData(sitePolygonData); - }, [loading]); + }, [loading, setPolygonData, sitePolygonData]); useEffect(() => { setPolygonCriteriaMap(polygonCriteriaMap); - }, [polygonCriteriaMap]); + }, [polygonCriteriaMap, setPolygonCriteriaMap]); useEffect(() => { if (shouldRefetchValidation) { refetch(); setShouldRefetchValidation(false); } - }, [shouldRefetchValidation]); + }, [refetch, setShouldRefetchValidation, shouldRefetchValidation]); const uploadFiles = async () => { const uploadPromises = []; closeModal(ModalId.ADD_POLYGON); @@ -578,9 +584,7 @@ const PolygonReviewTab: FC = props => { ); }; - const isLoading = ctxLoading; - - if (isLoading) return null; + if (ctxLoading) return null; const tableItemMenu = (props: TableItemMenuProps) => [ { diff --git a/src/admin/components/ShowTitle/index.tsx b/src/admin/components/ShowTitle/index.tsx index f9bcaca35..939440eef 100644 --- a/src/admin/components/ShowTitle/index.tsx +++ b/src/admin/components/ShowTitle/index.tsx @@ -1,5 +1,5 @@ import { Chip } from "@mui/material"; -import { Link, RaRecord, useRecordContext, useResourceContext, useShowContext } from "react-admin"; +import { Link, useGetRecordRepresentation, useRecordContext, useResourceContext, useShowContext } from "react-admin"; import { Else, If, Then, When } from "react-if"; import Text from "@/components/elements/Text/Text"; @@ -7,18 +7,17 @@ import Icon, { IconNames } from "@/components/extensive/Icon/Icon"; interface IProps { moduleName?: string; - getTitle: (record: RaRecord) => string; } const ShowTitle = (props: IProps) => { const record = useRecordContext(); const { isLoading } = useShowContext(); - const titleText = props.getTitle(record); const resource = useResourceContext(); + const titleGetter = useGetRecordRepresentation(resource); - const title = titleText && ( + const title = ( <> - {titleText} + {titleGetter(record)} {record?.is_test && } ); diff --git a/src/admin/hooks/useCanUserEdit.ts b/src/admin/hooks/useCanUserEdit.ts index 1dd24429d..31da7db52 100644 --- a/src/admin/hooks/useCanUserEdit.ts +++ b/src/admin/hooks/useCanUserEdit.ts @@ -1,29 +1,30 @@ import { useMemo } from "react"; -import { useGetUserRole } from "./useGetUserRole"; +import { getRoleData, useGetUserRole } from "./useGetUserRole"; -export const useCanUserEdit = (record: any, resource: string) => { - const { isFrameworkAdmin, isSuperAdmin, role } = useGetUserRole(); - - const canEdit = useMemo(() => { - if (resource === "user") { - if (isSuperAdmin) { - return true; - } - if (record?.role === "admin-super") { - return false; - } - if (record?.role === "project-developer" || record?.role === "project-manager") { - return true; - } - if (isFrameworkAdmin) { - if (record?.role === role) { - return true; - } - return false; - } +export const userCanEdit = ( + record: any, + resource: string, + { role, isSuperAdmin, isFrameworkAdmin }: ReturnType +) => { + if (resource === "user") { + if (isSuperAdmin) { + return true; + } + if (record?.role === "admin-super") { + return false; + } + if (record?.role === "project-developer" || record?.role === "project-manager") { + return true; } - return !!record; - }, [record, resource, isFrameworkAdmin, isSuperAdmin]); - return canEdit; + if (isFrameworkAdmin) { + return record?.role === role; + } + } + return record != null; +}; + +export const useCanUserEdit = (record: any, resource: string) => { + const roleData = useGetUserRole(); + return useMemo(() => userCanEdit(record, resource, roleData), [record, resource, roleData]); }; diff --git a/src/admin/hooks/useGetUserRole.ts b/src/admin/hooks/useGetUserRole.ts index 51fba7984..cb3c47450 100644 --- a/src/admin/hooks/useGetUserRole.ts +++ b/src/admin/hooks/useGetUserRole.ts @@ -1,14 +1,17 @@ +import { useMemo } from "react"; import { useGetIdentity } from "react-admin"; -export const useGetUserRole = () => { - const { data } = useGetIdentity(); - const user: any = data || {}; +import { TMUserIdentity } from "@/admin/apiProvider/authProvider"; + +export const getRoleData = (primaryRole?: string) => ({ + role: primaryRole, + isSuperAdmin: primaryRole === "admin-super", + isPPCAdmin: primaryRole === "admin-ppc", + isPPCTerrafundAdmin: primaryRole === "admin-terrafund", + isFrameworkAdmin: primaryRole?.includes("admin-") +}); - return { - role: user.primaryRole, - isSuperAdmin: user.primaryRole === "admin-super", - isPPCAdmin: user.primaryRole === "admin-ppc", - isPPCTerrafundAdmin: user.primaryRole === "admin-terrafund", - isFrameworkAdmin: user.primaryRole && user.primaryRole.includes("admin-") - }; +export const useGetUserRole = () => { + const user = useGetIdentity().data as TMUserIdentity | undefined; + return useMemo(() => getRoleData(user?.primaryRole), [user?.primaryRole]); }; diff --git a/src/admin/modules/application/components/ApplicationShow.tsx b/src/admin/modules/application/components/ApplicationShow.tsx index b5eb1177f..9b8300e07 100644 --- a/src/admin/modules/application/components/ApplicationShow.tsx +++ b/src/admin/modules/application/components/ApplicationShow.tsx @@ -1,20 +1,15 @@ import { Show } from "react-admin"; import ShowActions from "@/admin/components/Actions/ShowActions"; -import ShowTitle from "@/admin/components/ShowTitle"; import ApplicationShowAside from "./ApplicationShowAside"; import { ApplicationTabs } from "./ApplicationTabs"; -export const ApplicationShow = () => { - return ( - <> - `${item?.id}`} />} - actions={} - aside={} - > - - - - ); -}; + +export const ApplicationShow = () => ( + } + aside={} + > + + +); diff --git a/src/admin/modules/form/components/CopyFormToOtherEnv.tsx b/src/admin/modules/form/components/CopyFormToOtherEnv.tsx index 20e76da82..22c254926 100644 --- a/src/admin/modules/form/components/CopyFormToOtherEnv.tsx +++ b/src/admin/modules/form/components/CopyFormToOtherEnv.tsx @@ -40,7 +40,7 @@ export const CopyFormToOtherEnv = () => { } }); const { register, handleSubmit, formState, getValues } = formHook; - Log.info(getValues(), formState.errors); + Log.info("Copy form values", { ...getValues(), formErrors: formState.errors }); const copyToDestinationEnv = async ({ env: baseUrl, title: formTitle, framework_key, ...body }: any) => { const linkedFieldsData: any = await fetchGetV2FormsLinkedFieldListing({}); diff --git a/src/admin/modules/form/components/FormBuilder/QuestionArrayInput.tsx b/src/admin/modules/form/components/FormBuilder/QuestionArrayInput.tsx index 73ecb08f1..3a3616a4c 100644 --- a/src/admin/modules/form/components/FormBuilder/QuestionArrayInput.tsx +++ b/src/admin/modules/form/components/FormBuilder/QuestionArrayInput.tsx @@ -9,6 +9,7 @@ import { FormDataConsumer, FormDataConsumerRenderParams, minLength, + NumberInput, required, TextInput, useInput @@ -109,6 +110,28 @@ export const QuestionArrayInput = ({ height="75px" /> )} + + {({ scopedFormData, getSource }: FormDataConsumerRenderParams) => { + if (!scopedFormData || !getSource) return null; + const field = getFieldByUUID(scopedFormData.linked_field_key); + return field?.input_type == "long-text" ? ( + <> + + + + ) : ( + <> + ); + }} + { return !record?.published ? ( { ) : null; }; -export const FormEdit = () => { - return ( - } - title={ record?.title} moduleName="Form" />} - sx={{ marginBottom: 2 }} - > - - } noValidate paddingY="1.5rem"> - - - - ); -}; +export const FormEdit = () => ( + } + title={} + sx={{ marginBottom: 2 }} + > + + } noValidate paddingY="1.5rem"> + + + +); diff --git a/src/admin/modules/fundingProgrammes/components/FundingProgrammeShow.tsx b/src/admin/modules/fundingProgrammes/components/FundingProgrammeShow.tsx index da0ae3a5d..272e14409 100644 --- a/src/admin/modules/fundingProgrammes/components/FundingProgrammeShow.tsx +++ b/src/admin/modules/fundingProgrammes/components/FundingProgrammeShow.tsx @@ -1,31 +1,29 @@ import { Divider } from "@mui/material"; import { ImageField, Show, SimpleShowLayout, TabbedShowLayout, TextField } from "react-admin"; +import ShowActions from "@/admin/components/Actions/ShowActions"; import FundingProgrammeStages from "@/admin/modules/fundingProgrammes/components/FundingProgrammeStages"; import FundingProgrammeOrganisations from "./FundingProgrammeOrganisations"; -import FundingProgrammeTitle from "./FundingProgrammeTitle"; -export const FundingProgrammeShow = () => { - return ( - } sx={{ mb: 2 }}> - - - - - - - - - - - - - - - - - - - ); -}; +export const FundingProgrammeShow = () => ( + } sx={{ mb: 2 }}> + + + + + + + + + + + + + + + + + + +); diff --git a/src/admin/modules/impactStories/components/ImpactStories.tsx b/src/admin/modules/impactStories/components/ImpactStories.tsx new file mode 100644 index 000000000..88fa3c9d9 --- /dev/null +++ b/src/admin/modules/impactStories/components/ImpactStories.tsx @@ -0,0 +1,46 @@ +import { Create, Edit, SimpleForm } from "react-admin"; + +import ImpactStoryForm from "./ImpactStoryForm"; + +const transformData = (data: any) => { + const transformedData = { + organization_id: data.organization?.uuid, + title: data.title, + date: data.date, + category: data.category, + content: data.content, + status: data.status, + thumbnail: data.thumbnail + }; + + return Object.fromEntries(Object.entries(transformedData).filter(([_, value]) => value != null)); +}; +export const ImpactStoriesCreate: React.FC = () => ( + + + + + +); + +export const ImpactStoriesEdit: React.FC = () => ( + + + + + +); diff --git a/src/admin/modules/impactStories/components/ImpactStoriesEditForm.tsx b/src/admin/modules/impactStories/components/ImpactStoriesEditForm.tsx new file mode 100644 index 000000000..93c9e1bd9 --- /dev/null +++ b/src/admin/modules/impactStories/components/ImpactStoriesEditForm.tsx @@ -0,0 +1,118 @@ +// import { t } from "@transifex/native"; +import React from "react"; + +import Button from "@/components/elements/Button/Button"; +import Dropdown from "@/components/elements/Inputs/Dropdown/Dropdown"; +import { VARIANT_DROPDOWN_IMPACT_STORY } from "@/components/elements/Inputs/Dropdown/DropdownVariant"; +import FileInput from "@/components/elements/Inputs/FileInput/FileInput"; +import { VARIANT_FILE_INPUT_IMPACT_STORY } from "@/components/elements/Inputs/FileInput/FileInputVariants"; +import Input from "@/components/elements/Inputs/Input/Input"; +import Text from "@/components/elements/Text/Text"; + +// import { ModalId } from "@/components/extensive/Modal/ModalConst"; +// import ModalStory from "@/components/extensive/Modal/ModalStory"; +// import { useModalContext } from "@/context/modal.provider"; +import QuillEditor from "./QuillEditor"; + +const ImpactStoriesEditForm = () => { + // const { openModal } = useModalContext(); + const ModalStoryOpen = (uuid: string) => { + // openModal(ModalId.MODAL_STORY, ); + }; + return ( +
+ + Edit Impact Story + +
+
+ + +
+ {}} + labelClassName="capitalize text-14-bold" + className="text-14-light" + multiSelect={true} + variant={VARIANT_DROPDOWN_IMPACT_STORY} + /> +
+
+ {}} + variant={VARIANT_FILE_INPUT_IMPACT_STORY} + files={[]} + allowMultiple={true} + label="Thumbnail" + labelClassName="capitalize text-14-bold" + classNameTextOr="hidden" + descriptionInput={ + + documents or images to help reviewer + + } + /> +
+ + +
+ Content + +
+ +
+
+ +
+ + + +
+
+
+
+ ); +}; + +export default ImpactStoriesEditForm; diff --git a/src/admin/modules/impactStories/components/ImpactStoriesList.tsx b/src/admin/modules/impactStories/components/ImpactStoriesList.tsx new file mode 100644 index 000000000..cd06c75a1 --- /dev/null +++ b/src/admin/modules/impactStories/components/ImpactStoriesList.tsx @@ -0,0 +1,173 @@ +import { Stack } from "@mui/material"; +import { FC } from "react"; +import { + AutocompleteInput, + Datagrid, + DateField, + EditButton, + FunctionField, + List, + ReferenceInput, + SearchInput, + SelectInput, + TextField, + WrapperField +} from "react-admin"; + +import ListActionsImpactStories from "@/admin/components/Actions/ListActionsImpactStories"; +import CustomDeleteWithConfirmButton from "@/admin/components/Buttons/CustomDeleteWithConfirmButton"; +import CustomChipField from "@/admin/components/Fields/CustomChipField"; +import Menu from "@/components/elements/Menu/Menu"; +import { MENU_PLACEMENT_BOTTOM_LEFT } from "@/components/elements/Menu/MenuVariant"; +import Text from "@/components/elements/Text/Text"; +import Icon, { IconNames } from "@/components/extensive/Icon/Icon"; +import { getCountriesOptions } from "@/constants/options/countries"; +import { getChangeRequestStatusOptions, getPolygonOptions, getStatusOptions } from "@/constants/options/status"; +import { useUserFrameworkChoices } from "@/constants/options/userFrameworksChoices"; +import { optionToChoices } from "@/utils/options"; + +import modules from "../.."; + +const monitoringDataChoices = [ + { + id: "0", + name: "No" + }, + { + id: "1", + name: "Yes" + } +]; +const tableMenu = [ + { + id: "1", + render: () => + }, + { + id: "2", + render: () => { + return ( + + + + ); + } + } +]; + +const ImpactStoriesDataGrid: FC = () => { + return ( + + + { + return ; + }} + /> + + + record.organization?.countries?.length > 0 + ? record.organization.countries.map((c: any) => c.label).join(", ") + : "No country" + } + /> + + + + + + ); +}; + +export const ImpactStoriesList: FC = () => { + const frameworkInputChoices = useUserFrameworkChoices(); + + const filters = [ + , + , + + + , + + + , + , + , + , + , + + ]; + + return ( + <> + + + Impact Stories + + + + } filters={filters}> + + + + ); +}; diff --git a/src/admin/modules/impactStories/components/ImpactStoryForm.tsx b/src/admin/modules/impactStories/components/ImpactStoryForm.tsx new file mode 100644 index 000000000..773cebadc --- /dev/null +++ b/src/admin/modules/impactStories/components/ImpactStoryForm.tsx @@ -0,0 +1,210 @@ +import { Box } from "@mui/material"; +import { memo } from "react"; +import { ReferenceInput, required } from "react-admin"; +import { useFormContext } from "react-hook-form"; + +import { maxFileSize } from "@/admin/utils/forms"; +import Button from "@/components/elements/Button/Button"; +import Dropdown from "@/components/elements/Inputs/Dropdown/Dropdown"; +import { VARIANT_DROPDOWN_IMPACT_STORY } from "@/components/elements/Inputs/Dropdown/DropdownVariant"; +import Input from "@/components/elements/Inputs/Input/Input"; +import Text from "@/components/elements/Text/Text"; +import Icon, { IconNames } from "@/components/extensive/Icon/Icon"; +import { ModalId } from "@/components/extensive/Modal/ModalConst"; +import ModalStory from "@/components/extensive/Modal/ModalStory"; +import { useLoading } from "@/context/loaderAdmin.provider"; +import { useModalContext } from "@/context/modal.provider"; +import { useOnMount } from "@/hooks/useOnMount"; + +import modules from "../.."; +import { useImpactStoryForm } from "../hooks/useImpactStoryForm"; +import QuillEditor from "./QuillEditor"; +import StyledFileUploadInput from "./StyledFileUploadInput"; +import { StyledAutocompleteInput, StyledReferenceInput } from "./StyledInputs"; + +export interface ImpactCategory { + title: string; + value: string; +} + +export interface ImpactStoryFormProps { + mode: "create" | "edit"; +} + +export const IMPACT_CATEGORIES: ImpactCategory[] = [ + { title: "Business development/fundraising", value: "business-dev-fund" }, + { title: "Community benefits", value: "community-benefits" }, + { title: "Livelihoods strengthening", value: "livelihoods-strengthening" }, + { title: "Gender equity", value: "gender-equity" }, + { title: "Youth engagement", value: "youth-engagement" }, + { title: "Ecosystem services", value: "ecosystem-services" }, + { title: "Climate resilience", value: "climate-resilience" }, + { title: "Institutional capacity", value: "institutional-capacity" }, + { title: "Technical capacity", value: "technical-capacity" } +]; + +const ImpactStoryForm: React.FC = memo(({ mode }) => { + const { initialValues, handlers } = useImpactStoryForm(mode); + const { openModal } = useModalContext(); + const { getValues, trigger } = useFormContext(); + const { showLoader, hideLoader } = useLoading(); + useOnMount(() => hideLoader); + const handlePreviewClick = () => { + const formValues = getValues(); + const previewData = { + uuid: formValues.uuid ? formValues.uuid : formValues?.data?.uuid, + title: formValues.title ? formValues.title : formValues?.data?.title, + date: formValues.date ? formValues.date : formValues?.data?.date, + content: formValues.content ? JSON.parse(formValues.content) : JSON.parse(formValues.data?.content), + category: formValues.category ? formValues.category : formValues.data?.category, + thumbnail: + formValues.thumbnail instanceof File ? URL.createObjectURL(formValues.thumbnail) : formValues.thumbnail || "", + organization: { + name: formValues?.organization?.name + ? formValues?.organization?.name + : formValues?.data?.organization.name ?? "", + category: formValues?.category ? formValues?.category : formValues?.data?.category, + country: + formValues?.organization?.countries?.length > 0 + ? formValues.organization.countries.map((c: any) => c.label).join(", ") + : formValues?.data?.organization?.countries?.length > 0 + ? formValues.data.organization.countries.map((c: any) => c.label).join(", ") + : "No country", + facebook_url: formValues?.organization?.facebook_url + ? formValues?.organization?.facebook_url + : formValues?.data?.organization?.facebook_url, + instagram_url: formValues?.organization?.instagram_url + ? formValues?.organization?.instagram_url + : formValues?.data?.organization?.instagram_url, + linkedin_url: formValues?.organization?.linkedin_url + ? formValues?.organization?.linkedin_url + : formValues?.data?.organization?.linkedin_url, + twitter_url: formValues?.organization?.twitter_url + ? formValues?.organization?.twitter_url + : formValues?.data?.organization?.twitter_url + }, + status: formValues?.status ? formValues?.status : formValues?.data?.status + }; + + openModal(ModalId.MODAL_STORY, ); + }; + const handleSave = async (status: "draft" | "published") => { + const isValid = await trigger(); + if (!isValid) { + return; + } + showLoader(); + handlers.handleStatusChange(status); + }; + return ( +
+ + {mode === "create" ? "Create Impact Story" : "Edit Impact Story"} + + +
+
+ handlers.handleTitleChange(e.target.value)} + required + /> + handlers.handleDateChange(e.target.value)} + required + /> +
+ + + + + + + + handlers.handleImpactCategoryChange(e as string[])} + labelClassName="capitalize text-14-bold" + className="text-14-light" + multiSelect={true} + variant={VARIANT_DROPDOWN_IMPACT_STORY} + required + /> + +
+ + + + Click to upload + + + documents or images to help reviewer + + + } + /> + Uploaded +
+ +
+ + Content + + +
+ +
+ {mode === "edit" && ( + + )} +
+ + + +
+
+
+
+ ); +}); + +ImpactStoryForm.displayName = "ImpactStoryForm"; + +export default ImpactStoryForm; diff --git a/src/admin/modules/impactStories/components/QuillEditor.tsx b/src/admin/modules/impactStories/components/QuillEditor.tsx new file mode 100644 index 000000000..4023dcbb5 --- /dev/null +++ b/src/admin/modules/impactStories/components/QuillEditor.tsx @@ -0,0 +1,96 @@ +import React, { Component, createRef } from "react"; + +let Quill: any = null; + +if (typeof window !== "undefined") { + Quill = require("quill").default; + require("quill/dist/quill.snow.css"); +} + +interface QuillEditorProps { + value?: string; + onChange?: (content: string) => void; +} + +class QuillEditor extends Component { + private editorRef = createRef(); + private quill?: any; + + componentDidMount() { + if (typeof window !== "undefined" && Quill) { + this.initializeQuill(); + } + } + + componentDidUpdate(prevProps: QuillEditorProps) { + if (this.quill && prevProps.value !== this.props.value) { + this.quill.root.innerHTML = this.props.value || ""; + } + } + + initializeQuill() { + if (this.editorRef.current && !this.quill && Quill) { + this.quill = new Quill(this.editorRef.current, { + theme: "snow", + modules: { + toolbar: [ + [{ header: [1, 2, 3, false] }], + ["bold", "italic", "underline"], + ["link", "blockquote"], + [{ list: "ordered" }, { list: "bullet" }], + ["video"] + ] + }, + bounds: document.body + }); + + this.quill.on("text-change", () => { + if (this.quill && this.props.onChange) { + this.props.onChange(this.quill.root.innerHTML); + } + }); + + if (this.props.value) { + this.quill.root.innerHTML = this.props.value; + } + } + } + + render() { + return ( +
+ +
+
+ ); + } +} + +export default QuillEditor; diff --git a/src/admin/modules/impactStories/components/StyledFileUploadInput.tsx b/src/admin/modules/impactStories/components/StyledFileUploadInput.tsx new file mode 100644 index 000000000..8c9cf8074 --- /dev/null +++ b/src/admin/modules/impactStories/components/StyledFileUploadInput.tsx @@ -0,0 +1,56 @@ +import React from "react"; + +import { FileUploadInput } from "@/admin/components/Inputs/FileUploadInput"; + +const StyledFileUploadInput = (props: any) => { + return ( + + ); +}; + +export default StyledFileUploadInput; diff --git a/src/admin/modules/impactStories/components/StyledInputs.tsx b/src/admin/modules/impactStories/components/StyledInputs.tsx new file mode 100644 index 000000000..dfb190b09 --- /dev/null +++ b/src/admin/modules/impactStories/components/StyledInputs.tsx @@ -0,0 +1,51 @@ +import { ReactNode } from "react"; +import { AutocompleteInput } from "react-admin"; + +import Text from "@/components/elements/Text/Text"; + +export interface StyledReferenceInputProps { + label: string; + children: ReactNode; +} + +export const StyledAutocompleteInput = (props: any) => ( + +); + +export const StyledReferenceInput: React.FC = ({ label, children }) => ( +
+ + {label} + + {children} +
+); diff --git a/src/admin/modules/impactStories/hooks/useImpactStoryForm.ts b/src/admin/modules/impactStories/hooks/useImpactStoryForm.ts new file mode 100644 index 000000000..da795b8b1 --- /dev/null +++ b/src/admin/modules/impactStories/hooks/useImpactStoryForm.ts @@ -0,0 +1,85 @@ +import { useCallback } from "react"; +import { useNotify, useRecordContext, useRedirect } from "react-admin"; +import { useFormContext } from "react-hook-form"; + +export const useImpactStoryForm = (mode: "create" | "edit") => { + const { setValue, getValues, watch } = useFormContext(); + const status = watch("status"); + const record = useRecordContext(); + const notify = useNotify(); + const redirect = useRedirect(); + + const currentData = mode === "edit" && record?.data ? record.data : record; + const initialValues = { + content: currentData?.content ? JSON.parse(currentData.content) : "", + title: currentData?.title || "", + date: currentData?.date || "", + thumbnail: currentData?.thumbnail, + categories: currentData?.category ? currentData.category : "", + orgUuid: mode === "edit" ? currentData?.organization?.uuid : record?.organization?.uuid + }; + + const handleImpactCategoryChange = useCallback( + (selectedValues: string[]) => { + setValue("category", selectedValues); + }, + [setValue] + ); + + const handleContentChange = useCallback( + (content: string) => { + setValue("content", JSON.stringify(content)); + }, + [setValue] + ); + + const handleTitleChange = useCallback( + (value: string) => { + setValue("title", value); + }, + [setValue] + ); + + const handleDateChange = useCallback( + (value: string) => { + setValue("date", value); + }, + [setValue] + ); + + const handleStatusChange = useCallback( + (status: "draft" | "published") => { + setValue("status", status); + }, + [setValue] + ); + + const handlePreview = useCallback(() => { + const values = getValues(); + notify("Preview mode activated"); + console.log("values", values); + }, [getValues, notify]); + + const handleDelete = useCallback(async () => { + try { + notify("Story deleted successfully"); + redirect("list", "impactStories"); + } catch (error) { + notify("Error deleting story", { type: "error" }); + } + }, [notify, redirect]); + + return { + initialValues, + handlers: { + handleImpactCategoryChange, + handleContentChange, + handleTitleChange, + handleDateChange, + handleStatusChange, + handlePreview, + handleDelete + }, + status + }; +}; diff --git a/src/admin/modules/index.tsx b/src/admin/modules/index.tsx index 98c103770..2bcef56ec 100644 --- a/src/admin/modules/index.tsx +++ b/src/admin/modules/index.tsx @@ -19,6 +19,8 @@ import FundingProgrammeCreate from "./fundingProgrammes/components/FundingProgra import FundingProgrammeEdit from "./fundingProgrammes/components/FundingProgrammeEdit"; import { FundingProgrammeList } from "./fundingProgrammes/components/FundingProgrammeList"; import { FundingProgrammeShow } from "./fundingProgrammes/components/FundingProgrammeShow"; +import { ImpactStoriesCreate, ImpactStoriesEdit } from "./impactStories/components/ImpactStories"; +import { ImpactStoriesList } from "./impactStories/components/ImpactStoriesList"; import { NurseriesList } from "./nurseries/components/NurseriesList"; import NurseryShow from "./nurseries/components/NurseryShow"; import NurseryReportShow from "./nurseryReports/components/NurseryReportShow"; @@ -157,6 +159,13 @@ const validatePolygonFile = { List: ValidatePolygonFileShow }; +const impactStories = { + ResourceName: "impactStories", + List: ImpactStoriesList, + Create: ImpactStoriesCreate, + Edit: ImpactStoriesEdit +}; + const modules = { user, organisation, @@ -174,7 +183,8 @@ const modules = { siteReport, nurseryReport, audit, - validatePolygonFile + validatePolygonFile, + impactStories }; export default modules; diff --git a/src/admin/modules/nurseries/components/NurseryShow.tsx b/src/admin/modules/nurseries/components/NurseryShow.tsx index 0e2c9d6ce..d6d004ae0 100644 --- a/src/admin/modules/nurseries/components/NurseryShow.tsx +++ b/src/admin/modules/nurseries/components/NurseryShow.tsx @@ -1,4 +1,3 @@ -import { FC } from "react"; import { Show, TabbedShowLayout } from "react-admin"; import ShowActions from "@/admin/components/Actions/ShowActions"; @@ -8,27 +7,20 @@ import ChangeRequestsTab from "@/admin/components/ResourceTabs/ChangeRequestsTab import DocumentTab from "@/admin/components/ResourceTabs/DocumentTab/DocumentTab"; import GalleryTab from "@/admin/components/ResourceTabs/GalleryTab/GalleryTab"; import InformationTab from "@/admin/components/ResourceTabs/InformationTab"; -import ShowTitle from "@/admin/components/ShowTitle"; import { RecordFrameworkProvider } from "@/context/framework.provider"; -const NurseryShow: FC = () => { - return ( - record?.name} />} - actions={} - className="-mt-[50px] bg-neutral-100" - > - - - - - - - - - - - ); -}; +const NurseryShow = () => ( + } className="-mt-[50px] bg-neutral-100"> + + + + + + + + + + +); export default NurseryShow; diff --git a/src/admin/modules/nurseryReports/components/NurseryReportShow.tsx b/src/admin/modules/nurseryReports/components/NurseryReportShow.tsx index c21797ef5..378112bb8 100644 --- a/src/admin/modules/nurseryReports/components/NurseryReportShow.tsx +++ b/src/admin/modules/nurseryReports/components/NurseryReportShow.tsx @@ -1,4 +1,3 @@ -import { FC } from "react"; import { Show, TabbedShowLayout } from "react-admin"; import ShowActions from "@/admin/components/Actions/ShowActions"; @@ -8,27 +7,20 @@ import ChangeRequestsTab from "@/admin/components/ResourceTabs/ChangeRequestsTab import DocumentTab from "@/admin/components/ResourceTabs/DocumentTab/DocumentTab"; import GalleryTab from "@/admin/components/ResourceTabs/GalleryTab/GalleryTab"; import InformationTab from "@/admin/components/ResourceTabs/InformationTab"; -import ShowTitle from "@/admin/components/ShowTitle"; import { RecordFrameworkProvider } from "@/context/framework.provider"; -const NurseryReportShow: FC = () => { - return ( - record?.title} />} - actions={} - className="-mt-[50px] bg-neutral-100" - > - - - - - - - - - - - ); -}; +const NurseryReportShow = () => ( + } className="-mt-[50px] bg-neutral-100"> + + + + + + + + + + +); export default NurseryReportShow; diff --git a/src/admin/modules/organisations/components/OrganisationShow.tsx b/src/admin/modules/organisations/components/OrganisationShow.tsx index 1e8cf6302..2334eecf4 100644 --- a/src/admin/modules/organisations/components/OrganisationShow.tsx +++ b/src/admin/modules/organisations/components/OrganisationShow.tsx @@ -21,7 +21,6 @@ import ShowActions from "@/admin/components/Actions/ShowActions"; import { FileArrayField } from "@/admin/components/Fields/FileArrayField"; import MapField from "@/admin/components/Fields/MapField"; import SimpleChipFieldArray from "@/admin/components/Fields/SimpleChipFieldArray"; -import ShowTitle from "@/admin/components/ShowTitle"; import { getCountriesOptions } from "@/constants/options/countries"; import { getFarmersEngagementStrategyOptions, @@ -59,11 +58,7 @@ export const OrganisationShow = () => { return ( <> - } - title={ record?.name} />} - aside={} - > + } aside={}> diff --git a/src/admin/modules/pitch/components/PitchShow.tsx b/src/admin/modules/pitch/components/PitchShow.tsx index 4f6fce2b4..819ff8209 100644 --- a/src/admin/modules/pitch/components/PitchShow.tsx +++ b/src/admin/modules/pitch/components/PitchShow.tsx @@ -15,7 +15,6 @@ import ShowActions from "@/admin/components/Actions/ShowActions"; import { FileArrayField } from "@/admin/components/Fields/FileArrayField"; import MapField from "@/admin/components/Fields/MapField"; import SimpleChipFieldArray from "@/admin/components/Fields/SimpleChipFieldArray"; -import ShowTitle from "@/admin/components/ShowTitle"; import { getCapacityBuildingNeedOptions } from "@/constants/options/capacityBuildingNeeds"; import { getCountriesOptions } from "@/constants/options/countries"; import { getLandTenureOptions } from "@/constants/options/landTenure"; @@ -25,249 +24,239 @@ import { optionToChoices } from "@/utils/options"; import { PitchAside } from "./PitchAside"; -export const PitchShow = () => { - return ( - record?.project_name} />} - actions={} - aside={} - > - - - Objectives - - - - - - - - - - - +export const PitchShow = () => ( + } aside={}> + + + Objectives + + + + + + + + + + + - - - Proposed Project Area - - - - - + + + Proposed Project Area + + + + + - - - Timeline - - - - - - + + + Timeline + + + + + + - - - Land Tenure Strategy - - - + + + Land Tenure Strategy + + + - - - + + + - - - More Information - - - - - + + + More Information + + + + + - + - - - + + + - - - Environmental Impact - - - - - - - - - - - - - - + + + Environmental Impact + + + + + + + + + + + + + + - - - Biophysical characteristics of the project area Sources of tree seedlings for the project - - - - - + + + Biophysical characteristics of the project area Sources of tree seedlings for the project + + + + + - - - Sources of tree seedlings for the project - - - - + + + Sources of tree seedlings for the project + + + + - - - Social Impact - - - New jobs breakdown - - - - - - - - Project beneficiaries breakdown - - - - - - - - + + + Social Impact + + + New jobs breakdown + + + + + + + + Project beneficiaries breakdown + + + + + + + + - - - More Details on Social Impact of Project - - - - - - - - ); -}; + + + More Details on Social Impact of Project + + + + + + + +); diff --git a/src/admin/modules/projectReports/components/ProjectReportShow.tsx b/src/admin/modules/projectReports/components/ProjectReportShow.tsx index abad8a533..fd8af3ee7 100644 --- a/src/admin/modules/projectReports/components/ProjectReportShow.tsx +++ b/src/admin/modules/projectReports/components/ProjectReportShow.tsx @@ -1,4 +1,3 @@ -import { FC } from "react"; import { Show, TabbedShowLayout } from "react-admin"; import ShowActions from "@/admin/components/Actions/ShowActions"; @@ -8,27 +7,20 @@ import ChangeRequestsTab from "@/admin/components/ResourceTabs/ChangeRequestsTab import DocumentTab from "@/admin/components/ResourceTabs/DocumentTab/DocumentTab"; import GalleryTab from "@/admin/components/ResourceTabs/GalleryTab/GalleryTab"; import InformationTab from "@/admin/components/ResourceTabs/InformationTab"; -import ShowTitle from "@/admin/components/ShowTitle"; import { RecordFrameworkProvider } from "@/context/framework.provider"; -const ProjectReportShow: FC = () => { - return ( - record?.title} />} - actions={} - className="-mt-[50px] bg-neutral-100" - > - - - - - - - - - - - ); -}; +const ProjectReportShow = () => ( + } className="-mt-[50px] bg-neutral-100"> + + + + + + + + + + +); export default ProjectReportShow; diff --git a/src/admin/modules/projects/components/ProjectShow.tsx b/src/admin/modules/projects/components/ProjectShow.tsx index 40fca8a9d..17fcc75cf 100644 --- a/src/admin/modules/projects/components/ProjectShow.tsx +++ b/src/admin/modules/projects/components/ProjectShow.tsx @@ -10,7 +10,6 @@ import DocumentTab from "@/admin/components/ResourceTabs/DocumentTab/DocumentTab import GalleryTab from "@/admin/components/ResourceTabs/GalleryTab/GalleryTab"; import InformationTab from "@/admin/components/ResourceTabs/InformationTab"; import MonitoredTab from "@/admin/components/ResourceTabs/MonitoredTab/MonitoredTab"; -import ShowTitle from "@/admin/components/ShowTitle"; import { RecordFrameworkProvider } from "@/context/framework.provider"; import { usePutV2AdminProjectsUUID } from "@/generated/apiComponents"; @@ -33,8 +32,7 @@ const ProjectShow = () => { return ( record?.name} />} - actions={} + actions={} className="-mt-[50px] bg-neutral-100" > diff --git a/src/admin/modules/siteReports/components/SiteReportShow.tsx b/src/admin/modules/siteReports/components/SiteReportShow.tsx index 07b8e1f8e..05b59b648 100644 --- a/src/admin/modules/siteReports/components/SiteReportShow.tsx +++ b/src/admin/modules/siteReports/components/SiteReportShow.tsx @@ -1,4 +1,3 @@ -import { FC } from "react"; import { Show, TabbedShowLayout } from "react-admin"; import ShowActions from "@/admin/components/Actions/ShowActions"; @@ -8,27 +7,20 @@ import ChangeRequestsTab from "@/admin/components/ResourceTabs/ChangeRequestsTab import DocumentTab from "@/admin/components/ResourceTabs/DocumentTab/DocumentTab"; import GalleryTab from "@/admin/components/ResourceTabs/GalleryTab/GalleryTab"; import InformationTab from "@/admin/components/ResourceTabs/InformationTab"; -import ShowTitle from "@/admin/components/ShowTitle"; import { RecordFrameworkProvider } from "@/context/framework.provider"; -const SiteReportShow: FC = () => { - return ( - record?.title} />} - actions={} - className="-mt-[50px] bg-neutral-100" - > - - - - - - - - - - - ); -}; +const SiteReportShow = () => ( + } className="-mt-[50px] bg-neutral-100"> + + + + + + + + + + +); export default SiteReportShow; diff --git a/src/admin/modules/sites/components/SiteShow.tsx b/src/admin/modules/sites/components/SiteShow.tsx index 7662bf547..28fae8eb2 100644 --- a/src/admin/modules/sites/components/SiteShow.tsx +++ b/src/admin/modules/sites/components/SiteShow.tsx @@ -1,4 +1,3 @@ -import { FC } from "react"; import { Show, TabbedShowLayout } from "react-admin"; import ShowActions from "@/admin/components/Actions/ShowActions"; @@ -10,34 +9,27 @@ import GalleryTab from "@/admin/components/ResourceTabs/GalleryTab/GalleryTab"; import InformationTab from "@/admin/components/ResourceTabs/InformationTab"; import MonitoredTab from "@/admin/components/ResourceTabs/MonitoredTab/MonitoredTab"; import PolygonReviewTab from "@/admin/components/ResourceTabs/PolygonReviewTab"; -import ShowTitle from "@/admin/components/ShowTitle"; import { RecordFrameworkProvider } from "@/context/framework.provider"; import { MapAreaProvider } from "@/context/mapArea.provider"; -const SiteShow: FC = () => { - return ( - record?.name} />} - actions={} - className="-mt-[50px] bg-neutral-100" - > - - - - - - - - - - - - - - - - - ); -}; +const SiteShow = () => ( + } className="-mt-[50px] bg-neutral-100"> + + + + + + + + + + + + + + + + +); export default SiteShow; diff --git a/src/admin/modules/tasks/components/TaskShow.tsx b/src/admin/modules/tasks/components/TaskShow.tsx index 76240bed9..9be56093e 100644 --- a/src/admin/modules/tasks/components/TaskShow.tsx +++ b/src/admin/modules/tasks/components/TaskShow.tsx @@ -12,10 +12,9 @@ import { import { grey } from "@mui/material/colors"; import { useT } from "@transifex/react"; import { camelCase } from "lodash"; -import { FC, useMemo } from "react"; +import { useMemo } from "react"; import { RaRecord, Show, ShowButton, useShowContext } from "react-admin"; -import ShowTitle from "@/admin/components/ShowTitle"; import { useGetV2TasksUUIDReports } from "@/generated/apiComponents"; import { useDate } from "@/hooks/useDate"; @@ -129,8 +128,8 @@ function ShowReports() { ); } -const TaskShow: FC = () => ( - record?.project?.name} />}> +const TaskShow = () => ( + ); diff --git a/src/admin/modules/user/components/UserCreate.tsx b/src/admin/modules/user/components/UserCreate.tsx index 0efae8f5b..3100fd875 100644 --- a/src/admin/modules/user/components/UserCreate.tsx +++ b/src/admin/modules/user/components/UserCreate.tsx @@ -46,7 +46,7 @@ const UserCreate = () => { } return [...frameworkAdminPrimaryRoleChoices, userPrimaryRoleChoices.find(choice => choice.id === role)]; - }, [isFrameworkAdmin]); + }, [isSuperAdmin, role]); return ( diff --git a/src/admin/modules/user/components/UserEdit.tsx b/src/admin/modules/user/components/UserEdit.tsx index c1694ea63..daab9aec9 100644 --- a/src/admin/modules/user/components/UserEdit.tsx +++ b/src/admin/modules/user/components/UserEdit.tsx @@ -40,7 +40,7 @@ const UserEdit = () => { } return [...frameworkAdminPrimaryRoleChoices, userPrimaryRoleChoices.find(choice => choice.id === role)]; - }, [isFrameworkAdmin]); + }, [isSuperAdmin, role]); return ( } mutationMode="pessimistic" actions={false}> diff --git a/src/admin/modules/user/components/UserList.tsx b/src/admin/modules/user/components/UserList.tsx index f30f0ed05..6c9d69684 100644 --- a/src/admin/modules/user/components/UserList.tsx +++ b/src/admin/modules/user/components/UserList.tsx @@ -19,7 +19,7 @@ import { import { UserDataProvider } from "@/admin/apiProvider/dataProviders/userDataProvider"; import ListActionsCreateFilter from "@/admin/components/Actions/ListActionsCreateFilter"; import ExportProcessingAlert from "@/admin/components/Alerts/ExportProcessingAlert"; -import { useCanUserEdit } from "@/admin/hooks/useCanUserEdit"; +import { userCanEdit } from "@/admin/hooks/useCanUserEdit"; import { useGetUserRole } from "@/admin/hooks/useGetUserRole"; import Menu from "@/components/elements/Menu/Menu"; import { MENU_PLACEMENT_BOTTOM_LEFT } from "@/components/elements/Menu/MenuVariant"; @@ -65,6 +65,8 @@ const readOnlyMenu = [ShowItem]; const adminMenu = [EditItem, ShowItem]; const UserDataGrid = () => { + const roleData = useGetUserRole(); + return ( { { - const canEdit = useCanUserEdit(record, "user"); + const canEdit = userCanEdit(record, "user", roleData); return ( diff --git a/src/admin/modules/user/components/UserShow.tsx b/src/admin/modules/user/components/UserShow.tsx index e1f481c62..e8f347450 100644 --- a/src/admin/modules/user/components/UserShow.tsx +++ b/src/admin/modules/user/components/UserShow.tsx @@ -12,7 +12,6 @@ import { } from "react-admin"; import ShowActions from "@/admin/components/Actions/ShowActions"; -import ShowTitle from "@/admin/components/ShowTitle"; import { V2AdminOrganisationRead } from "@/generated/apiSchemas"; import modules from "../.."; @@ -42,16 +41,7 @@ const renderFrameworks = (property: string) => (record: any) => { }; export const UserShow = () => ( - `${record?.first_name} ${record?.last_name}`} - deleteProps={{ confirmTitle: "Delete User" }} - /> - } - title={ `${record?.first_name} ${record?.last_name}`} />} - aside={} - > + } aside={}> diff --git a/src/admin/modules/validationPolygonFile/components/ValidationPolygonFileShow.tsx b/src/admin/modules/validationPolygonFile/components/ValidationPolygonFileShow.tsx index bec6d42a5..cf647eae4 100644 --- a/src/admin/modules/validationPolygonFile/components/ValidationPolygonFileShow.tsx +++ b/src/admin/modules/validationPolygonFile/components/ValidationPolygonFileShow.tsx @@ -1,6 +1,6 @@ import { Stack } from "@mui/material"; import { useT } from "@transifex/react"; -import { FC, useEffect, useState } from "react"; +import { FC, useState } from "react"; import Button from "@/components/elements/Button/Button"; import Text from "@/components/elements/Text/Text"; @@ -14,6 +14,7 @@ import { fetchPostV2TerrafundUploadKmlValidate, fetchPostV2TerrafundUploadShapefileValidate } from "@/generated/apiComponents"; +import { useValueChanged } from "@/hooks/useValueChanged"; import { FileType, UploadedFile } from "@/types/common"; import { getErrorMessageFromPayload } from "@/utils/errors"; @@ -25,12 +26,12 @@ const ValidatePolygonFileShow: FC = () => { const { openNotification } = useNotificationContext(); const t = useT(); - useEffect(() => { + useValueChanged(saveFlags, () => { if (file && saveFlags) { uploadFile(); setSaveFlags(false); } - }, [saveFlags]); + }); const getFileType = (file: UploadedFile) => { const fileType = file?.file_name.split(".").pop()?.toLowerCase(); diff --git a/src/assets/icons/arrow-up-right.svg b/src/assets/icons/arrow-up-right.svg new file mode 100644 index 000000000..7a3facc84 --- /dev/null +++ b/src/assets/icons/arrow-up-right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/briefcase.svg b/src/assets/icons/briefcase.svg new file mode 100644 index 000000000..dfb0f58af --- /dev/null +++ b/src/assets/icons/briefcase.svg @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/src/assets/icons/check-laguages.svg b/src/assets/icons/check-laguages.svg new file mode 100644 index 000000000..9094ba8b1 --- /dev/null +++ b/src/assets/icons/check-laguages.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/check.svg b/src/assets/icons/check.svg new file mode 100644 index 000000000..da9b057a4 --- /dev/null +++ b/src/assets/icons/check.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/clear-dashboard.svg b/src/assets/icons/clear-dashboard.svg new file mode 100644 index 000000000..0efa2c6d1 --- /dev/null +++ b/src/assets/icons/clear-dashboard.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/dashboard-impact-story.svg b/src/assets/icons/dashboard-impact-story.svg new file mode 100644 index 000000000..1a0aeaee8 --- /dev/null +++ b/src/assets/icons/dashboard-impact-story.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/icons/earth-dashboard.svg b/src/assets/icons/earth-dashboard.svg new file mode 100644 index 000000000..57de2ebcb --- /dev/null +++ b/src/assets/icons/earth-dashboard.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/facebook.svg b/src/assets/icons/facebook.svg index d009cc4b7..6311ae3dd 100644 --- a/src/assets/icons/facebook.svg +++ b/src/assets/icons/facebook.svg @@ -1,3 +1,5 @@ - - + + \ No newline at end of file diff --git a/src/assets/icons/filter.svg b/src/assets/icons/filter.svg index 1941e4280..ed8f4c6be 100644 --- a/src/assets/icons/filter.svg +++ b/src/assets/icons/filter.svg @@ -1,7 +1,5 @@ - - - - - - + + + + diff --git a/src/assets/icons/ic-menu.svg b/src/assets/icons/ic-menu.svg new file mode 100644 index 000000000..35269a540 --- /dev/null +++ b/src/assets/icons/ic-menu.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/assets/icons/ic-switch.svg b/src/assets/icons/ic-switch.svg new file mode 100644 index 000000000..f60a74206 --- /dev/null +++ b/src/assets/icons/ic-switch.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/ic-user.svg b/src/assets/icons/ic-user.svg index 026ce70e9..a915cfab3 100644 --- a/src/assets/icons/ic-user.svg +++ b/src/assets/icons/ic-user.svg @@ -1,10 +1,12 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/instagram.svg b/src/assets/icons/instagram.svg index 18c2195f6..e079911f0 100644 --- a/src/assets/icons/instagram.svg +++ b/src/assets/icons/instagram.svg @@ -1,3 +1,5 @@ - - + + \ No newline at end of file diff --git a/src/assets/icons/linkedin.svg b/src/assets/icons/linkedin.svg index 0557402e8..dd34e130a 100644 --- a/src/assets/icons/linkedin.svg +++ b/src/assets/icons/linkedin.svg @@ -1,3 +1,5 @@ - - + + \ No newline at end of file diff --git a/src/assets/icons/logout.svg b/src/assets/icons/logout.svg new file mode 100644 index 000000000..b4c9b4004 --- /dev/null +++ b/src/assets/icons/logout.svg @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/my-account.svg b/src/assets/icons/my-account.svg new file mode 100644 index 000000000..660491e25 --- /dev/null +++ b/src/assets/icons/my-account.svg @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/no-check-laguages.svg b/src/assets/icons/no-check-laguages.svg new file mode 100644 index 000000000..c99806875 --- /dev/null +++ b/src/assets/icons/no-check-laguages.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/share-impact-story.svg b/src/assets/icons/share-impact-story.svg new file mode 100644 index 000000000..9cddfff04 --- /dev/null +++ b/src/assets/icons/share-impact-story.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/src/assets/icons/twitter.svg b/src/assets/icons/twitter.svg new file mode 100644 index 000000000..044f61c31 --- /dev/null +++ b/src/assets/icons/twitter.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/components/elements/Cards/ItemMonitoringCard/__snapshots__/ItemMonitoringCard.stories.storyshot b/src/components/elements/Cards/ItemMonitoringCard/__snapshots__/ItemMonitoringCard.stories.storyshot index 7d340fda7..b6a7dafe4 100644 --- a/src/components/elements/Cards/ItemMonitoringCard/__snapshots__/ItemMonitoringCard.stories.storyshot +++ b/src/components/elements/Cards/ItemMonitoringCard/__snapshots__/ItemMonitoringCard.stories.storyshot @@ -2,7 +2,7 @@ exports[`Storyshots Components/Elements/Cards/ItemMonitoringCards Default 1`] = `
{ + useValueChanged(sortOrder, () => { setSortLabel(sortOrder === "asc" ? t("Oldest to Newest") : t("Newest to Oldest")); - }, [sortOrder]); + }); const tabs = [ { key: "0", render: "All Images" }, @@ -321,9 +323,10 @@ const ImageGallery = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [pageIndex, pageSize, modelName]); - useEffect(() => { + useValueChanged(activeIndex, () => { onChangeGeotagged(activeIndex); - }, [activeIndex]); + }); + return ( <>
@@ -416,7 +419,7 @@ const ImageGallery = ({ )} , UseControllerProps { @@ -41,7 +42,7 @@ const ConditionalInput = (props: ConditionalInputProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.value, formHook]); - useEffect(() => { + useValueChanged(valueCondition, () => { if (valueCondition == true) { field.onChange(true); return; @@ -71,7 +72,7 @@ const ConditionalInput = (props: ConditionalInputProps) => { if (fieldsCount == fields?.length) { field.onChange(false); } - }, [valueCondition]); + }); return ( <> diff --git a/src/components/elements/Inputs/DataTable/DataTable.tsx b/src/components/elements/Inputs/DataTable/DataTable.tsx index 9156f0a42..acd9c5f45 100644 --- a/src/components/elements/Inputs/DataTable/DataTable.tsx +++ b/src/components/elements/Inputs/DataTable/DataTable.tsx @@ -67,7 +67,7 @@ function DataTable(props: DataTablePro handleCreate?.(fieldValues); closeModal(ModalId.FORM_MODAL); }, - [generateUuids, value, onChange, handleCreate, closeModal] + [generateUuids, onChange, value, handleCreate, closeModal, additionalValues] ); const onDeleteEntry = useCallback( diff --git a/src/components/elements/Inputs/Dropdown/Dropdown.tsx b/src/components/elements/Inputs/Dropdown/Dropdown.tsx index dff695d13..f15f595a2 100644 --- a/src/components/elements/Inputs/Dropdown/Dropdown.tsx +++ b/src/components/elements/Inputs/Dropdown/Dropdown.tsx @@ -272,7 +272,8 @@ const Dropdown = (props: PropsWithChildren) => { as="div" className={tw( "border-light absolute mt-2 max-h-[235px] min-w-full overflow-auto rounded-lg bg-white outline-none lg:max-h-[250px] wide:max-h-[266px]", - props.optionsClassName + props.optionsClassName, + variant.optionsClassName )} > diff --git a/src/components/elements/Inputs/Dropdown/DropdownVariant.tsx b/src/components/elements/Inputs/Dropdown/DropdownVariant.tsx index 2f7c8c071..e25eaf8e2 100644 --- a/src/components/elements/Inputs/Dropdown/DropdownVariant.tsx +++ b/src/components/elements/Inputs/Dropdown/DropdownVariant.tsx @@ -10,6 +10,7 @@ export interface DropdownVariant { iconNameClear?: IconNames; iconClearContainerClassName?: string; iconClearClassName?: string; + optionsClassName?: string; optionCheckboxClassName?: string; optionLabelClassName?: string; optionClassName?: string; @@ -63,3 +64,32 @@ export const VARIANT_DROPDOWN_SIMPLE: DropdownVariant = { optionLabelClassName: "text-14-semibold whitespace-nowrap", optionClassName: "gap-2" }; + +export const VARIANT_DROPDOWN_IMPACT_STORY: DropdownVariant = { + containerClassName: "relative", + className: "gap-2 !text-black border border-neutral-200 ", + iconClassName: "w-3 h-[9px] fill-trasparent", + iconName: IconNames.CHEVRON_DOWN_DASH, + iconNameClear: IconNames.CLEAR, + iconClearClassName: "w-3 h-3", + iconClearContainerClassName: "p-1 border border-neutral-200 rounded", + titleContainerClassName: "flex-1 overflow-hidden", + titleClassname: "leading-normal text-ellipsis whitespace-nowrap overflow-hidden", + optionCheckboxClassName: "checked:text-blueCustom-700", + optionLabelClassName: "text-14-semibold whitespace-nowrap", + optionClassName: "gap-2" +}; + +export const VARIANT_DROPDOWN_COLLAPSE: DropdownVariant = { + containerClassName: "", + className: "", + iconClassName: "w-4", + iconClearClassName: "w-4", + iconName: undefined, + titleContainerClassName: "flex items-center gap-2", + titleClassname: "hidden", + optionsClassName: "static bg-neutral-40 rounded-none", + optionClassName: "flex-row w-full", + optionCheckboxClassName: "w-4 h-4", + optionLabelClassName: "text-14-light" +}; diff --git a/src/components/elements/Inputs/FileInput/FileInput.tsx b/src/components/elements/Inputs/FileInput/FileInput.tsx index 75a6e7a07..1f26811c6 100644 --- a/src/components/elements/Inputs/FileInput/FileInput.tsx +++ b/src/components/elements/Inputs/FileInput/FileInput.tsx @@ -1,4 +1,5 @@ import { useT } from "@transifex/react"; +import classNames from "classnames"; import { ChangeEvent, Fragment, ReactNode, useId, useMemo, useRef } from "react"; import { useDropzone } from "react-dropzone"; import { UseFormReturn } from "react-hook-form"; @@ -34,6 +35,7 @@ export type FileInputProps = InputWrapperProps & { formHook?: UseFormReturn; updateFile?: (file: Partial) => void; entityData?: any; + classNameTextOr?: string; }; export interface FileStatus { @@ -148,7 +150,7 @@ const FileInput = (props: FileInputProps) => { {t("Click to upload")} - + {t("or")} diff --git a/src/components/elements/Inputs/FileInput/FileInputVariants.ts b/src/components/elements/Inputs/FileInput/FileInputVariants.ts index 00277208f..ab5f8b2f4 100644 --- a/src/components/elements/Inputs/FileInput/FileInputVariants.ts +++ b/src/components/elements/Inputs/FileInput/FileInputVariants.ts @@ -125,3 +125,11 @@ export const VARIANT_FILE_INPUT_MODAL_ADD_IMAGES_WITH_MAP: FileInputVariant = { listPreviewDescription: "flex items-center justify-between", filePreviewVariant: VARIANT_FILE_PREVIEW_CARD_MODAL_ADD_IMAGES_WITH_MAP }; + +export const VARIANT_FILE_INPUT_IMPACT_STORY: FileInputVariant = { + container: "flex flex-col items-center justify-center rounded-lg border border-grey-750 py-8", + snapshotPanel: true, + listPreview: "flex flex-col gap-4 w-full mt-2 mb-4 hidden", + listPreviewDescription: "flex items-center justify-between hidden", + filePreviewVariant: VARIANT_FILE_PREVIEW_CARD_MODAL_ADD_IMAGES_WITH_MAP +}; diff --git a/src/components/elements/Inputs/LanguageDropdown/LanguagesDropdown.tsx b/src/components/elements/Inputs/LanguageDropdown/LanguagesDropdown.tsx index 2f5f93271..a501de7e6 100644 --- a/src/components/elements/Inputs/LanguageDropdown/LanguagesDropdown.tsx +++ b/src/components/elements/Inputs/LanguageDropdown/LanguagesDropdown.tsx @@ -1,4 +1,5 @@ import { Popover } from "@headlessui/react"; +import { useMediaQuery } from "@mui/material"; import { useT } from "@transifex/react"; import classNames from "classnames"; import { useRouter } from "next/router"; @@ -10,10 +11,13 @@ import List from "@/components/extensive/List/List"; import { useValueChanged } from "@/hooks/useValueChanged"; import { Option, OptionValue } from "@/types/common"; +import { LanguagesDropdownVariant, VARIANT_LANGUAGES_DROPDOWN } from "./LanguagesDropdownVariant"; + export interface DropdownProps { defaultValue?: OptionValue; onChange?: (value: string) => void; className?: string; + variant?: LanguagesDropdownVariant; } const LANGUAGES: Option[] = [ @@ -28,8 +32,11 @@ const languageForLocale = (locale?: string | null) => LANGUAGES.find(({ value }) const LanguagesDropdown = (props: PropsWithChildren) => { const t = useT(); const router = useRouter(); - + const variantClass = props.variant ?? VARIANT_LANGUAGES_DROPDOWN; + const isMobile = useMediaQuery("(max-width: 1200px)"); const [selected, setSelected] = useState
`; diff --git a/src/components/elements/Inputs/Map/RHFMap.tsx b/src/components/elements/Inputs/Map/RHFMap.tsx index 2c5bc2301..c8c672a36 100644 --- a/src/components/elements/Inputs/Map/RHFMap.tsx +++ b/src/components/elements/Inputs/Map/RHFMap.tsx @@ -72,16 +72,18 @@ const RHFMap = ({ cacheTime: 0 } ); - const setBbboxAndZoom = async () => { - if (projectPolygon?.project_polygon?.poly_uuid) { - const bbox = await fetchGetV2TerrafundPolygonBboxUuid({ - pathParams: { uuid: projectPolygon.project_polygon?.poly_uuid ?? "" } - }); - const bounds: any = bbox.bbox; - setPolygonBbox(bounds); - } - }; + useEffect(() => { + const setBbboxAndZoom = async () => { + if (projectPolygon?.project_polygon?.poly_uuid) { + const bbox = await fetchGetV2TerrafundPolygonBboxUuid({ + pathParams: { uuid: projectPolygon.project_polygon?.poly_uuid ?? "" } + }); + const bounds: any = bbox.bbox; + setPolygonBbox(bounds); + } + }; + const getDataProjectPolygon = async () => { if (!projectPolygon?.project_polygon) { setPolygonDataMap({ [FORM_POLYGONS]: [] }); @@ -93,8 +95,9 @@ const RHFMap = ({ setPolygonFromMap({ isOpen: true, uuid: projectPolygon?.project_polygon?.poly_uuid }); } }; + getDataProjectPolygon(); - }, [projectPolygon, isRefetching]); + }, [projectPolygon, isRefetching, setSelectPolygonFromMap]); useEffect(() => { if (entity) { diff --git a/src/components/elements/Inputs/MyAccountDropdown/MyAccountDropdown.tsx b/src/components/elements/Inputs/MyAccountDropdown/MyAccountDropdown.tsx new file mode 100644 index 000000000..2676a02b0 --- /dev/null +++ b/src/components/elements/Inputs/MyAccountDropdown/MyAccountDropdown.tsx @@ -0,0 +1,124 @@ +import { Popover } from "@headlessui/react"; +import { useT } from "@transifex/react"; +import classNames from "classnames"; +import { useRouter } from "next/router"; +import { PropsWithChildren, useMemo, useRef, useState } from "react"; + +import { removeAccessToken } from "@/admin/apiProvider/utils/token"; +import Icon, { IconNames } from "@/components/extensive/Icon/Icon"; +import List from "@/components/extensive/List/List"; +import { useMyUser } from "@/connections/User"; + +import Text from "../../Text/Text"; +import { MyAccountDropdownVariant, VARIANT_MY_ACCOUNT_DROPDOWN } from "./MyAccountDropdownVariant"; + +export interface MyAccountDropdownProps { + variant?: MyAccountDropdownVariant; + className?: string; + isLoggedIn?: boolean; +} + +const MyAccountDropdown = (props: PropsWithChildren) => { + const t = useT(); + const router = useRouter(); + const rootPath = router.asPath.split("?")[0].split("/")[1]; + const isOnDashboard = rootPath === "dashboard"; + const [loaded, { isAdmin }] = useMyUser(); + const variantClass = props.variant ?? VARIANT_MY_ACCOUNT_DROPDOWN; + const [selectedIndex, setSelectedIndex] = useState(0); + + let buttonRef = useRef(); + + const OptionMyAccount = useMemo(() => { + return props.isLoggedIn + ? [ + { + value: isOnDashboard ? (isAdmin ? "Admin view" : "Project Developer view") : "Dashboard", + title: isOnDashboard ? (isAdmin ? "Admin view" : "Project Developer view") : "Dashboard", + icon: IconNames.IC_SWITCH + }, + { + value: "Logout", + title: "Logout", + icon: IconNames.LOGOUT + } + ] + : [ + { + value: "Go To Login", + title: "Go To Login", + icon: IconNames.LOGOUT + } + ]; + }, [props.isLoggedIn, isOnDashboard, isAdmin]); + + const onChange = (item: any) => { + if (item.value === "Go To Login") { + router.push("/auth/login"); + } else if (item.value === "Logout") { + removeAccessToken(); + router.push("/auth/login"); + } else { + if (!loaded) return; + if (isOnDashboard) { + if (isAdmin) { + router.push("/admin"); + } + router.push("/home"); + } else { + router.push("/dashboard"); + } + } + setTimeout(() => { + router.reload(); + }, 1000); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter" || event.key === " ") { + onChange(OptionMyAccount[selectedIndex]); + } + if (event.key === "ArrowDown") { + setSelectedIndex(prev => (prev + 1) % OptionMyAccount.length); + } else if (event.key === "ArrowUp") { + setSelectedIndex(prev => (prev - 1 + OptionMyAccount.length) % OptionMyAccount.length); + } + }; + + return ( +
+ + +
+ + +
+ {t("MY ACCOUNT")} + +
+ + + ( + onChange(item)} + > + + + {t(item.title)} + + )} + className={variantClass.classList} + /> + +
+
+ ); +}; + +export default MyAccountDropdown; diff --git a/src/components/elements/Inputs/MyAccountDropdown/MyAccountDropdownVariant.ts b/src/components/elements/Inputs/MyAccountDropdown/MyAccountDropdownVariant.ts new file mode 100644 index 000000000..7ae79df9c --- /dev/null +++ b/src/components/elements/Inputs/MyAccountDropdown/MyAccountDropdownVariant.ts @@ -0,0 +1,51 @@ +import { IconNames } from "@/components/extensive/Icon/Icon"; + +export interface MyAccountDropdownVariant { + classIcon: string; + classButtonPopover?: string; + classText: string; + icon: IconNames; + arrowIcon: IconNames; + arrowDashboardClass: string; + arrowNavbarClass: string; + classPanel: string; + classList: string; + classItem: string; + classIconSelected: string; + classContent: string; + classContentOpen: string; +} + +export const VARIANT_MY_ACCOUNT_DROPDOWN: MyAccountDropdownVariant = { + classIcon: "mr-2 text-neutral-700", + classButtonPopover: "flex items-center justify-between p-2", + classText: "text-14-light mr-2 whitespace-nowrap text-sm uppercase text-darkCustom", + icon: IconNames.MY_ACCOUNT, + arrowIcon: IconNames.TRIANGLE_DOWN, + arrowDashboardClass: "hidden", + arrowNavbarClass: "transition fill-neutral-700 ui-open:rotate-180 ui-open:transform", + classPanel: "border-1 absolute right-0 z-50 mt-4 w-auto border border-neutral-300 bg-white shadow", + classList: "", + classItem: + "px-3 py-1 text-neutral-900 first:pt-2 last:pb-2 hover:bg-neutral-200 whitespace-nowrap flex items-center gap-2 cursor-pointer", + classIconSelected: "w-3 h-3", + classContent: "relative w-fit", + classContentOpen: "" +}; + +export const VARIANT_MY_ACCOUNT_DROPDOWN_SECONDARY: MyAccountDropdownVariant = { + classIcon: "text-white w-8 h-8", + classButtonPopover: "flex flex-col items-start outline-none opacity-50 aria-expanded:opacity-100", + classText: "hidden", + icon: IconNames.IC_USER, + arrowIcon: IconNames.CHEVRON_DOWN, + arrowDashboardClass: "transition fill-white ui-open:rotate-180 ui-open:transform h-2.5 w-2.5 min-w-2.5", + arrowNavbarClass: "hidden", + classPanel: "border-1 absolute bottom-0 left-full z-50 ml-4 w-auto bg-white shadow rounded-lg overflow-hidden", + classList: "divide-y divide-grey-950", + classItem: + "py-2 px-3 hover:bg-neutral-200 text-black !font-normal flex items-center gap-2 whitespace-nowrap cursor-pointer", + classIconSelected: "w-3 h-3", + classContent: "relative w-fit", + classContentOpen: "!opacity-100" +}; diff --git a/src/components/elements/Inputs/textArea/TextArea.tsx b/src/components/elements/Inputs/textArea/TextArea.tsx index bea8c4742..cd9927ddf 100644 --- a/src/components/elements/Inputs/textArea/TextArea.tsx +++ b/src/components/elements/Inputs/textArea/TextArea.tsx @@ -64,7 +64,7 @@ const TextArea = ({ formHook, className, onChange: externalOnChange, ...inputWra