💡 react-icon 설치
- npm install react-icons --save
🌼 Ant Design
💡 ant-design 설치
- npm install antd
- npm i antd styled-components @ant-design/icons : styled-component설치
💡 fontawesome 패키지 설치(icon)
- npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome
💡 Excel파일로 다운로드
📌 참고
<Space size="middle">
은 Ant Design 라이브러리에서 제공하는 컴포넌트로, 내부 요소 사이의 간격을 설정하는 역할
- ( _ )는 일반적으로 사용되지 않는 매개변수를 나타내는 데 사용되는 자리 표시자 변수
- a.localeCompare(b) : 문자열 정렬
- a > b => 음수 (a가 먼저나와)
- a < b => 양수
- a = b => 0
- reduce(누적값 계산)
- 기본형 : array.reduce((acc,cur,index,element) => { return 결과 }, 초기값);
- acc: 누적값, cur: 현재값
- ex.
data.reduce((total, item) => total + item.exposeNum, 0)
📕 ScoreCard
![](https://velog.velcdn.com/images/iui9686/post/5cc285f6-0692-4884-9983-8120ad88111f/image.png)
const ScoreCard = () => {
const data = [
{ id: 1, name: "페이지뷰", value: 32 },
{ id: 2, name: "전체 방문수", value: 27 },
{ id: 3, name: "북마크/직접입력 방문수", value: 17 },
{ id: 4, name: "외부유입 방문 랜딩페이지 통과율", value: "44.44%" },
{ id: 5, name: "전체 반송수", value: 22 },
];
return (
<table className="scorecard">
<tbody>
<tr>
{data.map((item) => (
<td key={item.id} className="cardName">
{item.name}
<br /> <strong className="emphasis">{item.value}</strong>
</td>
))}
</tr>
</tbody>
</table>
);
};
📙 TableBasic
![](https://velog.velcdn.com/images/iui9686/post/65ffc862-eb34-464d-b9f5-a48d17a37e11/image.png)
const Type1 = () => {
const adPercent = [100, -50, 200];
const columns = [
{
title: "광고주",
dataIndex: "name",
align: "center",
},
{
title: "통계",
dataIndex: "stats",
align: "center",
render: (text) => <button className="btn basicBtn">{text}</button>,
},
{
title: "총 광고비",
dataIndex: "totalAd",
render: (text, record, index) => {
const percent = adPercent[index];
const upAnddown = percent > 0 ? "▲" : "▼";
const upAnddownColor = percent > 0 ? "#de481f" : "#4993e4";
return (
<span className="totalAd">
{text} ( {percent}%
<span className="upAnddown" style={{ color: upAnddownColor }}>
{upAnddown}
</span>{" "}
)
</span>
);
},
},
{
title: "총 광고비 (이전기간)",
dataIndex: "tatalAdPrev",
},
{
title: "ROAS(%)",
dataIndex: "roas",
},
{
title: "ROAS(%) (이전기간)",
dataIndex: "roasPrev",
},
];
const data = [
{
key: "1",
name: "쿠팡",
stats: "상세보기",
totalAd: "2,223,526원",
tatalAdPrev: "2,223,526원",
roas: "110%",
roasPrev: "110%",
},
{
key: "2",
name: "쿠팡",
stats: "상세보기",
totalAd: "1,223,526원",
tatalAdPrev: "1,223,526원",
roas: "120%",
roasPrev: "120%",
},
{
key: "3",
name: "쿠팡",
stats: "상세보기",
totalAd: "3,223,526원",
tatalAdPrev: "3,223,526원",
roas: "100%",
roasPrev: "100%",
},
];
const rowClassName = (index) => {
return index % 2 === 0 ? "even-row" : "odd-row";
};
return (
<div className="type1Div">
<Table
columns={columns}
dataSource={data}
pagination={false}
bordered={true}
rowClassName={rowClassName}
/>
</div>
);
};
📒 Table (소팅, 엑셀다운로드, 페이징)
![](https://velog.velcdn.com/images/iui9686/post/75794f11-4057-459e-8b6d-ae6689ad0bcf/image.png)
const Type2 = () => {
const columns = [
{
title: "ID",
dataIndex: "id",
sorter: true,
align: "center",
},
{
title: "오디언스 유형",
align: "center",
dataIndex: "audienceType",
sorter: true,
render: (text) => {
let iconColor = "";
switch (text) {
case "Behavioral":
iconColor = "icon-behavioral";
break;
case "Discovery":
iconColor = "icon-discovery";
break;
case "Union":
iconColor = "icon-union";
break;
default:
break;
}
return (
<button className="btn typeBtn">
<FontAwesomeIcon icon={faCircle} className={`icon ${iconColor}`} />
{" "}
{text}
</button>
);
},
},
{
title: "오디언스명",
dataIndex: "audienceName",
sorter: true,
render: (text) => <a>{text}</a>,
align: "center",
},
{
title: "잠재고객 수 (마지막일자)",
dataIndex: "potentialNum",
sorter: true,
align: "right",
},
{
title: "생성방법",
dataIndex: "method",
sorter: true,
align: "center",
},
{
title: "Status",
dataIndex: "status",
sorter: true,
align: "center",
render: (text) => {
let statusColor = "";
switch (text) {
case "생성중":
statusColor = "status-creating";
break;
case "오류":
statusColor = "status-error";
break;
case "완료":
statusColor = "status-complete";
break;
default:
break;
}
return (
<button className={`btn statusBtn ${statusColor}`}>{text}</button>
);
},
},
{
title: "관리",
dataIndex: "manage",
render: (text) => <button className="btn deleteBtn">{text}</button>,
align: "center",
},
];
const defaultdata = [
{
key: 1,
id: "101",
audienceType: "Discovery",
audienceName: "네이버로 인입하여 구매한 유저 그룹",
potentialNum: "61,000",
method: "자동",
status: "생성중",
manage: "삭제",
},
{
key: 2,
id: "102",
audienceType: "Behavioral",
audienceName: "네이버로 인입하여 구매한 유저 그룹",
potentialNum: "61,100",
method: "수동",
status: "오류",
manage: "삭제",
},
{
key: 3,
id: "103",
audienceType: "Union",
audienceName: "네이버로 인입하여 구매한 유저 그룹",
potentialNum: "60,000",
method: "자동",
status: "완료",
manage: "삭제",
},
{
key: 4,
id: "104",
audienceType: "Behavioral",
audienceName: "네이버로 인입하여 구매한 유저 그룹",
potentialNum: "62,000",
method: "수동",
status: "완료",
manage: "삭제",
},
{
key: 5,
id: "105",
audienceType: "Discovery",
audienceName: "네이버로 인입하여 구매한 유저 그룹",
potentialNum: "60,500",
method: "자동",
status: "생성중",
manage: "삭제",
},
];
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [tableParams, setTableParams] = useState({
pagination: {
current: 1,
pageSize: 3,
},
sorter: {
field: "",
order: "",
},
});
const handleTableChange = (pagination, filters, sorter) => {
setTableParams({
...tableParams,
pagination,
sorter,
});
};
useEffect(() => {
setLoading(true);
const sortedData = [...defaultdata];
const { field, order } = tableParams.sorter;
if (field && order) {
sortedData.sort((a, b) => {
const aValue = a[field];
const bValue = b[field];
if (typeof aValue === "string" && typeof bValue === "string") {
return order === "ascend"
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
} else if (typeof aValue === "number" && typeof bValue === "number") {
return order === "ascend" ? aValue - bValue : bValue - aValue;
}
return 0;
});
}
setData(sortedData);
setLoading(false);
setTableParams((prevParams) => ({
...prevParams,
pagination: {
...prevParams.pagination,
total: sortedData.length,
},
}));
}, [tableParams.sorter]);
const handleAdd = () => {};
const rowClassName = (record, index) => {
return index % 2 === 0 ? "even-row" : "odd-row";
};
const handleDownload = () => {
const workbook = XLSXUtils.book_new();
const worksheet = XLSXUtils.table_to_sheet(
document.getElementById("table")
);
XLSXUtils.book_append_sheet(workbook, worksheet, "Sheet1");
writeFile(workbook, "type2_table.xlsx");
};
return (
<div className="type2Div">
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 16,
}}
>
<Button
onClick={handleAdd}
type="primary"
style={{ marginBottom: 16 }}
className="newBtn"
>
New Union Audience
</Button>
<div>
<span className="searchText">Search:</span>
<input type="text" className="inputBox" />
<Button className="btn excelBtn" onClick={handleDownload}>
Excel
</Button>
</div>
</div>
<Table
id="table"
columns={columns}
dataSource={data}
pagination={tableParams.pagination}
loading={loading}
onChange={handleTableChange}
rowClassName={rowClassName}
/>
</div>
);
};
📌 검색기능
하나의 컬럼에서 검색
const [searchText, setSearchText] = useState("");
const onSearch = (value) => {
setSearchText(value);
const filteredData = defaultdata.filter((item) =>
item.roas.toLowerCase().includes(value.toLowerCase())
);
setData(filteredData);
};
모든 컬럼에서 검색
const onSearch = (value) => {
setSearchText(value);
const filteredData = defaultdata.filter((item) => {
const itemValues = Object.values(item);
return itemValues.some((itemValue) =>
itemValue.toString().toLowerCase().includes(value.toLowerCase())
);
});
setData(filteredData);
};
📗 Table (row펼침, 검색, 체크박스, pagesize변경)
![](https://velog.velcdn.com/images/iui9686/post/343356fb-364f-41a2-bc2f-bad84f82e2fd/image.png)
const Type3 = () => {
const columns = [
Table.SELECTION_COLUMN,
{
title: "키워드",
dataIndex: "keyword",
className: "column-width-fix",
sorter: true,
align: "left",
},
Table.EXPAND_COLUMN,
{
title: "캠페인",
dataIndex: "campaign",
className: "expanded-column",
align: "left",
},
{
title: "노출수",
dataIndex: "exposeNum",
className: "column-width-fix",
sorter: true,
},
{
title: "전환수",
dataIndex: "turnoverNum",
className: "column-width-fix",
sorter: true,
},
{
title: "매출액",
dataIndex: "sales",
className: "column-width-fix",
sorter: true,
},
{
title: "ROAS",
dataIndex: "roas",
className: "column-width-fix",
sorter: true,
},
];
const defaultdata = [];
for (let i = 1; i <= 21; i++) {
defaultdata.push({
key: i,
keyword: `방수천-${i}`,
exposeNum: `${i}2`,
turnoverNum: `${i}1`,
sales: `${i}`,
roas: `${i}10`,
description: "초기값",
});
}
const [grandTotal, setGrandTotal] = useState({
key: "total",
keyword: "총 합계",
exposeNum: 0,
turnoverNum: 0,
sales: 0,
roas: 0,
className: "total-row",
});
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [tableParams, setTableParams] = useState({
pagination: {
current: 1,
pageSize: 10,
},
sorter: {
field: "",
order: "",
},
});
const handleTableChange = (pagination, _, sorter) => {
setTableParams({
...tableParams,
pagination,
sorter,
});
};
useEffect(() => {
setLoading(true);
const sortedData = [...defaultdata];
const { field, order } = tableParams.sorter;
if (field && order) {
sortedData.sort((a, b) => {
const aValue = a[field];
const bValue = b[field];
if (typeof aValue === "string" && typeof bValue === "string") {
return order === "ascend"
? aValue.localeCompare(bValue, undefined, { numeric: true })
: bValue.localeCompare(aValue, undefined, { numeric: true });
} else if (typeof aValue === "number" && typeof bValue === "number") {
return order === "ascend" ? aValue - bValue : bValue - aValue;
}
return 0;
});
}
setTableParams((prevParams) => ({
...prevParams,
pagination: {
...prevParams.pagination,
total: sortedData.length,
},
}));
const pagination = tableParams.pagination;
const startIndex = (pagination.current - 1) * pagination.pageSize;
const endIndex = startIndex + pagination.pageSize;
const slicedData = sortedData.slice(startIndex, endIndex);
const updatedGrandTotal = {
key: "total",
keyword: "총 합계",
exposeNum: slicedData.reduce(
(total, item) => total + Number(item.exposeNum),
0
),
turnoverNum: slicedData.reduce(
(total, item) => total + Number(item.turnoverNum),
0
),
sales: slicedData.reduce((total, item) => total + Number(item.sales), 0),
roas: slicedData.reduce((total, item) => total + Number(item.roas), 0),
className: "total-row",
};
setGrandTotal(updatedGrandTotal);
setData(slicedData);
setLoading(false);
}, [tableParams.sorter, tableParams.pagination.pageSize]);
const { Search } = Input;
const [searchText, setSearchText] = useState("");
const rowClassName = (record) => {
return record.key === "total" ? "total-row" : null;
};
const handleDownload = () => {
const workbook = XLSXUtils.book_new();
const worksheet = XLSXUtils.table_to_sheet(
document.getElementById("table")
);
XLSXUtils.book_append_sheet(workbook, worksheet, "Sheet1");
writeFile(workbook, "type3_table.xlsx");
};
const onSearch = (value) => {
setSearchText(value);
const filteredData = defaultdata.filter((item) => {
const itemValues = Object.values(item);
return itemValues.some((itemValue) =>
itemValue.toString().toLowerCase().includes(value.toLowerCase())
);
});
setTableParams((prevParams) => ({
...prevParams,
pagination: {
...prevParams.pagination,
current: 1,
total: filteredData.length,
},
}));
const pagination = tableParams.pagination;
const startIndex = (pagination.current - 1) * pagination.pageSize;
const endIndex = startIndex + pagination.pageSize;
const slicedData = filteredData.slice(startIndex, endIndex);
const updatedGrandTotal = {
key: "total",
keyword: "총 합계",
exposeNum: slicedData.reduce(
(total, item) => total + Number(item.exposeNum),
0
),
turnoverNum: slicedData.reduce(
(total, item) => total + Number(item.turnoverNum),
0
),
sales: slicedData.reduce((total, item) => total + Number(item.sales), 0),
roas: slicedData.reduce((total, item) => total + Number(item.roas), 0),
className: "total-row",
};
setGrandTotal(updatedGrandTotal);
setData(slicedData);
};
const handlePageSizeChange = (value) => {
const totalItems = defaultdata.length;
const pageSize = value === 40 ? totalItems : value;
setTableParams((prevParams) => ({
...prevParams,
pagination: {
...prevParams.pagination,
current: 1,
pageSize: pageSize,
},
}));
};
const itemLength = () => {
return searchText ? data.length : defaultdata.length;
};
return (
<div className="type3Div">
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 16,
}}
>
<div className="selectText">
<Select
defaultValue={{ value: 10, label: "10개씩 보기" }}
className="selectBox"
options={[
{ value: 10, label: "10개씩 보기" },
{ value: 20, label: "20개씩 보기" },
{ value: 30, label: "30개씩 보기" },
{ value: 40, label: "전체 보기" },
]}
onChange={handlePageSizeChange}
/>
조회된 항목 수 : {itemLength()}
</div>
<div>
<Search
placeholder="검색"
onSearch={onSearch}
className="searchBtn"
/>
<Button className="btn excelBtn" onClick={handleDownload}>
Excel
</Button>
</div>
</div>
<Table
id="table"
pagination={tableParams.pagination}
columns={columns}
dataSource={data}
rowSelection={{}}
expandable={{
expandedRowRender: () => (
<p style={{ margin: 0 }}>
<SubTable />
</p>
),
}}
loading={loading}
onChange={handleTableChange}
rowClassName={rowClassName}
footer={() => (
<tfoot>
<tr className="total-row">
<td className="selection-column"></td>
<td className="column-width-fix">{grandTotal.keyword}</td>
<td className="ant-table-row-expand-icon-cell"></td>
<td className="expanded-column"></td>
<td className="column-width-fix">{grandTotal.exposeNum}</td>
<td className="column-width-fix">{grandTotal.turnoverNum}</td>
<td className="column-width-fix">{grandTotal.sales}</td>
<td className="column-width-fix">{grandTotal.roas}</td>
</tr>
</tfoot>
)}
/>
</div>
);
};
const SubTable = () => {
const subTableColumns = [
{
title: "ID",
dataIndex: "id",
align: "center",
},
{
title: "하나",
dataIndex: "one",
align: "center",
},
{
title: "둘",
dataIndex: "two",
align: "center",
},
{
title: "셋",
dataIndex: "three",
align: "center",
},
];
const subTableData = [];
for (let i = 1; i <= 2; i++) {
subTableData.push({
id: i,
one: 1111111111,
two: 2222222222,
three: 333333333,
});
}
return (
<div>
<Table
columns={subTableColumns}
dataSource={subTableData}
pagination={false}
/>
</div>
);
};
const DataGridComponent = () => {
return (
<div>
<Type3 />
<ScoreCard />
<Type2 />
<Type1 />
</div>
);
};
export default DataGridComponent;
📘 Table (시간별 출력)
- PAD : 좌우에 특정한 문자열로 채우는
- string.padStart(maxLength, 채울문자)
- 채울 문자를 작성하지 않으면 빈 공백으로 채움
- ex.
String(5).padStrt(4, 0);
: 0005 로 출력됨