Skip to content

Commit a689cde

Browse files
authored
Merge pull request #1057 from AppQuality/rework-dashboard
feat: Refactor campaigns list and filters to use grouped campaigns hook
2 parents 4e9c51b + 27eabaa commit a689cde

File tree

6 files changed

+219
-159
lines changed

6 files changed

+219
-159
lines changed

src/pages/Dashboard/campaigns-list/index.tsx

Lines changed: 6 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,18 @@ import {
66
TextDescription,
77
theme,
88
} from '@appquality/unguess-design-system';
9-
import { createSelector } from '@reduxjs/toolkit';
10-
import { useEffect, useMemo, useState } from 'react';
9+
import { useEffect, useState } from 'react';
1110
import { useTranslation } from 'react-i18next';
12-
import { useAppSelector } from 'src/app/hooks';
1311
import { ReactComponent as GridIcon } from 'src/assets/icons/grid.svg';
1412
import { ReactComponent as ListIcon } from 'src/assets/icons/list.svg';
15-
import { useGetWorkspacesByWidCampaignsQuery } from 'src/features/api';
16-
import {
17-
selectFilteredCampaigns,
18-
selectGroupedCampaigns,
19-
} from 'src/features/campaigns';
20-
import { useActiveWorkspace } from 'src/hooks/useActiveWorkspace';
2113
import useWindowSize from 'src/hooks/useWindowSize';
2214
import styled from 'styled-components';
2315
import { Separator } from '../Separator';
2416
import { EmptyResults } from '../emptyState';
2517
import { Filters } from '../filters';
2618
import { CardList } from './list';
2719
import { TableList } from './table';
20+
import { useCampaignsGroupedByProject } from './useCampaignsGroupedByProject';
2821

2922
const FloatRight = styled.div`
3023
float: right;
@@ -33,42 +26,10 @@ const FloatRight = styled.div`
3326

3427
export const CampaignsList = () => {
3528
const { t } = useTranslation();
36-
const { activeWorkspace } = useActiveWorkspace();
3729
const { width } = useWindowSize();
3830
const breakpointMd = parseInt(theme.breakpoints.md, 10);
31+
const { count: campaignsCount } = useCampaignsGroupedByProject();
3932

40-
// Get workspaces campaigns from rtk query and filter them
41-
const filters = useAppSelector((state) => state.filters);
42-
43-
const getFilteredCampaigns = useMemo(
44-
() => createSelector(selectFilteredCampaigns, (campaigns) => campaigns),
45-
[]
46-
);
47-
48-
const { filteredCampaigns, isLoading, isFetching, isError } =
49-
useGetWorkspacesByWidCampaignsQuery(
50-
{
51-
wid: activeWorkspace?.id.toString() || '',
52-
orderBy: 'start_date',
53-
order: 'DESC',
54-
},
55-
{
56-
selectFromResult: (result) => ({
57-
...result,
58-
filteredCampaigns: getFilteredCampaigns(
59-
result?.data?.items || [],
60-
filters
61-
),
62-
}),
63-
}
64-
);
65-
66-
const campaigns = useMemo(
67-
() => selectGroupedCampaigns(filteredCampaigns),
68-
[filteredCampaigns]
69-
);
70-
71-
const campaignsCount = filteredCampaigns.length;
7233
const [viewType, setViewType] = useState('list');
7334

7435
useEffect(() => {
@@ -77,8 +38,6 @@ export const CampaignsList = () => {
7738
}
7839
}, [viewType, width]);
7940

80-
if (isLoading || isError || isFetching) return null;
81-
8241
return (
8342
<>
8443
<Row
@@ -120,14 +79,10 @@ export const CampaignsList = () => {
12079
)}
12180
</Row>
12281
<Separator style={{ marginTop: '0', marginBottom: theme.space.sm }} />
123-
<Filters campaigns={filteredCampaigns} />
82+
<Filters />
12483

125-
{campaignsCount > 0 && viewType === 'list' && (
126-
<TableList campaigns={campaigns} />
127-
)}
128-
{campaignsCount > 0 && viewType === 'grid' && (
129-
<CardList campaigns={campaigns} />
130-
)}
84+
{campaignsCount > 0 && viewType === 'list' && <TableList />}
85+
{campaignsCount > 0 && viewType === 'grid' && <CardList />}
13186
{!campaignsCount && <EmptyResults />}
13287
</>
13388
);

