diff --git a/src/components/Campaigns/CloneCampaign.tsx b/src/components/Campaigns/CloneCampaign.tsx index a8915a61f..088c31ab4 100644 --- a/src/components/Campaigns/CloneCampaign.tsx +++ b/src/components/Campaigns/CloneCampaign.tsx @@ -1,17 +1,18 @@ import { Box, Button, - Chip, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, LinearProgress, + Tooltip, } from "@mui/material"; import { - CampaignFragment, + CampaignSummaryFragment, useCreateCampaignMutation, + useLoadCampaignLazyQuery, } from "graphql/campaign.generated"; import { useHistory } from "react-router-dom"; import { useContext, useState } from "react"; @@ -21,20 +22,21 @@ import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import { useUser } from "auth/hooks/queries/useUser"; import { FilterContext } from "state/context"; +import { CampaignFormat, CampaignSource } from "graphql/types"; interface Props { - campaignFragment?: CampaignFragment | null; - useChip?: boolean; + campaign?: CampaignSummaryFragment; disabled?: boolean; } -export function CloneCampaign({ campaignFragment, useChip, disabled }: Props) { +export function CloneCampaign({ campaign, disabled }: Props) { const { advertiser } = useAdvertiser(); const { fromDate } = useContext(FilterContext); const { userId } = useUser(); const history = useHistory(); const [open, setOpen] = useState(false); + const [getCampaign, { loading: getLoading }] = useLoadCampaignLazyQuery(); const [copyCampaign, { loading }] = useCreateCampaignMutation({ refetchQueries: [ { @@ -54,59 +56,72 @@ export function CloneCampaign({ campaignFragment, useChip, disabled }: Props) { }, }); + const doClone = async () => { + if (campaign) { + getCampaign({ + variables: { id: campaign.id }, + onCompleted(data) { + if (data.campaign) { + copyCampaign({ + variables: { + input: createCampaignFromFragment(data.campaign, userId), + }, + }); + } else { + alert("Unable to clone campaign"); + } + }, + }); + } + }; + + const canClone = + campaign && + campaign.source === CampaignSource.SelfServe && + [CampaignFormat.PushNotification, CampaignFormat.NewsDisplayAd].includes( + campaign.format, + ); return ( - {useChip ? ( - { - setOpen(true); - }} - disabled={loading || !campaignFragment || disabled} - icon={} - /> - ) : ( - - )} + + + + + setOpen(false)}> - {`Copy campaign: "${campaignFragment?.name}"?`} + {`Copy campaign: "${campaign?.name}"?`} Cloning a campaign will take all properties including ad sets and ads, and create a new draft campaign with them. - {loading && } + {(loading || getLoading) && } + + + ); +}; diff --git a/src/user/views/adsManager/views/advanced/components/adSet/AdSetFields.tsx b/src/user/views/adsManager/views/advanced/components/adSet/AdSetFields.tsx index 9daf4f26a..393eeec12 100644 --- a/src/user/views/adsManager/views/advanced/components/adSet/AdSetFields.tsx +++ b/src/user/views/adsManager/views/advanced/components/adSet/AdSetFields.tsx @@ -4,7 +4,7 @@ import { CardContainer } from "components/Card/CardContainer"; import { useHistory } from "react-router-dom"; import { FormikTextField, useIsEdit } from "form/FormikHelpers"; import { AdSetAds } from "user/views/adsManager/views/advanced/components/adSet/fields/AdSetAds"; -import { adSetOnOffState } from "components/EnhancedTable/renderers"; +import { adSetOnOffState } from "components/Datagrid/renderers"; import { Stack } from "@mui/material"; import { useFormikContext } from "formik"; import { CampaignForm } from "user/views/adsManager/types"; diff --git a/src/user/views/user/AdDetailTable.tsx b/src/user/views/user/AdDetailTable.tsx index 7f9e30be9..a57e442cc 100644 --- a/src/user/views/user/AdDetailTable.tsx +++ b/src/user/views/user/AdDetailTable.tsx @@ -1,18 +1,15 @@ -import { ColumnDescriptor, EnhancedTable } from "components/EnhancedTable"; import { CampaignAdsFragment } from "graphql/campaign.generated"; import { CampaignFormat } from "graphql/types"; import { StatsMetric } from "user/analytics/analyticsOverview/types"; import { renderStatsCell } from "user/analytics/renderers"; +import { DataGrid, GridColDef, GridValidRowModel } from "@mui/x-data-grid"; -interface Props { +interface Props { rows: T[]; - columns: ColumnDescriptor[]; + columns: GridColDef[]; engagements: Map; loading: boolean; campaign?: Omit | null; - propOverride?: { - initialSortColumn?: number; - }; } export function AdDetailTable({ @@ -21,63 +18,94 @@ export function AdDetailTable({ campaign, engagements, loading, - propOverride, }: Props) { const displayColumns = [...columns]; if (campaign?.format !== CampaignFormat.NtpSi) { displayColumns.push( { - title: "Spend", - value: (c) => engagements.get(c.id)?.spend ?? "N/A", - extendedRenderer: (r) => + field: "spend", + headerName: "Spend", + valueGetter: ({ row }) => engagements.get(row.id)?.spend ?? "N/A", + renderCell: ({ row }) => renderStatsCell( loading, "spend", - engagements.get(r.id), + engagements.get(row.id), campaign?.currency, ), align: "right", + headerAlign: "right", + minWidth: 100, + maxWidth: 250, }, { - title: "Impressions", - value: (c) => engagements.get(c.id)?.views ?? "N/A", - extendedRenderer: (r) => - renderStatsCell(loading, "views", engagements.get(r.id)), + field: "view", + headerName: "Impressions", + valueGetter: ({ row }) => engagements.get(row.id)?.views ?? "N/A", + renderCell: ({ row }) => + renderStatsCell(loading, "views", engagements.get(row.id)), align: "right", + headerAlign: "right", + minWidth: 100, + maxWidth: 250, }, { - title: "Clicks", - value: (c) => engagements.get(c.id)?.clicks, - extendedRenderer: (r) => - renderStatsCell(loading, "clicks", engagements.get(r.id)), + field: "click", + headerName: "Clicks", + valueGetter: ({ row }) => engagements.get(row.id)?.clicks, + renderCell: ({ row }) => + renderStatsCell(loading, "clicks", engagements.get(row.id)), align: "right", + headerAlign: "right", + minWidth: 100, + maxWidth: 250, }, { - title: "10s Visits", - value: (c) => engagements.get(c.id)?.landings, - extendedRenderer: (r) => - renderStatsCell(loading, "landings", engagements.get(r.id)), + field: "landed", + headerName: "10s Visits", + valueGetter: ({ row }) => engagements.get(row.id)?.landings, + renderCell: ({ row }) => + renderStatsCell(loading, "landings", engagements.get(row.id)), align: "right", + headerAlign: "right", + minWidth: 100, + maxWidth: 250, }, { - title: "CTR", - value: (c) => engagements.get(c.id)?.ctr, - extendedRenderer: (r) => - renderStatsCell(loading, "ctr", engagements.get(r.id)), + field: "ctr", + headerName: "CTR", + valueGetter: ({ row }) => engagements.get(row.id)?.ctr, + renderCell: ({ row }) => + renderStatsCell(loading, "ctr", engagements.get(row.id)), align: "right", + headerAlign: "right", + minWidth: 100, + maxWidth: 250, }, ); } return ( - ); } diff --git a/src/user/views/user/CampaignView.tsx b/src/user/views/user/CampaignView.tsx index 12638300a..48327b2cd 100644 --- a/src/user/views/user/CampaignView.tsx +++ b/src/user/views/user/CampaignView.tsx @@ -1,39 +1,17 @@ -import { Box, Chip, Skeleton, Stack, Tooltip, Typography } from "@mui/material"; -import { useCallback, useContext, useState } from "react"; +import { Box, Skeleton } from "@mui/material"; +import { useContext } from "react"; import { useAdvertiserCampaignsQuery } from "graphql/advertiser.generated"; import { CampaignAgeFilter } from "components/Campaigns/CampaignAgeFilter"; import { CampaignList } from "user/campaignList/CampaignList"; import { ErrorDetail } from "components/Error/ErrorDetail"; import { CardContainer } from "components/Card/CardContainer"; import MiniSideBar from "components/Drawer/MiniSideBar"; -import { useLoadCampaignQuery } from "graphql/campaign.generated"; -import { CampaignFormat, CampaignSource } from "graphql/types"; import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; -import { Link as RouterLink } from "react-router-dom"; -import { CloneCampaign } from "components/Campaigns/CloneCampaign"; -import EditIcon from "@mui/icons-material/Edit"; import { FilterContext } from "state/context"; export function CampaignView() { const { advertiser } = useAdvertiser(); const { fromDate } = useContext(FilterContext); - const [selectedCampaigns, setSelectedCampaigns] = useState([]); - - const handleCampaignSelect = useCallback( - (c: string, include: boolean) => { - const indexOfId = selectedCampaigns.findIndex((summary) => c === summary); - if (include && indexOfId === -1) { - setSelectedCampaigns([...selectedCampaigns, c]); - } else if (!include && indexOfId >= 0) { - const res = - selectedCampaigns.length === 1 - ? [] - : selectedCampaigns.splice(indexOfId, 1); - setSelectedCampaigns(res); - } - }, - [selectedCampaigns], - ); const { loading, data, error } = useAdvertiserCampaignsQuery({ variables: { @@ -57,13 +35,8 @@ export function CampaignView() { return ( - ) : ( - "Campaigns" - ) - } + header="Campaigns" + useTypography sx={{ flexGrow: 1, overflowX: "auto", @@ -71,11 +44,7 @@ export function CampaignView() { additionalAction={} > {!loading ? ( - + ) : ( @@ -85,55 +54,3 @@ export function CampaignView() { ); } - -function CampaignHeader(props: { selectedCampaigns: string[] }) { - const editableCampaigns = [ - CampaignFormat.PushNotification, - CampaignFormat.NewsDisplayAd, - ]; - const oneCampaignSelected = props.selectedCampaigns.length === 1; - const firstCampaign = oneCampaignSelected ? props.selectedCampaigns[0] : null; - const { data, loading } = useLoadCampaignQuery({ - variables: { id: firstCampaign ?? "" }, - skip: !oneCampaignSelected || !firstCampaign, - }); - - let canClone = false; - let canEdit = false; - if (!loading && data?.campaign) { - canClone = - data.campaign.source === CampaignSource.SelfServe && - editableCampaigns.includes(data.campaign.format); - canEdit = canClone && data.campaign.state !== "completed"; - } - - return ( - - Campaigns - - - - - - - - - } - clickable - /> - - - - - ); -}