diff --git a/app/(root)/dashboard/page.tsx b/app/(root)/dashboard/page.tsx index 38c8ad7..733d45c 100644 --- a/app/(root)/dashboard/page.tsx +++ b/app/(root)/dashboard/page.tsx @@ -1,16 +1,14 @@ "use client"; import React from "react"; -import DashBoard from "@/components/ui/dashboard/index"; -import DashboardChart from "@/components/ui/dashboard/charts"; +import Layout from "@/components/ui/layout/index"; +import Dashboard from "@/components/ui/dashboard/dashboard"; export default function Page() { return ( - - - - - + + + ); }; diff --git a/app/(root)/page.tsx b/app/(root)/page.tsx index 38c8ad7..733d45c 100644 --- a/app/(root)/page.tsx +++ b/app/(root)/page.tsx @@ -1,16 +1,14 @@ "use client"; import React from "react"; -import DashBoard from "@/components/ui/dashboard/index"; -import DashboardChart from "@/components/ui/dashboard/charts"; +import Layout from "@/components/ui/layout/index"; +import Dashboard from "@/components/ui/dashboard/dashboard"; export default function Page() { return ( - - - - - + + + ); }; diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx new file mode 100644 index 0000000..18c69b2 --- /dev/null +++ b/app/dashboard/layout.tsx @@ -0,0 +1,38 @@ +import '../(root)/globals.css'; +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', +} +import { Analytics } from '@vercel/analytics/react'; +import { SessionProvider } from 'next-auth/react'; +import { SpeedInsights } from "@vercel/speed-insights/next" +import { NextUIProvider } from '@nextui-org/react'; +// import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { NotificationProvider } from '@/components/ui/NotificationContext'; +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + + + {/* */} +
+ + {children} + +
+ + + {/*
*/} +
+
+ + + + ) +} diff --git a/app/reports/automation/page.tsx b/app/reports/automation/page.tsx new file mode 100644 index 0000000..a318962 --- /dev/null +++ b/app/reports/automation/page.tsx @@ -0,0 +1,16 @@ +"use client"; +import ScheduleAutomationPage from "@/components/ui/report/automation/automation" +import React from "react"; +import Layout from "@/components/ui/layout/index"; + +const Page: React.FC = () => { + return ( + + + + ); + }; + + + + export default Page \ No newline at end of file diff --git a/app/reports/import/page.tsx b/app/reports/import/page.tsx new file mode 100644 index 0000000..09c1c71 --- /dev/null +++ b/app/reports/import/page.tsx @@ -0,0 +1,17 @@ +"use client"; + +import React from "react"; +import Layout from "@/components/ui/layout/index"; +import ImportPage from "@/components/ui/report/import/import"; + +const Page: React.FC = () => { + return ( + + + + ); +}; + + + +export default Page \ No newline at end of file diff --git a/app/reports/layout.tsx b/app/reports/layout.tsx new file mode 100644 index 0000000..14498d4 --- /dev/null +++ b/app/reports/layout.tsx @@ -0,0 +1,38 @@ +import '../globals.css'; +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', +} +import { Analytics } from '@vercel/analytics/react'; +import { SessionProvider } from 'next-auth/react'; +import { SpeedInsights } from "@vercel/speed-insights/next" +import { NextUIProvider } from '@nextui-org/react'; +// import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { NotificationProvider } from '@/components/ui/NotificationContext'; +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + + + {/* */} +
+ + {children} + +
+ + + {/*
*/} +
+
+ + + + ) +} diff --git a/app/reports/page.tsx b/app/reports/page.tsx new file mode 100644 index 0000000..c799f1e --- /dev/null +++ b/app/reports/page.tsx @@ -0,0 +1,17 @@ +"use client"; + +import React from "react"; +import Layout from "@/components/ui/layout/index"; +import Report from "@/components/ui/report/report"; + +const Page: React.FC = () => { + return ( + + + + ); +}; + + + +export default Page \ No newline at end of file diff --git a/components/ui/NotificationContext.tsx b/components/ui/NotificationContext.tsx new file mode 100644 index 0000000..605cead --- /dev/null +++ b/components/ui/NotificationContext.tsx @@ -0,0 +1,44 @@ +'use client'; + +import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react'; +import { toast, ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; +import Cookies from 'js-cookie'; + +interface NotificationContextProps { + notify: (message: string) => void; +} + +const NotificationContext = createContext(undefined); + +export const useNotification = () => { + const context = useContext(NotificationContext); + if (!context) { + throw new Error('useNotification must be used within a NotificationProvider'); + } + return context; +}; + +export function NotificationProvider({ children }: { children: ReactNode }) { + const [message, setMessage] = useState(null); + + const notify = (message: string) => { + setMessage(message); + Cookies.set('notification', message); + }; + + useEffect(() => { + const message = Cookies.get('notification'); + if (message) { + toast.success(message); + Cookies.remove('notification'); + } + }, []); + + return ( + + {children} + + + ); +}; \ No newline at end of file diff --git a/components/ui/dashboard/barChart.tsx b/components/ui/charts/barChart.tsx similarity index 61% rename from components/ui/dashboard/barChart.tsx rename to components/ui/charts/barChart.tsx index 0f8801a..001d390 100644 --- a/components/ui/dashboard/barChart.tsx +++ b/components/ui/charts/barChart.tsx @@ -3,8 +3,9 @@ import { BarChart, Card, Divider } from '@tremor/react'; interface BarChartProps { data: { [key: string]: any }[]; - indexKey?: string; - categoryKeys?: string[]; + index?: string; + title?: string; + categories?: string[]; colors?: string[]; valueFormatter?: (number: number) => string; yAxisWidth?: number; @@ -12,9 +13,10 @@ interface BarChartProps { } const BarChartComponent: React.FC = ({ + title = "", data, - indexKey = 'age', - categoryKeys = ['This Year'], + index = 'age', + categories = ['This Year'], colors = ['blue'], valueFormatter, yAxisWidth = 45, @@ -22,10 +24,15 @@ const BarChartComponent: React.FC = ({ }) => { return ( +
+

+ {title} +

