Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add creatives table and sectors page #23

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"logrocket": "^2.2.1",
"lottie-react": "^2.4.0",
"match-sorter": "^6.3.1",
"omick": "^1.0.0",
"pluralize": "^8.0.0",
"react": "^17",
"react-apexcharts": "^1.4.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CreativeLibraryRepository } from "src/domain/library/creative-library.domain";
import {
CreativeLibraryFilter,
CreativeLibraryFolder,
} from "src/graphql/client";
import { client } from "../clients/graphql.client";

export class CreativeLibraryBackendRepository
implements CreativeLibraryRepository
{
async listFolder(
filter: CreativeLibraryFilter,
): Promise<CreativeLibraryFolder> {
return client.chain.query.folder({ input: filter }).get({
id: true,
creatives: {
name: true,
fileType: true,
createdAt: true,
thumbnailUrl: true,
},
}) as Promise<CreativeLibraryFolder>;
}
}
4 changes: 4 additions & 0 deletions src/app/features/repositories/catalog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { UsersBackendRepository } from "./users.repository";
import { AssetsBackendRepository } from "./assets.repository";
import { BusinessBackendRepository } from "./business.repository";
import { EarlyAccessBackendRepository } from "./early-access.repository";
import { CreativeLibraryBackendRepository } from "src/app/features/repositories/catalog/creative-library.repository";
import { SectorsBackendRepository } from "src/app/features/repositories/catalog/sectors.repository";

export const repository = new Map();

Expand All @@ -11,3 +13,5 @@ repository.set("UsersRepository", UsersBackendRepository);
repository.set("AssetsRepository", AssetsBackendRepository);
repository.set("EarlyAccessRepository", EarlyAccessBackendRepository);
repository.set("BusinessRepository", BusinessBackendRepository);
repository.set("CreativeLibraryRepository", CreativeLibraryBackendRepository);
repository.set("SectorsRepository", SectorsBackendRepository);
21 changes: 21 additions & 0 deletions src/app/features/repositories/catalog/sectors.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SectorsRepository } from "src/domain/sectors/sectors.domain";
import {
sectorsCountsMockData,
sectorsNamesMockData,
} from "src/app/features/repositories/mocks/sectors.mocks";

export class SectorsBackendRepository implements SectorsRepository {
async getSectorsCount(): Promise<{ id: number; count: number }[]> {
// TODO: Remove mocks when getSectorsCount BE service becomes available.
return new Promise((resolve) => {
setTimeout(() => resolve(sectorsCountsMockData), 1000);
});
}

async getSectorsName(): Promise<{ id: number; name: string }[]> {
// TODO: Remove mocks when getSectorsName BE service becomes available.
return new Promise((resolve) => {
setTimeout(() => resolve(sectorsNamesMockData), 1000);
});
}
}
67 changes: 67 additions & 0 deletions src/app/features/repositories/mocks/sectors.mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// TODO: This file contain mock responses for sectors repository. Delete it once sectors BE services are available

export const sectorsNamesMockData = [
{ id: 1, name: "Apparel and Accessories" },
{ id: 2, name: "Beauty and Personal Care" },
{ id: 3, name: "Food and Beverage" },
{ id: 4, name: "Home and Garden" },
{ id: 5, name: "Sports and Fitness" },
{ id: 6, name: "Home Appliances" },
{ id: 7, name: "Home Improvement" },
{ id: 8, name: "Household Supplies" },
{ id: 9, name: "Pet Care" },
{ id: 10, name: "Tobacco and Smoking Accessories" },
{ id: 11, name: "Toys and Games" },
{ id: 12, name: "Oil and Gas" },
{ id: 13, name: "Renewable Energy" },
{ id: 14, name: "Utilities" },
{ id: 15, name: "Banking and Lending" },
{ id: 16, name: "Insurance" },
{ id: 17, name: "Investment and Wealth Management" },
{ id: 18, name: "Pharmaceuticals and Biotechnology" },
{ id: 19, name: "Medical Devices" },
{ id: 20, name: "Healthcare Services" },
{ id: 21, name: "Construction and Engineering" },
{ id: 22, name: "Aerospace and Defense" },
{ id: 23, name: "Transportation Equipment" },
{ id: 24, name: "Software and IT Services" },
{ id: 25, name: "Hardware and Electronics" },
{ id: 26, name: "Internet Services" },
{ id: 27, name: "Telecommunications Equipment" },
{ id: 28, name: "Telecommunications Services" },
{ id: 29, name: "Networking Equipment" },
{ id: 30, name: "Airlines and air transportation" },
];

