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

Add top referral table #15

Merged
merged 11 commits into from
Dec 1, 2023
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@
"postinstall": "husky install"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@faker-js/faker": "^8.3.1",
"@mui/icons-material": "^5.14.19",
"@mui/material": "^5.14.19",
"@mui/x-data-grid": "^6.18.2",
"@mui/x-data-grid-generator": "^6.18.2",
"@mui/x-data-grid-premium": "^6.18.2",
"@tanstack/react-query": "^5.9.0",
"@tanstack/react-table": "^8.10.7",
"apexcharts": "^3.44.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
Expand All @@ -28,7 +37,8 @@
"react-grid-layout": "^1.4.4",
"react-icons": "^4.12.0",
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"uuid": "^9.0.1"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.6.1",
Expand Down
6 changes: 6 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ export const getTopReferral = () => {
path: "/event_3.json",
});
};

export const getTopReferralArea = () => {
return request.get<null, Response>({
path: "/event_4.json",
});
};
9 changes: 6 additions & 3 deletions src/components/Dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import UniqueEventCountSum from "./widgets/UniqueEventCountSum";
import TotalEventCountSum from "./widgets/TotalEventCountSum";
import DAU from "./widgets/DAU";
import TopReferralPie from "./widgets/TopReferralPie";
import TopReferralTable from "./widgets/TopReferralTable";
import "./index.css";

const ReactGridLayout = WidthProvider(RGL);
Expand All @@ -27,8 +28,8 @@ const initialLayout = [
{ x: 0, y: 0, w: 6, h: 7, i: "0" },
{ x: 6, y: 0, w: 6, h: 7, i: "1" },
{ x: 0, y: 7, w: 12, h: 8, i: "2" },
{ x: 0, y: 14, w: 6, h: 8, i: "3" },
{ x: 6, y: 14, w: 6, h: 8, i: "4" },
{ x: 0, y: 14, w: 6, h: 10, i: "3" },
{ x: 6, y: 14, w: 6, h: 10, i: "4" },
];

const Dashboard = ({ className, rowHeight, cols }: Props) => {
Expand Down Expand Up @@ -71,7 +72,9 @@ const Dashboard = ({ className, rowHeight, cols }: Props) => {
<div key="3">
<TopReferralPie />
</div>
{/* TODO: TopReferralTable 추가 */}
<div key="4">
<TopReferralTable />
</div>
</ReactGridLayout>
);
};
Expand Down
57 changes: 57 additions & 0 deletions src/components/Dashboard/widgets/TopReferralTable/View.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
DataGridPremium,
GridToolbar,
useGridApiRef,
useKeepGroupedColumnsHidden,
} from "@mui/x-data-grid-premium";

interface Props {
columns: {
id: string;
field: string;
headerName: string;
hide: boolean;
width: number;
aggregable?: boolean;
type?: string;
}[];
rows: Array<{
[key: string]: string | number;
}>;
isLoading: boolean;
}

export default function View({ columns, rows, isLoading }: Props) {
const apiRef = useGridApiRef();

const initialState = useKeepGroupedColumnsHidden({
apiRef,
initialState: {
rowGrouping: {
model: ["country", "region", "city"],
},
sorting: {
sortModel: [{ field: "__row_group_by_columns_group__", sort: "asc" }],
},
aggregation: {
model: {
uniqueEventCount: "sum",
},
},
},
});

return (
<div className="w-full h-full">
<DataGridPremium
rows={rows}
columns={columns}
apiRef={apiRef}
loading={isLoading}
disableRowSelectionOnClick
initialState={initialState}
slots={{ toolbar: GridToolbar }}
/>
</div>
);
}
69 changes: 69 additions & 0 deletions src/components/Dashboard/widgets/TopReferralTable/index.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { Meta, StoryObj } from "@storybook/react";
import Component from "./View";

const meta: Meta<typeof Component> = {
title: "widgets/TopReferralTable",
component: Component,
tags: ["autodocs"],
parameters: {
layout: "fullscreen",
},
};

export default meta;
type Story = StoryObj<typeof Component>;

export const Default: Story = {
args: {
columns: [
{
id: "123",
field: "country",
headerName: "Country",
hide: false,
width: 110,
aggregable: true,
},
{
id: "234",
field: "region",
headerName: "Region",
hide: false,
width: 110,
aggregable: true,
},
{
id: "345",
field: "city",
headerName: "City",
hide: false,
width: 110,
aggregable: true,
},
{
id: "456",
field: "uniqueEventCount",
headerName: "Unique event count",
hide: false,
width: 110,
type: "number",
},
],
rows: [
{
id: "3",
country: "USA",
region: "California",
city: "New York",
uniqueEventCount: 20,
},
{
id: "30",
country: "USA",
region: "California",
city: "New York",
uniqueEventCount: 20,
},
],
},
};
59 changes: 59 additions & 0 deletions src/components/Dashboard/widgets/TopReferralTable/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useTopReferralAreaQuery } from "@/queries";
import { useMemo } from "react";
import { convertArrayToObject } from "@/utils/handleData";
import View from "./View";

