From bb999601d738f6a21ae98814c073b03d85f71af4 Mon Sep 17 00:00:00 2001 From: Carsten Koch Date: Sat, 23 Nov 2024 18:28:18 +0100 Subject: [PATCH] feat: checklist in weekly planning with inbox zero, and upload financial data and projects --- api/useLatestUploadsWork.tsx | 90 +++++++++++++++++++ .../instructions/instructions-upload-mrr.tsx | 19 +++- components/analytics/useMrrFilter.tsx | 2 +- components/crm/import-project-data.tsx | 29 +++++- components/planning/week/PlanWeekAction.tsx | 13 +++ .../planning/week/PlanWeekStatistics.tsx | 8 +- docs/releases/next.md | 5 +- pages/planweek.tsx | 47 ++++++++-- 8 files changed, 190 insertions(+), 23 deletions(-) create mode 100644 api/useLatestUploadsWork.tsx create mode 100644 components/planning/week/PlanWeekAction.tsx diff --git a/api/useLatestUploadsWork.tsx b/api/useLatestUploadsWork.tsx new file mode 100644 index 000000000..0af6aefc1 --- /dev/null +++ b/api/useLatestUploadsWork.tsx @@ -0,0 +1,90 @@ +import { type Schema } from "@/amplify/data/resource"; +import { generateClient, SelectionSet } from "aws-amplify/data"; +import { differenceInCalendarDays } from "date-fns"; +import { first, flow, get, identity } from "lodash/fp"; +import useSWR from "swr"; +import { handleApiErrors } from "./globals"; +const client = generateClient(); + +const selectionSet = ["createdAt"] as const; + +type MrrLatestUploadData = SelectionSet< + Schema["MrrDataUpload"]["type"], + typeof selectionSet +>; + +type SfdcLatestUploadData = SelectionSet< + Schema["CrmProjectImport"]["type"], + typeof selectionSet +>; + +const isTooOld = (dateStr: string | undefined) => + !dateStr || differenceInCalendarDays(new Date(), dateStr) >= 7; + +const fetchSfdcLatestUpload = async () => { + const { data, errors } = + await client.models.CrmProjectImport.listByImportStatus( + { + status: "DONE", + }, + { + sortDirection: "DESC", + limit: 1, + selectionSet, + } + ); + if (errors) { + handleApiErrors(errors, "Loading SfdcLatestUpload failed"); + throw errors; + } + try { + return flow( + identity, + first, + get("createdAt"), + isTooOld + )(data); + } catch (error) { + console.error("fetchSfdcLatestUpload", error); + throw error; + } +}; + +const fetchMrrLatestUpload = async () => { + const { data, errors } = + await client.models.MrrDataUpload.listMrrDataUploadByStatusAndCreatedAt( + { status: "DONE" }, + { sortDirection: "DESC", limit: 1, selectionSet } + ); + if (errors) { + handleApiErrors(errors, "Loading MrrLatestUpload failed"); + throw errors; + } + try { + return flow( + identity, + first, + get("createdAt"), + isTooOld + )(data); + } catch (error) { + console.error("fetchMrrLatestUpload", error); + throw error; + } +}; + +const useLatestUploadsWork = () => { + const { data: mrrUploadTooOld, mutate: mutateMrr } = useSWR( + "/api/mrr-latest", + fetchMrrLatestUpload + ); + + const { data: sfdcUploadTooOld, mutate: mutateSfdc } = useSWR( + "/api/sfdc-latest", + fetchSfdcLatestUpload + ); + + return { mrrUploadTooOld, mutateMrr, sfdcUploadTooOld, mutateSfdc }; +}; + +export default useLatestUploadsWork; diff --git a/components/analytics/instructions/instructions-upload-mrr.tsx b/components/analytics/instructions/instructions-upload-mrr.tsx index 35bee42cd..20f4b51ab 100644 --- a/components/analytics/instructions/instructions-upload-mrr.tsx +++ b/components/analytics/instructions/instructions-upload-mrr.tsx @@ -1,8 +1,10 @@ +import { MrrMutator } from "@/api/useMrr"; import imgDownload from "@/public/images/analytics/mrr-download.png"; import imgFilter from "@/public/images/analytics/mrr-filters.png"; import { ExternalLink } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; +import { FC } from "react"; import DefaultAccordionItem from "../../ui-elements/accordion/DefaultAccordionItem"; import BulletList from "../../ui-elements/list-items/bullet-list"; import { useMrrFilter } from "../useMrrFilter"; @@ -11,9 +13,22 @@ import ImportMrrData from "./import-data"; const mrrTableauLink = "https://awstableau.corp.amazon.com/t/WWSalesInsights/views/MonthlyRevenueDeep/DeepMonthlyRevenue?%3Aembed=yes&%3Alinktarget=_blank&%3Aoriginal_view=yes#1"; -const InstructionsUploadMrr = () => { +type InstructionsUploadMrrProps = { + reloader?: () => void; +}; + +const InstructionsUploadMrr: FC = ({ + reloader, +}) => { const { mrr, mutateMrr } = useMrrFilter(); + const reload: MrrMutator = (data) => { + if (reloader) { + reloader(); + } + return mutateMrr(data); + }; + return (
@@ -67,7 +82,7 @@ const InstructionsUploadMrr = () => {
Then upload the file here:
- +
); diff --git a/components/analytics/useMrrFilter.tsx b/components/analytics/useMrrFilter.tsx index 1c422cfee..2836e9395 100644 --- a/components/analytics/useMrrFilter.tsx +++ b/components/analytics/useMrrFilter.tsx @@ -31,7 +31,7 @@ interface MrrFilterProviderProps { children: React.ReactNode; } -const MrrFilterProvider: FC = ({ children }) => { +export const MrrFilterProvider: FC = ({ children }) => { const [mrrFilter, setMrrFilter] = useState("6"); const { mrr, isLoading, error, mutate } = useMrr("DONE", mrrFilter); diff --git a/components/crm/import-project-data.tsx b/components/crm/import-project-data.tsx index 9009d2e77..62642cf74 100644 --- a/components/crm/import-project-data.tsx +++ b/components/crm/import-project-data.tsx @@ -7,8 +7,9 @@ import { } from "@/helpers/crm/filters"; import { cn } from "@/lib/utils"; import { flow, map, sum } from "lodash/fp"; -import { Loader2 } from "lucide-react"; -import { useEffect, useState } from "react"; +import { ExternalLink, Loader2 } from "lucide-react"; +import Link from "next/link"; +import { FC, useEffect, useState } from "react"; import ApiLoadingError from "../layouts/ApiLoadingError"; import { Accordion } from "../ui/accordion"; import { Button } from "../ui/button"; @@ -19,7 +20,14 @@ import ChangedCrmProjects from "./changed-projects"; import MissingCrmProjects from "./missing-projects"; import NewCrmProjects from "./new-projects"; -const ImportProjectData = () => { +const linkSfdcReport = + "https://aws-crm.lightning.force.com/lightning/r/Report/00ORU000000oksT2AQ/view"; + +type ImportProjectDataProps = { + reloader?: () => void; +}; + +const ImportProjectData: FC = ({ reloader }) => { const { crmProjects, mutate } = useCrmProjects(); const { crmProjectsImport, @@ -78,12 +86,25 @@ const ImportProjectData = () => { }); }, [crmProjects, processedData]); + const handleClose = async () => { + await closeImportFile(); + reloader?.(); + }; + return (
{!crmProjectsImport && (
+
+ +
+ Link to SFDC opportunity report + +
+ +
{loadingImports ? (
Loading status of imported data… @@ -137,7 +158,7 @@ const ImportProjectData = () => { )} {crmProjectsImport && ( - + )}
); diff --git a/components/planning/week/PlanWeekAction.tsx b/components/planning/week/PlanWeekAction.tsx new file mode 100644 index 000000000..f98a1d250 --- /dev/null +++ b/components/planning/week/PlanWeekAction.tsx @@ -0,0 +1,13 @@ +import { FC } from "react"; + +type PlanWeekActionProps = { + label: string; +}; + +const PlanWeekAction: FC = ({ label }) => ( +
+ Next Action: {label} +
+); + +export default PlanWeekAction; diff --git a/components/planning/week/PlanWeekStatistics.tsx b/components/planning/week/PlanWeekStatistics.tsx index ad3104080..f38b64a12 100644 --- a/components/planning/week/PlanWeekStatistics.tsx +++ b/components/planning/week/PlanWeekStatistics.tsx @@ -8,14 +8,8 @@ const PlanWeekStatistics = () => { return (
- {!weekPlan ? ( - "Start Week Planning to review a list of projects for the current context." - ) : ( + {weekPlan && (
-
- Review each project and decide if you can make progress here during - the next week. -
Projects to be reviewed: {openProjectsCount}
Projects on hold: {onholdProjectsCount}
Projects in focus: {focusProjectsCount}
diff --git a/docs/releases/next.md b/docs/releases/next.md index eefcda382..17684ce36 100644 --- a/docs/releases/next.md +++ b/docs/releases/next.md @@ -4,6 +4,7 @@ - Neue Inbox Einträge können nun mit Cmd+Enter gespeichert werden. - Der Workflow für Inbox-Einträge ist vollständig überarbeitet und insgesamt schlüssiger und schneller. Die getroffene Entscheidung wird ausschließlich am Ende gespeichert und nicht mehr zwischendurch. - Inbox-Einträge können nun auch als Gelerntes über Personen gespeichert werden. +- In der Wochenplanung ist nun eine kleine Checkliste eingeführt. Zunächst werden offene Inbox-Einträge verarbeitet, dann aktuelle Umsätze und Projekte der Kunden geladen und schließlich – wie zuvor auch – die Projekte überprüft. ## Kleinere Verbesserungen @@ -11,8 +12,6 @@ ## In Arbeit -- In der Wochenplanung direkt auch anbieten, die Inbox zu verarbeiten. - ## Geplant ### Account Details @@ -38,7 +37,7 @@ - In Wochenplanung persönliche Termine mit berücksichtigen (Geburtstage, Jahrestage). - Ich möchte einfache Todos haben, die keinem Projekt zugeordnet sind. -- Eine Checkliste einführen für das wöchentliche oder tägliche Planen. +- Eine Checkliste einführen für das tägliche Planen. ### Projekte diff --git a/pages/planweek.tsx b/pages/planweek.tsx index d424cbc96..03f4b6029 100644 --- a/pages/planweek.tsx +++ b/pages/planweek.tsx @@ -1,3 +1,9 @@ +import useInbox from "@/api/useInbox"; +import useMrrLatestUpload from "@/api/useLatestUploadsWork"; +import InstructionsUploadMrr from "@/components/analytics/instructions/instructions-upload-mrr"; +import { MrrFilterProvider } from "@/components/analytics/useMrrFilter"; +import ImportProjectData from "@/components/crm/import-project-data"; +import ProcessInboxItem from "@/components/inbox/ProcessInboxItem"; import ApiLoadingError from "@/components/layouts/ApiLoadingError"; import MainLayout from "@/components/layouts/MainLayout"; import ContextSwitcher from "@/components/navigation-menu/ContextSwitcher"; @@ -6,16 +12,21 @@ import { useWeekPlanContext, withWeekPlan, } from "@/components/planning/useWeekPlanContext"; +import PlanWeekAction from "@/components/planning/week/PlanWeekAction"; import PlanWeekContextNotWork from "@/components/planning/week/PlanWeekContextNotWork"; import PlanWeekContextWork from "@/components/planning/week/PlanWeekContextWork"; import PlanWeekFilter from "@/components/planning/week/PlanWeekFilter"; import PlanWeekForm from "@/components/planning/week/PlanWeekForm"; import PlanWeekStatistics from "@/components/planning/week/PlanWeekStatistics"; +import { Accordion } from "@/components/ui/accordion"; import { useContextContext } from "@/contexts/ContextContext"; const WeeklyPlanningPage = () => { const { context } = useContextContext(); const { error } = useWeekPlanContext(); + const { inbox } = useInbox(); + const { mrrUploadTooOld, mutateMrr, sfdcUploadTooOld, mutateSfdc } = + useMrrLatestUpload(); return ( @@ -28,14 +39,38 @@ const WeeklyPlanningPage = () => {
- - + {inbox && inbox.length > 0 ? ( + <> + + + + ) : context === "work" && mrrUploadTooOld ? ( + <> + + + + + + + + ) : context === "work" && sfdcUploadTooOld ? ( + <> + + + + ) : ( + <> + + + - + - {context !== "work" && } - {context === "work" && } - + {context !== "work" && } + {context === "work" && } + + + )}
);