export const sectorsCountsMockData = [
{ id: 1, count: 457 },
{ id: 2, count: 512 },
{ id: 3, count: 79 },
{ id: 4, count: 687 },
{ id: 5, count: 234 },
{ id: 6, count: 820 },
{ id: 7, count: 112 },
{ id: 8, count: 400 },
{ id: 9, count: 517 },
{ id: 10, count: 300 },
{ id: 11, count: 700 },
{ id: 12, count: 120 },
{ id: 13, count: 350 },
{ id: 14, count: 600 },
{ id: 15, count: 450 },
{ id: 16, count: 250 },
{ id: 17, count: 500 },
{ id: 18, count: 100 },
{ id: 19, count: 550 },
{ id: 20, count: 200 },
{ id: 21, count: 650 },
{ id: 22, count: 150 },
{ id: 23, count: 700 },
{ id: 24, count: 50 },
{ id: 25, count: 750 },
{ id: 26, count: 400 },
{ id: 27, count: 800 },
{ id: 28, count: 350 },
{ id: 29, count: 750 },
{ id: 30, count: 300 },
];
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { lazy } from "react";
import { Route } from "src/app/features/navigation/models/route.model";
import Sectors from "src/app/pages/creative-intelligence-suite/pages/business-settings/pages/sectors/sectors.page";