+
- `${Intl.NumberFormat('us').format(number).toString()}`; +const defaultFormatter = (number: number | bigint) => { return number }; const BarListChart: React.FC = ({ title = 'Top Destination Url', diff --git a/components/ui/charts/donutChart.tsx b/components/ui/charts/donutChart.tsx new file mode 100644 index 0000000..3d5661e --- /dev/null +++ b/components/ui/charts/donutChart.tsx @@ -0,0 +1,55 @@ + +import { Card, DonutChart, List, ListItem } from '@tremor/react'; + +const currencyFormatter = (number: any) => { + return number +}; +function classNames(...classes: any) { return classes.filter(Boolean).join(' '); } + +export default function Example(props: any) { + return ( + <> + +

+ {props.title} +

+ +

+ Category + Total/Share +

+ + {props.data.map((item: any) => ( + +
+ + + {item.name} + {(item?.amount)?.toFixed(2)} + +
+
+ + {(item?.total)?.toFixed(2)}/{item.share} + +
+
+ ))} +
+
+ + ); +} + diff --git a/components/ui/dashboard/lineChart.tsx b/components/ui/charts/lineChart.tsx similarity index 91% rename from components/ui/dashboard/lineChart.tsx rename to components/ui/charts/lineChart.tsx index 8d6431e..7330e30 100644 --- a/components/ui/dashboard/lineChart.tsx +++ b/components/ui/charts/lineChart.tsx @@ -9,7 +9,7 @@ interface DataPoint { interface LineChartsProps { title?: string; subTitle?: string; - data: DataPoint[]; + data: any; categories?: string[]; colors?: string[]; valueFormatter?: (number: number | bigint) => string; @@ -19,14 +19,15 @@ interface LineChartsProps { cardClassName?: string; chartClassName?: string; index?: string; + props?: any } -const defaultFormatter = (number: number | bigint) => +const defaultFormatter = (number: number | bigint) => `${Intl.NumberFormat('us').format(number).toString()}`; const LineCharts: React.FC = ({ title = '', - subTitle="", + subTitle = "", data, categories = ['Organic'], colors = ['blue'], @@ -36,7 +37,8 @@ const LineCharts: React.FC = ({ startEndOnly = true, cardClassName = 'sm:mx-auto sm:max-w-md text-center', chartClassName = 'mt-6 h-32', - index="date" + index = "date", + props }) => { return ( @@ -47,6 +49,7 @@ const LineCharts: React.FC = ({ {subTitle} { +import {Slider } from "@nextui-org/react"; +const Dashboard = () => { const [value, setValue] = useState(0); const [startDate, setStartDate] = useState(new Date('2023-01-01').getTime()); const [endDate, setEndDate] = useState(new Date('2023-12-31').getTime()); const [filteredData, setFilteredData] = useState([]); + const handleChange = (event: React.ChangeEvent, isStart: boolean) => { - const newValue = +event.target.value; + const newValue = +event; if (isStart) { setValue(newValue); } else { @@ -37,7 +38,7 @@ const DashboardChart = () => { }; return ( -
+

Dashboard

@@ -45,17 +46,21 @@ const DashboardChart = () => {

Welcome, Xue!

- - {formatDate(value)} + handleChange(event, true)} - title={formatDate(value)} + defaultValue={[startDate, endDate]} + onChange={(event: any) => handleChange(event, true)} + aria-label={formatDate(value)} + classNames={{ + base: "max-w-md", + filler: "bg-orange-500" + }} + />
@@ -174,8 +179,8 @@ const DashboardChart = () => { /> { ); }; -export default DashboardChart; +export default Dashboard; diff --git a/components/ui/dashboard/dummyData.tsx b/components/ui/dashboard/dummyData.tsx deleted file mode 100644 index 3d6bb65..0000000 --- a/components/ui/dashboard/dummyData.tsx +++ /dev/null @@ -1,375 +0,0 @@ -export const data = [ - { - date: 'Jan 23', - Google: 232, - Sponsored: 0, - Affiliate: 49, - filterDate:'23/01/2023' - }, - { - date: 'Feb 23', - Google: 241, - Sponsored: 0, - Affiliate: 61, - filterDate:'23/02/2023' - }, - { - date: 'Mar 23', - Google: 291, - Sponsored: 0, - Affiliate: 55, - filterDate:'23/03/2023' - }, - { - date: 'Apr 23', - Google: 101, - Sponsored: 0, - Affiliate: 21, - filterDate:'23/04/2023' - }, - { - date: 'May 23', - Google: 318, - Sponsored: 0, - Affiliate: 66, - filterDate:'23/05/2023' - }, - { - date: 'Jun 23', - Google: 205, - Sponsored: 0, - Affiliate: 69, - filterDate:'23/06/2023' - }, - { - date: 'Jul 23', - Google: 372, - Sponsored: 0, - Affiliate: 55, - filterDate:'23/07/2023' - }, - { - date: 'Aug 23', - Google: 341, - Sponsored: 0, - Affiliate: 74, - filterDate:'23/08/2023' - }, - { - date: 'Sep 23', - Google: 387, - Sponsored: 120, - Affiliate: 190, - filterDate:'23/09/2023' - }, - { - date: 'Oct 23', - Google: 220, - Sponsored: 0, - Affiliate: 89, - filterDate:'23/10/2023' - }, - { - date: 'Nov 23', - Google: 372, - Sponsored: 0, - Affiliate: 144, - filterDate:'23/11/2023' - }, - { - date: 'Dec 23', - Google: 421, - Sponsored: 0, - Affiliate: 93, - filterDate:'23/12/2023' - }, -]; - - -interface DashboardData { - filter(arg0: (entry: any) => boolean): unknown; - impressions: { - date: Date; - impressions: number; - }[]; - clicks: { - date: Date; - clicks: number; - }[]; - amountSpent: { - date: Date; - amountSpent: number; - }[]; - conversions: { - month: string; - conversions: number; - }[]; - topChannels: { - campaign: string; - impressions: number; - clicks: number; - conversions: number; - }[]; - topKeywords: { - category: string; - items: { - name: string; - value: number; - }[]; - }[]; - topDestinationUrl: { - url: string; - clicks: number; - }[]; - clicksByDate: { - date: Date; - clicks: number; - }[]; -} - -export const demoData: DashboardData = { - impressions: [ - { - date: new Date("2023-01-01"), - impressions: 100000, - }, - { - date: new Date("2023-02-07"), - impressions: 200000, - }, - { - date: new Date("2023-02-14"), - impressions: 150000, - }, - { - date: new Date("2023-02-21"), - impressions: 180000, - }, - { - date: new Date("2023-02-28"), - impressions: 220000, - }, - ], - clicks: [ - { - date: new Date("2023-01-01"), - clicks: 1000, - }, - { - date: new Date("2023-02-07"), - clicks: 2000, - }, - { - date: new Date("2023-02-14"), - clicks: 1500, - }, - { - date: new Date("2023-02-21"), - clicks: 1800, - }, - { - date: new Date("2023-02-28"), - clicks: 2200, - }, - ], - amountSpent: [ - { - date: new Date("2023-01-01"), - amountSpent: 100, - }, - { - date: new Date("2023-02-07"), - amountSpent: 200, - }, - { - date: new Date("2023-02-14"), - amountSpent: 150, - }, - { - date: new Date("2023-02-21"), - amountSpent: 180, - }, - { - date: new Date("2023-02-28"), - amountSpent: 220, - }, - ], - conversions: [ - { - month: "January", - conversions: 50, - }, - { - month: "February", - conversions: 50, - }, - ], - topChannels: [ - { - campaign: "Campaign 1", - impressions: 1000, - clicks: 1000, - conversions: 100, - }, - { - campaign: "Campaign 2", - impressions: 800, - clicks: 800, - conversions: 800, - }, - { - campaign: "Campaign 3", - impressions: 600, - clicks: 600, - conversions: 600, - }, - ], - topKeywords: [ - { - category: "Category 1", - items: [ - { - name: "Keyword 1", - value: 1000, - }, - { - name: "Keyword 2", - value: 800, - }, - { - name: "Keyword 3", - value: 600, - }, - ], - }, - { - category: "Category 2", - items: [ - { - name: "Keyword 4", - value: 500, - }, - { - name: "Keyword 5", - value: 400, - }, - { - name: "Keyword 6", - value: 300, - }, - ], - }, - ], - topDestinationUrl: [ - { - url: "/packaging-machines/", - clicks: 300, - }, - { - url: "/packaging-machines/amax-series-traysealers/", - clicks: 200, - }, - { - url: "/distributors/", - clicks: 100, - }, - ], - clicksByDate: [ - { - date: new Date("2023-01-11"), - clicks: 250, - }, - { - date: new Date("2023-01-12"), - clicks: 230, - }, - { - date: new Date("2023-01-13"), - clicks: 210, - }, - { - date: new Date("2023-01-14"), - clicks: 200, - }, - { - date: new Date("2023-01-15"), - clicks: 220, - }, - ], - filter: function (arg0: (entry: any) => boolean): unknown { - throw new Error("Function not implemented."); - } -}; - -export const pages = [ - { - name: 'https://www.tesla.com', - value: 2019, - }, - { - name: 'https://mi-tech.ca', - value: 1053, - }, - { - name: 'https://abhaitech.com', - value: 997, - } -]; - - -export const keyWords = [ - { - name: 'Open AI', - value: 2019, - }, - { - name: 'Live Bots', - value: 1053, - }, - { - name: 'web3', - value: 997, - } -]; - -export const campaignsData = [ - { - id: 1, - name: "Google Shopping", - impressions: 12345, - clicks: 1234, - connections: 123, - }, - { - id: 2, - name: "Google Ads", - impressions: 23456, - clicks: 2345, - connections: 234, - }, - { - id: 3, - name: "Holiday Promo", - impressions: 34567, - clicks: 3456, - connections: 345, - } -]; -export const BarData = [ - { - age: '22-24', - 'This Year': 68560, - }, - { - age: '24-25', - 'This Year': 80233, - }, - { - age: '25-26', - 'This Year': 55123, - }, - { - age: '26-27', - 'This Year': 56000, - } -]; \ No newline at end of file diff --git a/components/ui/dashboard/icons.tsx b/components/ui/dashboard/icons.tsx new file mode 100644 index 0000000..528c0c4 --- /dev/null +++ b/components/ui/dashboard/icons.tsx @@ -0,0 +1,375 @@ +"use client" +import { JSX, SVGProps } from "react" +export function BarChartIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + ) +} +export function ChevronDownIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + ) +} +export function ChevronUpIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + ); +} + + +export function BellIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + ) +} + + +export function CircleAlertIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + ) +} + + +export function ClockIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + ) +} + + +export function CreditCardIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + ) +} + + +export function DollarSignIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + ) +} + + +export function FileIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + ) +} + + +export function FileTextIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + + + ) +} + + +export function FolderIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + ) +} + + +export function LayoutDashboardIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + + ) +} + + +export function LayoutGridIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + + ) +} + + + + +export function LogOutIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + ) +} + + +export function SearchIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + ) +} + + + + +export function UserIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + ) +} + + +export function UsersIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + + ) +} +export function Question(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + ) +} \ No newline at end of file diff --git a/components/ui/dashboard/index.tsx b/components/ui/dashboard/index.tsx deleted file mode 100644 index 8c9ceb9..0000000 --- a/components/ui/dashboard/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -"use client" - -export default function DashBoard(props: any) { - return (<> - {props.children} - - ) -} - diff --git a/components/ui/dummyData.tsx b/components/ui/dummyData.tsx new file mode 100644 index 0000000..10d8996 --- /dev/null +++ b/components/ui/dummyData.tsx @@ -0,0 +1,875 @@ +export const data = [ + { + date: 'Jan 23', + Google: 232, + Sponsored: 0, + Affiliate: 49, + filterDate: '23/01/2023' + }, + { + date: 'Feb 23', + Google: 241, + Sponsored: 0, + Affiliate: 61, + filterDate: '23/02/2023' + }, + { + date: 'Mar 23', + Google: 291, + Sponsored: 0, + Affiliate: 55, + filterDate: '23/03/2023' + }, + { + date: 'Apr 23', + Google: 101, + Sponsored: 0, + Affiliate: 21, + filterDate: '23/04/2023' + }, + { + date: 'May 23', + Google: 318, + Sponsored: 0, + Affiliate: 66, + filterDate: '23/05/2023' + }, + { + date: 'Jun 23', + Google: 205, + Sponsored: 0, + Affiliate: 69, + filterDate: '23/06/2023' + }, + { + date: 'Jul 23', + Google: 372, + Sponsored: 0, + Affiliate: 55, + filterDate: '23/07/2023' + }, + { + date: 'Aug 23', + Google: 341, + Sponsored: 0, + Affiliate: 74, + filterDate: '23/08/2023' + }, + { + date: 'Sep 23', + Google: 387, + Sponsored: 120, + Affiliate: 190, + filterDate: '23/09/2023' + }, + { + date: 'Oct 23', + Google: 220, + Sponsored: 0, + Affiliate: 89, + filterDate: '23/10/2023' + }, + { + date: 'Nov 23', + Google: 372, + Sponsored: 0, + Affiliate: 144, + filterDate: '23/11/2023' + }, + { + date: 'Dec 23', + Google: 421, + Sponsored: 0, + Affiliate: 93, + filterDate: '23/12/2023' + }, +]; + + +interface DashboardData { + filter(arg0: (entry: any) => boolean): unknown; + impressions: { + date: Date; + impressions: number; + }[]; + clicks: { + date: Date; + clicks: number; + }[]; + amountSpent: { + date: Date; + amountSpent: number; + }[]; + conversions: { + month: string; + conversions: number; + }[]; + topChannels: { + campaign: string; + impressions: number; + clicks: number; + conversions: number; + }[]; + topKeywords: { + category: string; + items: { + name: string; + value: number; + }[]; + }[]; + topDestinationUrl: { + url: string; + clicks: number; + }[]; + clicksByDate: { + date: Date; + clicks: number; + }[]; +} + +export const demoData: DashboardData = { + impressions: [ + { + date: new Date("2023-01-01"), + impressions: 100000, + }, + { + date: new Date("2023-02-07"), + impressions: 200000, + }, + { + date: new Date("2023-02-14"), + impressions: 150000, + }, + { + date: new Date("2023-02-21"), + impressions: 180000, + }, + { + date: new Date("2023-02-28"), + impressions: 220000, + }, + ], + clicks: [ + { + date: new Date("2023-01-01"), + clicks: 1000, + }, + { + date: new Date("2023-02-07"), + clicks: 2000, + }, + { + date: new Date("2023-02-14"), + clicks: 1500, + }, + { + date: new Date("2023-02-21"), + clicks: 1800, + }, + { + date: new Date("2023-02-28"), + clicks: 2200, + }, + ], + amountSpent: [ + { + date: new Date("2023-01-01"), + amountSpent: 100, + }, + { + date: new Date("2023-02-07"), + amountSpent: 200, + }, + { + date: new Date("2023-02-14"), + amountSpent: 150, + }, + { + date: new Date("2023-02-21"), + amountSpent: 180, + }, + { + date: new Date("2023-02-28"), + amountSpent: 220, + }, + ], + conversions: [ + { + month: "January", + conversions: 50, + }, + { + month: "February", + conversions: 50, + }, + ], + topChannels: [ + { + campaign: "Campaign 1", + impressions: 1000, + clicks: 1000, + conversions: 100, + }, + { + campaign: "Campaign 2", + impressions: 800, + clicks: 800, + conversions: 800, + }, + { + campaign: "Campaign 3", + impressions: 600, + clicks: 600, + conversions: 600, + }, + ], + topKeywords: [ + { + category: "Category 1", + items: [ + { + name: "Keyword 1", + value: 1000, + }, + { + name: "Keyword 2", + value: 800, + }, + { + name: "Keyword 3", + value: 600, + }, + ], + }, + { + category: "Category 2", + items: [ + { + name: "Keyword 4", + value: 500, + }, + { + name: "Keyword 5", + value: 400, + }, + { + name: "Keyword 6", + value: 300, + }, + ], + }, + ], + topDestinationUrl: [ + { + url: "/packaging-machines/", + clicks: 300, + }, + { + url: "/packaging-machines/amax-series-traysealers/", + clicks: 200, + }, + { + url: "/distributors/", + clicks: 100, + }, + ], + clicksByDate: [ + { + date: new Date("2023-01-11"), + clicks: 250, + }, + { + date: new Date("2023-01-12"), + clicks: 230, + }, + { + date: new Date("2023-01-13"), + clicks: 210, + }, + { + date: new Date("2023-01-14"), + clicks: 200, + }, + { + date: new Date("2023-01-15"), + clicks: 220, + }, + ], + filter: function (arg0: (entry: any) => boolean): unknown { + throw new Error("Function not implemented."); + } +}; + +export const pages = [ + { + name: 'https://www.tesla.com', + value: 2019, + }, + { + name: 'https://mi-tech.ca', + value: 1053, + }, + { + name: 'https://abhaitech.com', + value: 997, + } +]; + + +export const keyWords = [ + { + name: 'Open AI', + value: 2019, + }, + { + name: 'Live Bots', + value: 1053, + }, + { + name: 'web3', + value: 997, + } +]; + +export const campaignsData = [ + { + id: 1, + name: "Google Shopping", + impressions: 12345, + clicks: 1234, + connections: 123, + }, + { + id: 2, + name: "Google Ads", + impressions: 23456, + clicks: 2345, + connections: 234, + }, + { + id: 3, + name: "Holiday Promo", + impressions: 34567, + clicks: 3456, + connections: 345, + } +]; +export const BarData = [ + { + age: '22-24', + 'This Year': 68560, + }, + { + age: '24-25', + 'This Year': 80233, + }, + { + age: '25-26', + 'This Year': 55123, + }, + { + age: '26-27', + 'This Year': 56000, + } +]; + +export const animals = [ + { key: "cat", label: "Cat" }, + { key: "dog", label: "Dog" }, + { key: "elephant", label: "Elephant" }, + { key: "lion", label: "Lion" }, + { key: "tiger", label: "Tiger" }, + { key: "giraffe", label: "Giraffe" }, + { key: "dolphin", label: "Dolphin" }, + { key: "penguin", label: "Penguin" }, + { key: "zebra", label: "Zebra" }, + { key: "shark", label: "Shark" }, + { key: "whale", label: "Whale" }, + { key: "otter", label: "Otter" }, + { key: "crocodile", label: "Crocodile" } +]; + +export const Reports = [ + { key: "view_through_rates", label: "View Through Rates" }, + { key: "avg_cpc", label: "Avg CPC" }, + { key: "clicks", label: "Clicks" }, + { key: "conversion_rate", label: "Conversion Rate" }, + { key: "connection", label: "Connection" }, + { key: "cost", label: "Cost" }, + { key: "cost_conversion", label: "Cost/Conversion" }, + { key: "impression", label: "Impression" } +]; + +export const Channels = [ + { key: "google_ads", label: "Google Ads" }, + { key: "facebook", label: "Facebook" }, + { key: "instagram", label: "Instgram" }, + { key: "linkedin", label: "Linkedin" } +]; +export const Campaigns = [{ key: "summer_sale", label: "Summer Sale" }, +{ key: "holiday_sale", label: "Holiday Sale" }, +{ key: "instagram_sale", label: "Instgram Sale" }, +{ key: "linkedin_sale", label: "Linkedin Sale" }] + +export const ChartData = [ + { + channel: "linkedin", + campaign: "instagram_sale", + filterDate: "14/08/2023", + date: "Aug 14", + view_through_rates: 0.82, + avg_cpc: 8.44, + clicks: 618, + conversion_rate: 36.84, + connection: 321, + cost: 534.25, + cost_conversion: 45.77, + impression: 7495 + }, + { + channel: "google_ads", + campaign: "summer_sale", + filterDate: "23/03/2023", + date: "Mar 23", + view_through_rates: 0.37, + avg_cpc: 5.81, + clicks: 276, + conversion_rate: 53.18, + connection: 888, + cost: 896.43, + cost_conversion: 35.97, + impression: 5917 + }, + { + channel: "facebook", + campaign: "holiday_sale", + filterDate: "05/11/2023", + date: "Nov 5", + view_through_rates: 0.91, + avg_cpc: 2.75, + clicks: 124, + conversion_rate: 73.85, + connection: 997, + cost: 683.67, + cost_conversion: 11.24, + impression: 3095 + }, + { + channel: "instagram", + campaign: "linkedin_sale", + filterDate: "30/06/2023", + date: "Jun 30", + view_through_rates: 0.34, + avg_cpc: 9.12, + clicks: 946, + conversion_rate: 26.57, + connection: 361, + cost: 429.55, + cost_conversion: 52.73, + impression: 8645 + }, + { + channel: "linkedin", + campaign: "holiday_sale", + filterDate: "12/12/2023", + date: "Dec 12", + view_through_rates: 0.78, + avg_cpc: 7.63, + clicks: 471, + conversion_rate: 15.82, + connection: 648, + cost: 354.62, + cost_conversion: 76.82, + impression: 2690 + }, + { + channel: "google_ads", + campaign: "instagram_sale", + filterDate: "19/09/2023", + date: "Sep 19", + view_through_rates: 0.29, + avg_cpc: 1.44, + clicks: 513, + conversion_rate: 66.19, + connection: 267, + cost: 943.12, + cost_conversion: 14.75, + impression: 3991 + }, + { + channel: "facebook", + campaign: "linkedin_sale", + filterDate: "08/04/2023", + date: "Apr 8", + view_through_rates: 0.67, + avg_cpc: 4.26, + clicks: 705, + conversion_rate: 21.54, + connection: 146, + cost: 532.88, + cost_conversion: 68.77, + impression: 1653 + }, + { + channel: "instagram", + campaign: "summer_sale", + filterDate: "15/07/2023", + date: "Jul 15", + view_through_rates: 0.56, + avg_cpc: 6.75, + clicks: 487, + conversion_rate: 10.93, + connection: 523, + cost: 629.47, + cost_conversion: 37.92, + impression: 7325 + }, + { + channel: "linkedin", + campaign: "holiday_sale", + filterDate: "27/10/2023", + date: "Oct 27", + view_through_rates: 0.23, + avg_cpc: 8.11, + clicks: 352, + conversion_rate: 94.73, + connection: 819, + cost: 913.82, + cost_conversion: 88.24, + impression: 5861 + }, + { + channel: "google_ads", + campaign: "summer_sale", + filterDate: "22/02/2023", + date: "Feb 22", + view_through_rates: 0.85, + avg_cpc: 3.17, + clicks: 283, + conversion_rate: 50.82, + connection: 964, + cost: 876.26, + cost_conversion: 43.57, + impression: 7952 + }, + { + channel: "facebook", + campaign: "holiday_sale", + filterDate: "03/10/2023", + date: "Oct 3", + view_through_rates: 0.42, + avg_cpc: 1.68, + clicks: 235, + conversion_rate: 35.19, + connection: 372, + cost: 142.85, + cost_conversion: 92.51, + impression: 3612 + }, + { + channel: "instagram", + campaign: "linkedin_sale", + filterDate: "28/11/2023", + date: "Nov 28", + view_through_rates: 0.51, + avg_cpc: 2.74, + clicks: 592, + conversion_rate: 48.17, + connection: 516, + cost: 982.35, + cost_conversion: 36.89, + impression: 6801 + }, + { + channel: "linkedin", + campaign: "summer_sale", + filterDate: "07/06/2023", + date: "Jun 7", + view_through_rates: 0.38, + avg_cpc: 5.13, + clicks: 158, + conversion_rate: 12.85, + connection: 407, + cost: 645.25, + cost_conversion: 23.87, + impression: 5127 + }, + { + channel: "google_ads", + campaign: "instagram_sale", + filterDate: "18/01/2023", + date: "Jan 18", + view_through_rates: 0.16, + avg_cpc: 7.94, + clicks: 634, + conversion_rate: 82.49, + connection: 701, + cost: 364.25, + cost_conversion: 87.12, + impression: 9192 + }, + { + channel: "facebook", + campaign: "linkedin_sale", + filterDate: "25/05/2023", + date: "May 25", + view_through_rates: 0.62, + avg_cpc: 9.51, + clicks: 512, + conversion_rate: 29.58, + connection: 169, + cost: 278.36, + cost_conversion: 32.45, + impression: 7410 + }, + { + channel: "instagram", + campaign: "holiday_sale", + filterDate: "13/09/2023", + date: "Sep 13", + view_through_rates: 0.58, + avg_cpc: 1.95, + clicks: 475, + conversion_rate: 93.12, + connection: 549, + cost: 782.14, + cost_conversion: 94.52, + impression: 8365 + }, + { + channel: "linkedin", + campaign: "summer_sale", + filterDate: "21/03/2023", + date: "Mar 21", + view_through_rates: 0.77, + avg_cpc: 6.28, + clicks: 591, + conversion_rate: 79.13, + connection: 333, + cost: 217.48, + cost_conversion: 63.98, + impression: 4731 + }, + { + channel: "google_ads", + campaign: "holiday_sale", + filterDate: "04/08/2023", + date: "Aug 4", + view_through_rates: 0.59, + avg_cpc: 4.19, + clicks: 683, + conversion_rate: 42.51, + connection: 938, + cost: 157.93, + cost_conversion: 79.45, + impression: 2895 + }, + { + channel: "facebook", + campaign: "instagram_sale", + filterDate: "29/07/2023", + date: "Jul 29", + view_through_rates: 0.43, + avg_cpc: 3.11, + clicks: 256, + conversion_rate: 54.32, + connection: 788, + cost: 973.61, + cost_conversion: 22.91, + impression: 6571 + }, + { + channel: "linkedin", + campaign: "instagram_sale", + filterDate: "09/12/2023", + date: "Dec 9", + view_through_rates: 0.71, + avg_cpc: 3.88, + clicks: 412, + conversion_rate: 62.19, + connection: 692, + cost: 428.15, + cost_conversion: 18.34, + impression: 4378 + }, + { + channel: "google_ads", + campaign: "holiday_sale", + filterDate: "16/05/2023", + date: "May 16", + view_through_rates: 0.63, + avg_cpc: 2.93, + clicks: 319, + conversion_rate: 18.45, + connection: 569, + cost: 548.77, + cost_conversion: 25.61, + impression: 6093 + }, + { + channel: "facebook", + campaign: "summer_sale", + filterDate: "10/11/2023", + date: "Nov 10", + view_through_rates: 0.39, + avg_cpc: 6.82, + clicks: 548, + conversion_rate: 27.91, + connection: 388, + cost: 374.56, + cost_conversion: 47.89, + impression: 5176 + }, + { + channel: "instagram", + campaign: "holiday_sale", + filterDate: "20/01/2023", + date: "Jan 20", + view_through_rates: 0.45, + avg_cpc: 5.64, + clicks: 129, + conversion_rate: 39.27, + connection: 236, + cost: 742.89, + cost_conversion: 53.48, + impression: 4243 + }, + { + channel: "linkedin", + campaign: "linkedin_sale", + filterDate: "17/09/2023", + date: "Sep 17", + view_through_rates: 0.81, + avg_cpc: 4.72, + clicks: 256, + conversion_rate: 16.82, + connection: 419, + cost: 271.58, + cost_conversion: 62.45, + impression: 1965 + }, + { + channel: "google_ads", + campaign: "instagram_sale", + filterDate: "08/02/2023", + date: "Feb 8", + view_through_rates: 0.48, + avg_cpc: 3.27, + clicks: 752, + conversion_rate: 45.29, + connection: 524, + cost: 681.34, + cost_conversion: 38.65, + impression: 3981 + }, + { + channel: "facebook", + campaign: "summer_sale", + filterDate: "14/06/2023", + date: "Jun 14", + view_through_rates: 0.74, + avg_cpc: 9.24, + clicks: 415, + conversion_rate: 60.88, + connection: 791, + cost: 932.47, + cost_conversion: 86.21, + impression: 7695 + }, + { + channel: "instagram", + campaign: "linkedin_sale", + filterDate: "12/04/2023", + date: "Apr 12", + view_through_rates: 0.54, + avg_cpc: 7.19, + clicks: 618, + conversion_rate: 81.35, + connection: 398, + cost: 523.55, + cost_conversion: 47.83, + impression: 5982 + } +]; + + + + +export const chartsMetaData = [ + { + title: "View Through Rates", + subTitle: "$231", + categories: ["view_through_rates"], + colors: ["blue"], + }, + { + title: "Avg CPC", + subTitle: "$231", + categories: ["avg_cpc"], + colors: ["blue"], + }, + { + title: "Clicks", + subTitle: "231", + categories: ["clicks"], + colors: ["blue"], + }, + { + title: "Conversion Rate", + subTitle: "99%", + categories: ["conversion_rate"], + colors: ["blue"], + }, + { + title: "Connection", + subTitle: "1,231", + categories: ["connection"], + colors: ["blue"], + }, + { + title: "Cost", + subTitle: "$300.15", + categories: ["cost"], + colors: ["blue"], + }, + { + title: "Cost/Conversion", + subTitle: "$231", + categories: ["cost_conversion"], + colors: ["blue"], + }, + { + title: "Impression", + subTitle: "923", + categories: ["impression"], + colors: ["blue"], + }, +]; + +export const chartsSet1 = [ + { + title: "Connection", + subTitle: "1,231", + categories: ["connection"], + colors: ["blue"], + }, + { + title: "Cost", + subTitle: "$300.15", + categories: ["cost"], + colors: ["blue"], + }, + { + title: "Cost/Conversion", + subTitle: "$231", + categories: ["cost_conversion"], + colors: ["blue"], + }, + { + title: "Impression", + subTitle: "923", + categories: ["impression"], + colors: ["blue"], + }, +]; \ No newline at end of file diff --git a/components/ui/layout/index.tsx b/components/ui/layout/index.tsx new file mode 100644 index 0000000..0858586 --- /dev/null +++ b/components/ui/layout/index.tsx @@ -0,0 +1,217 @@ +"use client" +import Link from "next/link" +import { usePathname } from 'next/navigation' +import Image from 'next/image'; +import logo from '@/public/logo/logo.jpg' +import { + LayoutDashboardIcon, + UsersIcon, + FileIcon, + CreditCardIcon, + DollarSignIcon, + FolderIcon, + UserIcon, + ChevronDownIcon, + ChevronUpIcon, +} from '../dashboard/icons'; +import { CircleAlertIcon, SearchIcon, BellIcon, Question } from "../dashboard/icons"; +import { useState } from "react"; +import { Avatar } from '@nextui-org/react'; + +export default function Layout(props: any) { + const [isOpen, setIsOpen] = useState(false); + + const toggleSubMenu = () => { + setIsOpen(!isOpen); + }; + return ( +
+ +
+
+
+ +
+ There are 30 days left in your trial. + + Upgrade Account + +
+
+
+ + + + + XD + +
+
+ {props.children} +
+
+ ) +} + +interface NavLinkProps { + href: string; + icon: any; + label: string; + isActive?: boolean; + submenu?: SubNavLinkData[]; +} + +const NavLink: React.FC = ({ href, icon: Icon, label, submenu }) => { + const pathname = usePathname(); + const isActive = pathname === href; + const [isOpen, setIsOpen] = useState(false); + + const toggleSubMenu = () => { + setIsOpen(!isOpen); + }; + + return ( + +
+ +
+ + {label} + {submenu && isOpen && } + {submenu && !isOpen && } +
+ + {isOpen && submenu && ( +
    + {submenu.map((submenuItem, index) => ( +
  • + + {submenuItem.label} + +
  • + ))} +
+ )} +
+ + + ); +}; +interface SubNavLinkData { + href: string; + label: string; +} + +interface NavLinkData { + href: string; + icon?: React.ComponentType<{ className?: string }>; + label: string; + isActive?: boolean; + submenu?: SubNavLinkData[]; +} + + +const navLinks: NavLinkData[] = [ + { href: '/dashboard', icon: LayoutDashboardIcon, label: 'Dashboard' }, + { + href: '#', + icon: UsersIcon, + label: 'Channels', + submenu: [ + { href: '#', label: 'Public' }, + { href: '#', label: 'Private' }, + ], + }, + { + href: '#', icon: FileIcon, label: 'Campagins', submenu: [ + { href: '#', label: 'Campaigns' }, + { href: '#', label: 'Ad Gap' }, + { href: '#', label: 'Ads' } + ], + }, + { href: '#', icon: FileIcon, label: 'Analytics' }, + { + href: '/reports', + icon: CreditCardIcon, + label: 'Reports', + submenu: [ + { href: '/reports', label: 'Export' }, + { href: '/reports/import', label: 'Import' }, + { href: '#', label: 'Schedule Automation' } + ], + }, + { href: '#', icon: DollarSignIcon, label: 'Insights' }, + { href: '#', icon: FolderIcon, label: 'E-commerce' }, +]; + +const Nav: React.FC = () => { + return ( + + ); +}; diff --git a/components/ui/report/automation/automation.tsx b/components/ui/report/automation/automation.tsx new file mode 100644 index 0000000..ecb5939 --- /dev/null +++ b/components/ui/report/automation/automation.tsx @@ -0,0 +1,226 @@ +import Head from 'next/head'; +import { useState } from 'react'; +import { Select, SelectItem } from "@nextui-org/react"; +import { RadioGroup, Radio } from "@nextui-org/react"; +import { DatePicker } from "@nextui-org/react"; +import { TimeInput } from "@nextui-org/react"; +import { Time } from "@internationalized/date"; +import { Checkbox } from "@nextui-org/react"; +interface ScheduleForm { + reportName: string; + format: string; + startDate: string; + recurring: boolean; + repeats: string; + startTime: string; + endTime: string; + email: string; +} + +const ScheduleAutomationPage = () => { + const [selectedReports, setSelectedReports] = useState([]); + const [selectedRepeat, setSelectedRepeat] = useState([]); + const [orientation, setOrientation] = useState("horizontal"); + const [checked, setChecked] = useState(false); + const [scheduleForm, setScheduleForm] = useState({ + reportName: '', + format: '', + startDate: '', + recurring: false, + repeats: '', + startTime: '', + endTime: '', + email: '', + }); + + const handleChange = (event: any) => { + setChecked(event.target.checked); + }; + const handleChanges = (event: any) => { + setOrientation(event.target.value); + }; + + const handleFormChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setScheduleForm((prevForm) => ({ ...prevForm, [name]: value })); + }; + + const handleFormSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + console.log(scheduleForm); + }; + const chartTypes = ['Windows assets details report', 'Linux assets details report', 'Mac assets details report', 'Network devices details report', 'Cloud resources details report']; + const repeatOptions = ['Daily', 'Weekly', 'Monthly']; + return ( +
+ + Schedule Automation Page + +

Schedule Automation Page

+ +
+
+ + +
+ +
+
+ +
+ + PDF (Portrait) + PDF (Landscape) + + +
+
+
+ +
+ +
+ + Manual + Schedule + +
+ +
+
+
+ + + + {orientation === "schedule" && ( + <> +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ + { handleChange(e) }}> Recurring + +
+
+ + {checked && ( + <> +
+ + +
+ + )} + {!scheduleForm.recurring && ( +
+
+ + +
+
+ + +
+
+ )} + + )} + +
+ + +
+ + +
+ +
+ ); +}; + +export default ScheduleAutomationPage; \ No newline at end of file diff --git a/components/ui/report/import/import.tsx b/components/ui/report/import/import.tsx new file mode 100644 index 0000000..6612bdd --- /dev/null +++ b/components/ui/report/import/import.tsx @@ -0,0 +1,375 @@ +"use client" +import { useState } from "react"; +import PieChart from "../../charts/donutChart"; +import LineCharts from "../../charts/lineChart"; +import BarChartComponent from "../../charts/barChart"; +import * as XLSX from 'xlsx'; + +import { Select, SelectItem } from "@nextui-org/react"; +import { toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; +import Cookies from "js-cookie"; + +const ChartType = [{ key: "bar", label: "Bar Graph" }, { key: "line_chart", label: "Line Graph" }, { key: "pie_chart", label: "Pie Chart" }] + +const Report = () => { + const [file, setFile] = useState(); + const [selectedChannels, setSelectedChannels] = useState([]); + const [selectedCharts, setSelectedCharts] = useState([]); + const [selectedCampaigns, setSelectedCampaigns] = useState([]); + const [selectedReports, setSelectedReports] = useState([]); + const [filteredData, setFilteredData] = useState(); + const [chartTypes, setChartTypes] = useState(); + const [rawData, setRawData] = useState(); + + + const handleOnChange = (e: any) => { + setFile(e.target.files[0]); + }; + + const parseCSV = (csv: string) => { + const lines = csv.split('\n'); + const result: any[] = []; + const headers = lines[0].split(',').map(header => header.trim().replace(/^"|"$/g, '')); + const uniqueCampaigns = new Set(); + const uniqueChannels = new Set(); + const numericFields = new Set(); + + for (let i = 1; i < lines.length; i++) { + const obj: any = {}; + const currentLine = lines[i].split(','); + for (let j = 0; j < headers.length; j++) { + let value = currentLine[j]?.trim() || ''; + value = value.replace(/^"|"$/g, ''); + if (!isNaN(Number(value)) && value !== '') { + obj[headers[j]] = Number(value); + numericFields.add(headers[j]); + } else { + obj[headers[j]] = value; + } + } + if (obj.campaign) uniqueCampaigns.add(obj.campaign); + if (obj.channel) uniqueChannels.add(obj.channel); + result.push(obj); + } + setRawData(result) + const groupedData = groupChartData(result, "campaign"); + setFilteredData(groupedData); + setSelectedCampaigns(Array.from(uniqueCampaigns)); + setSelectedChannels(Array.from(uniqueChannels)); + setChartTypes(Array.from(numericFields)); + + }; + + const handleOnSubmit = (e: any) => { + try { + const fileReader = new FileReader(); + e.preventDefault(); + if (file) { + fileReader.onload = function (event: any) { + const text = event.target.result; + if (typeof text === 'string') { + parseCSV(text); + } else { + const workbook = XLSX.read(text, { type: 'binary' }); + const sheetName = workbook.SheetNames[0]; + const csv = XLSX.utils.sheet_to_csv(workbook.Sheets[sheetName]); + parseCSV(csv); + } + }; + const fileName = file.name; + const fileExtension = fileName.split('.').pop()?.toLowerCase(); + if (fileExtension === 'csv') { + fileReader.readAsText(file); + } else { + fileReader.readAsArrayBuffer(file); + } + toast.success('File has been imported successfully.'); + } else { + toast.error("Error While Importing File") + } + } catch { + toast.error("Error While Importing File") + } + + + }; + + const groupChartData = (data: any[], groupByKey: string): any => { + return data.reduce((acc, item) => { + const keyValue = item[groupByKey]; + const { [groupByKey]: _, ...groupData } = item; + + if (!acc[keyValue]) { + acc[keyValue] = []; + } + acc[keyValue].push(groupData); + return acc; + }, {} as Record); + }; + const getRandomColor = (): string => { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; + }; + const groupAndCalculateShares = (data: any[], groupByKey1: string, groupByKey2: string, numericalValueKey: string, key: string): any => { + const groupedData: any = {}; + + data.forEach(item => { + const campaignKey = item[groupByKey1]; + const saleType = item[groupByKey2]; + const numericalValue = item[numericalValueKey]; + + if (!groupedData[campaignKey]) { + groupedData[campaignKey] = []; + } + + const existingEntryIndex = groupedData[campaignKey].findIndex((entry: any) => entry.name === saleType); + + if (existingEntryIndex !== -1) { + groupedData[campaignKey][existingEntryIndex].total += numericalValue; + } else { + groupedData[campaignKey].push({ + name: saleType, + total: numericalValue, + share: '', + color: getRandomColor() + }); + } + }); + + Object.keys(groupedData).forEach(campaignKey => { + const totalCampaignValue = groupedData[campaignKey].reduce((total: number, sale: any) => total + sale.total, 0); + + groupedData[campaignKey].forEach((sale: any) => { + sale.share = ((sale.total / totalCampaignValue) * 100).toFixed(1) + '%'; + }); + }); + let resultData = groupedData[key].map((obj: any) => { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => [key.replace(/^"(.*)"$/, '$1'), value]) + ); + }); + return resultData; + }; + + + const transformDataForLineChart = (groupedData: any, valueKey: string): any[] => { + const transformedData: any[] = []; + Object.keys(groupedData).forEach(campaign => { + groupedData[campaign].forEach((data: any) => { + const { channel, date } = data; + const value = data[valueKey]; + let dateEntry = transformedData.find(entry => entry.date === date); + if (!dateEntry) { + dateEntry = { date }; + transformedData.push(dateEntry); + } + if (!dateEntry[channel]) { + dateEntry[channel] = value; + } else { + dateEntry[channel] += value; + } + }); + }); + return transformedData; + }; + + const transformData = (groupedData: any, valueKey: string, groupByKey: string, dateKey: string): any[] => { + const transformedData: any[] = []; + Object.keys(groupedData).forEach(group => { + groupedData[group].forEach((data: any) => { + const groupValue = data[groupByKey]; + const dateValue = data[dateKey]; + const value = data[valueKey]; + + let groupEntry = transformedData.find(entry => entry[groupByKey] === groupValue && entry[dateKey] === dateValue); + if (!groupEntry) { + groupEntry = { [dateKey]: dateValue }; + transformedData.push(groupEntry); + } + if (!groupEntry[valueKey]) { + groupEntry[valueKey] = value; + } else { + groupEntry[valueKey] += value; + } + }); + }); + return transformedData; + }; + + const handleSelect = (e: any) => { + const array = e?.target?.value.split(","); + if (e.target.name === "reports") { + setSelectedReports(array) + } else if (e.target.name === "charts") { + setSelectedCharts(array) + } + } + + return ( +
+
+
+

Import

+
+

Welcome, Xue!

+
+
+

+ Click Browse button and choose a data file that you want to import. +

+
+ + + {file && ( + + {file.name} + + )} +

You are only allowed to upload CSV, XLSX or XLS files.

+
+ +
+ {chartTypes ? + + : null} + {ChartType && chartTypes ? + + : null} + {chartTypes ? + : null} +
+
+
+ + + {selectedCharts.includes("line_chart") ?
+

Line Graph

+ {selectedCampaigns?.map((group, rowIndex) => ( +
+

{group}

+
+ {selectedReports?.map((chart, colIndex) => ( +
+ +
+ ))} +
+
+ ))} +
: null} + {selectedCharts.includes("pie_chart") ?
+

Pie Chart

+ {selectedCampaigns?.map((group, rowIndex) => ( +
+

{group}

+
+ {selectedReports?.map((chart, colIndex) => ( +
+ +
+ ))} +
+
+ ))} +
: null} + + {selectedCharts.includes("bar") ?
+

Bar Graph

+ {selectedCampaigns?.map((group, rowIndex) => ( +
+

{group}

+
+ {selectedReports?.map((chart, colIndex) => ( +
+ +
+ ))} +
+
+ ))} +
: null + } +
+ ); +}; + +export default Report; diff --git a/components/ui/report/report.tsx b/components/ui/report/report.tsx new file mode 100644 index 0000000..5176628 --- /dev/null +++ b/components/ui/report/report.tsx @@ -0,0 +1,267 @@ +"use client" +import { useState } from "react"; +import LineCharts from "../charts/lineChart"; +import { data, ChartData, Reports, Channels, Campaigns, chartsMetaData, chartsSet1 } from "../dummyData"; +import { format } from 'date-fns'; +import { Select, SelectItem, Button, Slider } from "@nextui-org/react"; +import html2canvas from "html2canvas"; +import { jsPDF } from "jspdf"; + + +const Report = () => { + const [value, setValue] = useState(new Date('2023-01-01').getTime()); + const [selectedChannels, setSelectedChannels] = useState([]); + const [selectedCampaigns, setSelectedCampaigns] = useState([]); + const [selectedReports, setSelectedReports] = useState([]); + const [startDate, setStartDate] = useState(new Date('2023-01-01').getTime()); + const [endDate, setEndDate] = useState(new Date('2024-01-28').getTime()); + const [filteredData, setFilteredData] = useState(); + const [chartTypes, setChartTypes] = useState(); + + const handleChange = (event: React.ChangeEvent, isStart: boolean) => { + const newValue = +event; + if (isStart) { + setValue(newValue); + } else { + setValue(newValue); + } + applyFilters() + }; + + + const formatDate = (timestamp: number) => { + return format(new Date(timestamp), 'MM/dd/yyyy'); + }; + const monthMilliseconds = 30.44 * 24 * 60 * 60 * 1000; + + const applyFilters = () => { + const filteredChartsMetaData = chartsMetaData.filter(meta => + selectedReports.includes(meta.categories[0]) + ); + setChartTypes(filteredChartsMetaData) + let filteredDate = filterDataByDate(ChartData, startDate, endDate) + let filteredData = groupChartData(filteredDate); + setFilteredData(filteredData) + }; + + const groupChartData = (data: any[]): any => { + return data.reduce((acc, item) => { + const { campaign, ...campaignData } = item; + if (!acc[campaign]) { + acc[campaign] = []; + } + acc[campaign].push(campaignData); + return acc; + }, {} as any); + }; + const filterDataByDate = (data: any, startDate: number, endDate: number): any => { + const filtered: any = data.filter((entry: any) => { + const entryDate = new Date(entry.filterDate.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$2/$1/$3')).getTime(); + return entryDate >= value && entryDate <= endDate; + }); + return filtered + }; + + const transformDataForLineChart = (groupedData: any, valueKey: string): any[] => { + const transformedData: any[] = []; + Object.keys(groupedData).forEach(campaign => { + groupedData[campaign].forEach((data: any) => { + const { channel, date } = data; + const value = data[valueKey]; + let dateEntry = transformedData.find(entry => entry.date === date); + if (!dateEntry) { + dateEntry = { date }; + transformedData.push(dateEntry); + } + if (!dateEntry[channel]) { + dateEntry[channel] = value; + } else { + dateEntry[channel] += value; + } + }); + }); + return transformedData; + }; + + + const handleSelect = (e: any) => { + const array = e?.target?.value.split(","); + if (e?.target?.name === "channels") { + setSelectedChannels(array) + } else if (e.target.name === "campaigns") { + setSelectedCampaigns(array) + } else if (e.target.name === "reports") { + setSelectedReports(array) + } + applyFilters() + } + + const downloadPDF = async () => { + const pdf = new jsPDF(); + try { + + let element = document.getElementById("graphPart"); + if (!element || !isVisible(element)) { + throw new Error(`Element with id "${data}" not found or not visible.`); + } + const canvas = await html2canvas(element); + const imgData = canvas.toDataURL("image/png"); + + const imgWidth = canvas.width; + const imgHeight = canvas.height; + const aspectRatio = imgWidth / imgHeight; + const pdfWidth = pdf.internal.pageSize.getWidth() - 20; + const pdfHeight = pdfWidth / aspectRatio; + // if (i > 0) { + // pdf.addPage(); + // } + const x = (pdf.internal.pageSize.getWidth() - pdfWidth) / 2; + const y = (pdf.internal.pageSize.getHeight() - pdfHeight) / 2; + pdf.addImage(imgData, "PNG", x, y, pdfWidth, pdfHeight); + + pdf.save("chart.pdf"); + } catch (error) { + console.error("Error generating PDF:", error); + } + }; + function isVisible(elem: any) { + return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); + } + + + return ( +
+
+
+

Reports

+
+

Welcome, Xue!

+
+
+ + handleChange(event, true)} + aria-label={formatDate(value)} + classNames={{ + base: "max-w-md", + filler: "bg-orange-500" + }} + + /> + +
+ +
+
+
+
+
+ + + + +
+
+
+
+ +
+
+
+ {/*
+ {chartTypes?.map((chart, index) => ( +
+ + +
+ ))} +
*/} + +
+ {selectedCampaigns?.map((group, rowIndex) => ( +
+

{Campaigns.filter(data => data.key === group)[0]?.label}

+
+ {chartTypes?.map((chart, colIndex) => ( +
+ +
+ ))} +
+
+ ))} +
+
+ ); +}; + +export default Report; diff --git a/package.json b/package.json index e1ed13a..8b6cc7b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,11 @@ "ai": "^3.2.11", "axios": "^1.6.5", "react-dropzone": "^14.2.3", + "html2canvas": "^1.4.1", + "js-cookie": "^3.0.5", + "jspdf": "^2.5.1", + "react-toastify": "^10.0.5", + "xlsx": "^0.18.5", "zod": "^3.23.7" }, "prettier": { diff --git a/public/logo/logo.jpg b/public/logo/logo.jpg new file mode 100644 index 0000000..92b64d2 Binary files /dev/null and b/public/logo/logo.jpg differ