const COLUMNS = [
{
id: "123",
field: "country",
headerName: "Country",
hide: false,
width: 110,
aggregable: true,
},
{
id: "234",
field: "region",
headerName: "Region",
hide: false,
width: 110,
aggregable: true,
},
{
id: "345",
field: "city",
headerName: "City",
hide: false,
width: 110,
aggregable: true,
},
{
id: "456",
field: "uniqueEventCount",
headerName: "Unique event count",
hide: false,
width: 110,
type: "number",
},
];

function TopReferralTable() {
const { data: topReferral } = useTopReferralAreaQuery();
const data = useMemo(
() =>
convertArrayToObject(
[
{ name: "country", type: "string" },
{ name: "region", type: "string" },
{ name: "city", type: "string" },
{ name: "uniqueEventCount", type: "number" },
],
topReferral?.data?.rows || []
),
[topReferral]
);
return <View columns={COLUMNS} rows={data} isLoading={false} />;
}

export default TopReferralTable;
9 changes: 8 additions & 1 deletion src/queries/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { getTopReferral, getUsers } from "../api";
import { getTopReferral, getUsers, getTopReferralArea } from "../api";

export const useUsersQuery = () => {
return useQuery({ queryKey: ["userEvents"], queryFn: getUsers });
Expand All @@ -8,3 +8,10 @@ export const useUsersQuery = () => {
export const useTopReferralQuery = () => {
return useQuery({ queryKey: ["topReferral"], queryFn: getTopReferral });
};

export const useTopReferralAreaQuery = () => {
return useQuery({
queryKey: ["topReferralArea"],
queryFn: getTopReferralArea,
});
};
57 changes: 56 additions & 1 deletion src/utils/handleData/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, it, expect } from "vitest";
import {
convertArrayToObject,
formatNumberWithCommas,
generateDateArray,
generateSumArrayOfKey,
Expand All @@ -9,7 +10,8 @@ import {
seperateKeyValuesFromArray,
sumDataOfKey,
} from ".";
import { topReferral, userEvents } from "./mockData";
import { topReferral, topReferralTable, userEvents } from "./mockData";
import { removeKeyFromObjectsInArray } from "../testUtil";

describe("sumDataOfKey 테스트", () => {
it("데이터 중 해당 key의 특정 인덱스 값 총합 리턴", () => {
Expand Down Expand Up @@ -92,3 +94,56 @@ describe("seperateKeyValuesFromArray 테스트", () => {
});
});
});

describe("convertArrayToObject", () => {
it("입력한 Key값으로 배열을 객체로 변환", () => {
const result = convertArrayToObject(
[
{ name: "country", type: "string" },
{ name: "region", type: "string" },
{ name: "city", type: "string" },
{ name: "uniqueEventCount", type: "number" },
],
topReferralTable
);
expect(removeKeyFromObjectsInArray(result, "id")).toEqual([
{
city: "guro-gu",
country: "kr",
region: "seoul",
uniqueEventCount: 119,
},
{
city: "namyangju",
country: "kr",
region: "gyeonggi-do",
uniqueEventCount: 25,
},
{
city: "pocheon-si",
country: "kr",
region: "gyeonggi-do",
uniqueEventCount: 4,
},
]);
});
it("문자열 값 중 빈 값이 있다면 (empty)로 입력", () => {
const result = convertArrayToObject(
[
{ name: "country", type: "string" },
{ name: "region", type: "string" },
{ name: "city", type: "string" },
{ name: "uniqueEventCount", type: "number" },
],
[["kr", "", "pocheon-si", "0"]]
);
expect(removeKeyFromObjectsInArray(result, "id")).toEqual([
{
city: "pocheon-si",
country: "kr",
region: "(empty)",
uniqueEventCount: 0,
},
]);
});
});
22 changes: 22 additions & 0 deletions src/utils/handleData/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dayjs from "dayjs";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
import { v4 as uuid } from "uuid";

dayjs.extend(isSameOrBefore);
dayjs.extend(isSameOrAfter);
Expand Down Expand Up @@ -103,3 +104,24 @@ export const generateDateArray = (startDate: string, endDate: string) => {
export const formatNumberWithCommas = (number: number) => {
return number?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};

export const convertArrayToObject = (
keys: {
name: string;
type: "string" | "number";
}[],
data: string[][]
) => {
return data.map((item) => {
const obj: Record<string, string | number> = {};
keys.forEach((key, index) => {
obj["id"] = uuid();
if (key.type === "string") {
obj[key.name] = item[index].length ? item[index] : "(empty)";
} else {
obj[key.name] = parseInt(item[index], 10);
}
});
return obj;
});
};
6 changes: 6 additions & 0 deletions src/utils/handleData/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ export const topReferral = [
["adpick.co.kr", "1"],
["madup.atlassian.net", "1"],
];

export const topReferralTable = [
["kr", "seoul", "guro-gu", "119"],
["kr", "gyeonggi-do", "namyangju", "25"],
["kr", "gyeonggi-do", "pocheon-si", "4"],
];
Loading