const AccountAndBrands = lazy(
() =>
Expand All @@ -14,4 +15,9 @@ export const BusinessSettingsRoutes: Route[] = [
element: AccountAndBrands,
title: "Account & Brands",
},
{
path: "/business-settings/sectors",
element: Sectors,
title: "Sectors",
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Col, Row } from "antd";
import { FC } from "react";

export interface SectorsGridWidgetProps {
data?: { id: number; name: string; count: number }[];
searchText?: string;
loading?: boolean;
}

const SectorCard: React.FC<{ name: string; count: number }> = (props) => (
<div
className="flex flex-col items-center justify-center"
style={{
backgroundColor: "rgba(0, 0, 0, 0.3",
width: "150px",
height: "150px",
}}
>
<div style={{ textAlign: "center", padding: "10px" }}>
<b>{props.name}</b>
</div>
<div>{props.count}</div>
</div>
);

export const SectorsGridWidget: FC<SectorsGridWidgetProps> = ({ data }) => {
return (
<Row gutter={[16, 16]} justify="start" align="middle">
{data?.map((s) => (
<Col key={s.id}>
<SectorCard name={s.name} count={s.count} />
</Col>
))}
</Row>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { FC, useMemo } from "react";
import CardPageUI from "src/app/ui/cards/card-page.ui";
import { Descriptions } from "antd";
import { SectorsGridWidget } from "src/app/pages/creative-intelligence-suite/pages/business-settings/pages/sectors/sectors.grid.widget";
import useSWR from "swr";
import { useSectorsDomain } from "src/domain/sectors/sectors.domain";
import { EmptyLoaderUI } from "src/app/ui/empty/empty-loader.ui";
import { EmptyCreateUI } from "src/app/ui/empty/empty-create.ui";

const SectorsPage: FC = () => {
const { getSectorsName, getSectorsCount } = useSectorsDomain();
const { data: sectorsCount, isLoading: isLoadingSectorsCount } = useSWR(
"getSectorsCount",
getSectorsCount,
);
const { data: sectorsName, isLoading: isLoadingSectorsName } = useSWR(
"getSectorsName",
getSectorsName,
);

const data = useMemo(() => {
if (sectorsCount && sectorsName) {
const sectorCountsByKey = sectorsCount.reduce((accum, sc) => {
accum[sc.id] = sc.count;
return accum;
}, {});
return sectorsName.map((sc) => ({
id: sc.id,
name: sc.name,
count: sectorCountsByKey[sc.id],
}));
}
}, [sectorsCount, sectorsName]);

return (
<CardPageUI>
<div>
<header className="mb-4 flex gap-2">
<div className="h-4" />
<Descriptions title="Sectors"></Descriptions>
<div className="flex-1" />
</header>
<EmptyLoaderUI
isLoading={isLoadingSectorsCount || isLoadingSectorsName}
/>
{!(isLoadingSectorsCount || isLoadingSectorsName) && (
<div>
{data ? (
<SectorsGridWidget data={data} />
) : (
<EmptyCreateUI description="You don't have any sectors yet." />
)}
</div>
)}
</div>
</CardPageUI>
);
};
export default SectorsPage;
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import { FC } from "react";
import { FC, useState } from "react";
import CardPageUI from "src/app/ui/cards/card-page.ui";
import { SearchInputUI } from "src/app/ui/inputs/search-input.ui";
import { useSessionFeature } from "src/app/features/session/session.feature";
import { useCreativeLibraryDomain } from "src/domain/library/creative-library.domain";
import useSWR from "swr";
import { Descriptions } from "antd";
import { CreativeLibraryTableWidget } from "src/app/pages/creative-intelligence-suite/pages/creative-lab/pages/creative-library/creative-library.table.widget";

const CreativeLibraryPage: FC = () => {
const { currentBrand } = useSessionFeature();
const { listFolder } = useCreativeLibraryDomain();
const [searchText, setSearchText] = useState<string>();

const { data, isLoading } = useSWR(
{ brandId: currentBrand?.id, creativeType: "Creative" },
listFolder,
);

const onSearchInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(e?.target?.value);
};

return (
<CardPageUI>
<header
Expand All @@ -15,9 +33,20 @@ const CreativeLibraryPage: FC = () => {
padding: "13px 0",
}}
>
<SearchInputUI />
<SearchInputUI onChange={onSearchInputChange} />
</header>
<pre>Insert Table here</pre>
<div>
<header className="mb-4 flex gap-2">
<div className="h-4" />
<Descriptions title="Creatives"></Descriptions>
<div className="flex-1" />
</header>
<CreativeLibraryTableWidget
data={data?.creatives}
searchText={searchText}
loading={isLoading}
/>
</div>
</CardPageUI>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Avatar } from "antd";
import { ColumnsType } from "antd/es/table";
import { FC } from "react";
import { TableUI } from "src/app/ui/tables/table.ui";
import { Creative } from "src/graphql/client";
import { titleCaseFormatterTool } from "src/app/tools/formatters/title-case.formatter.tool";
import { dateFormatterTool } from "src/app/tools/formatters/date.formatter.tool";

export interface CreativeLibraryTableWidgetProps {
data?: any[];
searchText?: string;
loading?: boolean;
}

export const CreativeLibraryTableWidget: FC<
CreativeLibraryTableWidgetProps
> = ({ data = [], loading, searchText = "" }) => {
const generateCreativeLibraryKey = (creative: Creative) => ({
...creative,
key: creative.name,
});

const columns: ColumnsType<Creative> = [
{
title: "Name",
dataIndex: "name",
filteredValue: [searchText],
onFilter: (value, record) => {
const lowerCaseSearchText = searchText.toLocaleLowerCase();
return (
record.name.toLocaleLowerCase().includes(lowerCaseSearchText) ||
dateFormatterTool(
new Date(record.createdAt),
navigator.language,
).includes(lowerCaseSearchText) ||
titleCaseFormatterTool(record.fileType)
.toLocaleLowerCase()
.includes(lowerCaseSearchText)
);
},
render: (name, record) => {
return (
<div style={{ display: "flex", gap: "8px" }}>
<Avatar
shape="square"
src={record.thumbnailUrl}
style={{
backgroundColor: "rgb(230 244 255)",
color: "#1677ff",
fontWeight: "bold",
}}
>
{record.thumbnailUrl ? "" : name[0]}
</Avatar>
<div className="flex items-center">
<div>{name}</div>
</div>
</div>
);
},
},
{
title: "Uploaded Date",
dataIndex: "createdAt",
render: (createdAt) => {
return (
<div className="flex items-center">
<div>
{dateFormatterTool(new Date(createdAt), navigator.language)}
</div>
</div>
);
},
},
{
title: "File Type",
dataIndex: "fileType",
render: (fileType) => {
return (
<div className="flex items-center">
<div>{titleCaseFormatterTool(fileType)}</div>
</div>
);
},
},
];

return (
<TableUI
columns={columns}
data={data.map(generateCreativeLibraryKey)}
isLoading={loading}
/>
);
};
Loading