src/pages/Dashboard/campaigns-list/list.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Campaign } from 'src/features/api';
1212
import i18n from 'src/i18n';
1313
import styled from 'styled-components';
1414
import { CampaignItem } from '../CampaignItem';
15+
import { useCampaignsGroupedByProject } from './useCampaignsGroupedByProject';
1516

1617
const FloatRight = styled.div`
1718
float: right;
@@ -77,16 +78,19 @@ const CardGroup = ({ items }: { items: Array<Campaign> }) => {
7778
);
7879
};
7980

80-
export const CardList = ({
81-
campaigns,
82-
}: {
83-
campaigns: Array<Array<Campaign>>;
84-
}) => (
85-
<>
86-
{campaigns.map((group) => (
87-
<Row key={`cards_row_start_${group[0].id}`}>
88-
<CardGroup key={`cards_group_start_${group[0].id}`} items={group} />
89-
</Row>
90-
))}
91-
</>
92-
);
81+
export const CardList = () => {
82+
const { campaigns, isLoading, isFetching, isError } =
83+
useCampaignsGroupedByProject();
84+
85+
if (!campaigns.length || isLoading || isFetching || isError) return null;
86+
87+
return (
88+
<>
89+
{campaigns.map(({ project, items }) => (
90+
<Row key={`cards_row_start_${project.id}`}>
91+
<CardGroup key={`cards_group_start_${project.id}`} items={items} />
92+
</Row>
93+
))}
94+
</>
95+
);
96+
};

src/pages/Dashboard/campaigns-list/table.tsx

Lines changed: 37 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { GroupedTable, Tooltip } from '@appquality/unguess-design-system';
22
import { useTranslation } from 'react-i18next';
3-
import { CampaignWithOutput } from 'src/features/api';
43
import { getStatusInfo } from 'src/common/components/utils/getStatusInfo';
54
import { CampaignStatus } from 'src/types';
65
import { CampaignAnchorTitle } from './CampaignAnchorTitle';
6+
import { useCampaignsGroupedByProject } from './useCampaignsGroupedByProject';
77

8-
export const TableList = ({
9-
campaigns,
10-
}: {
11-
campaigns: Array<Array<CampaignWithOutput>>;
12-
}) => {
8+
export const TableList = () => {
139
const { t } = useTranslation();
1410

11+
const { campaigns, isLoading, isFetching, isError } =
12+
useCampaignsGroupedByProject();
13+
1514
const columns = [
1615
{ name: t('__CAMPAIGNS_TABLE_COLUMN_NAME'), field: 'name' },
1716
{ name: t('__CAMPAIGNS_TABLE_COLUMN_CAMPAIGN_TYPE'), field: 'type' },
@@ -20,55 +19,38 @@ export const TableList = ({
2019
{ name: t('__CAMPAIGNS_TABLE_COLUMN_STATUS'), field: 'status' },
2120
];
2221

23-
// Colonne Nome Campagna, Tipologia, Tipo Test, StartDate, Status
24-
25-
if (!campaigns.length) return null;
26-
27-
// group campaigns by project
28-
const groupesCampaigns = campaigns.reduce(
29-
// acc must be any because the ds component has too strict types without any sense
30-
(acc: any[], curr: CampaignWithOutput[]) => {
31-
const projectName = curr[0].project.name;
32-
const groupExists = acc.find((group) => group.groupName === projectName);
33-
34-
const items = curr.map((campaign) => {
35-
const statusInfo = getStatusInfo(
36-
campaign.status.name as CampaignStatus,
37-
t
38-
);
39-
40-
const cpStartDate = new Date(campaign.start_date).toLocaleDateString();
41-
42-
return {
43-
name: <CampaignAnchorTitle campaign={campaign} />,
44-
type: campaign.family.name,
45-
testType: campaign.type.name,
46-
startDate: cpStartDate,
47-
status: (
48-
<Tooltip
49-
type="light"
50-
placement="auto"
51-
size="medium"
52-
content={statusInfo.text}
53-
>
54-
<span style={{ height: '1em' }}>{statusInfo.icon}</span>
55-
</Tooltip>
56-
),
57-
};
58-
});
59-
60-
if (groupExists) {
61-
groupExists.items = [...groupExists.items, ...items];
62-
} else {
63-
acc.push({
64-
groupName: projectName,
65-
items,
66-
});
67-
}
68-
return acc;
69-
},
70-
[]
71-
);
22+
if (!Object.keys(campaigns).length || isLoading || isFetching || isError)
23+
return null;
24+
25+
const groupesCampaigns = campaigns.map(({ project, items }) => ({
26+
groupName: project.name,
27+
groupIcon: undefined as unknown as 'circle',
28+
items: items.map((campaign) => {
29+
const statusInfo = getStatusInfo(
30+
campaign.status.name as CampaignStatus,
31+
t
32+
);
33+
34+
const cpStartDate = new Date(campaign.start_date).toLocaleDateString();
35+
36+
return {
37+
name: <CampaignAnchorTitle campaign={campaign} />,
38+
type: campaign.family.name,
39+
testType: campaign.type.name,
40+
startDate: cpStartDate,
41+
status: (
42+
<Tooltip
43+
type="light"
44+
placement="auto"
45+
size="medium"
46+
content={statusInfo.text}
47+
>
48+
<span style={{ height: '1em' }}>{statusInfo.icon}</span>
49+
</Tooltip>
50+
),
51+
};
52+
}),
53+
}));
7254

7355
return (
7456
<GroupedTable
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { useAppSelector } from 'src/app/hooks';
2+
import { useGetWorkspacesByWidCampaignsQuery } from 'src/features/api';
3+
import { useActiveWorkspace } from 'src/hooks/useActiveWorkspace';
4+
5+
const useCampaignsGroupedByProject = () => {
6+
const { activeWorkspace } = useActiveWorkspace();
7+
const filters = useAppSelector((state) => state.filters);
8+
9+
const { data, isLoading, isFetching, isError } =
10+
useGetWorkspacesByWidCampaignsQuery({
11+
wid: activeWorkspace?.id.toString() || '',
12+
orderBy: 'start_date',
13+
order: 'DESC',
14+
});
15+
16+
if (!data || isLoading || isFetching || isError) {
17+
return {
18+
campaigns: [],
19+
isLoading,
20+
isFetching,
21+
isError,
22+
count: 0,
23+
};
24+
}
25+
26+
if (!data.items) {
27+
return {
28+
campaigns: [],
29+
isLoading,
30+
isFetching,
31+
isError,
32+
count: 0,
33+
};
34+
}
35+
36+
const filtered = data.items.filter(
37+
({ project, status, family, type, customer_title }) => {
38+
if (filters.projectId && filters.projectId !== project.id) return false;
39+
40+
// Check status
41+
if (
42+
filters.status !== 'all' &&
43+
status.name &&
44+
status.name !== filters.status
45+
)
46+
return false;
47+
48+
// Check Type
49+
if (filters.type !== 'all' && family.name.toLowerCase() !== filters.type)
50+
return false;
51+
52+
// Check Test Type
53+
if (filters.testNameId && type.id !== filters.testNameId) return false;
54+
55+
// Check Search
56+
if (
57+
filters.search &&
58+
customer_title.toLowerCase().indexOf(filters.search.toLowerCase()) ===
59+
-1
60+
)
61+
return false;
62+
63+
// All conditions met
64+
return true;
65+
}
66+
);
67+
68+
const grouped = filtered.reduce(
69+
(
70+
acc: { [key: string]: (typeof filtered)[number][] },
71+
campaign: (typeof filtered)[number]
72+
) => {
73+
if (campaign.project.id in acc) {
74+
acc[campaign.project.id].push(campaign);
75+
} else {
76+
acc[campaign.project.id] = [campaign];
77+
}
78+
return acc;
79+
},
80+
{} as { [key: string]: (typeof filtered)[number][] }
81+
);
82+
83+
const sorted = Object.entries(grouped)
84+
.sort(([, a], [, b]) => {
85+
const maxDateA = Math.max(
86+
...a.map((item) => new Date(item.start_date).getTime())
87+
);
88+
const maxDateB = Math.max(
89+
...b.map((item) => new Date(item.start_date).getTime())
90+
);
91+
return maxDateB - maxDateA;
92+
})
93+
.map(([projectId, items]) => ({
94+
project: {
95+
id: projectId,
96+
name: items[0].project.name,
97+
},
98+
items,
99+
}));
100+
101+
return {
102+
campaigns: sorted,
103+
isLoading,
104+
isFetching,
105+
isError,
106+
count: sorted.flatMap((campaign) => campaign.items).length,
107+
};
108+
};
109+
110+
export { useCampaignsGroupedByProject };

0 commit comments

Comments
 (0)