Skip to content

SoyaNyan/medi-table-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

24 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Medical Table App

This project was bootstrapped with Create React App.
์ด ํ”„๋กœ์ ํŠธ๋Š” CRA(Create React App)์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค.

Available Scripts

npm start

ํ”„๋กœ์ ํŠธ๋ฅผ ๊ฐœ๋ฐœํ™˜๊ฒฝ์œผ๋กœ ๋กœ์ปฌ์—์„œ ํ˜ธ์ŠคํŒ…ํ•˜๋ ค๋ฉด ์œ„ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•ด์ฃผ์„ธ์š”.

npm run build

ํ”„๋กœ์ ํŠธ๋ฅผ build ํ•˜๋ ค๋ฉด ์œ„ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•ด์ฃผ์„ธ์š”.

npm run start:prod

build๋œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋กœ์ปฌ์—์„œ ํ˜ธ์ŠคํŒ…ํ•˜๋ ค๋ฉด ์œ„ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•ด์ฃผ์„ธ์š”.
Production build๋ฅผ ํ˜ธ์ŠคํŒ…ํ•˜๋ ค๋ฉด ๋จผ์ € npm install -g serve ๋ฅผ ํ†ตํ•ด serve ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ด์ฃผ์„ธ์š”.

Project Structure

ํ”„๋กœ์ ํŠธ์˜ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

root
|   .env
|   .gitignore
|   output.txt
|   package-lock.json
|   package.json
|   README.md
|   tree.txt
+---build
|   |
|   \ ...
+---node_modules
|   |
|   \ ...
+---public
|       favicon.ico
|       index.html
|       logo192.png
|       logo512.png
|       manifest.json
|       robots.txt
|
\---src
    |   App.css
    |   App.jsx
    |   App.test.js
    |   custom.css
    |   index.css
    |   index.js
    |   logo.svg
    |   reportWebVitals.js
    |   setupTests.js
    |
    +---api
    |       mediData.js
    |
    \---component
            ChartContainer.jsx
            DetailCellRenderer.jsx
            PatientTable.jsx

ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” API ์„œ๋ฒ„์˜ ์ฃผ์†Œ๋ฅผ .env ํŒŒ์ผ์— ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์œ„ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ์™€ ๋™์ผํ•œ ์œ„์น˜์— .env ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‚ด๋ถ€์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.

# CRA requires REACT_APP_ prefix
REACT_APP_API_BASE_ADDR=[API_ADRESS_HERE]

CRA๋กœ ์ƒ์„ฑ๋œ ํ”„๋กœ์ ํŠธ์—๋Š” dotenv ํŒจํ‚ค์ง€๊ฐ€ ์ด๋ฏธ ์„ค์น˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, .env ํŒŒ์ผ ๋‚ด๋ถ€์— REACT_APP_ ์ ‘๋ฏธ์‚ฌ๊ฐ€ ๋ถ™์€ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋งŒ์„ ์ •์ƒ์ ์œผ๋กœ ๋ถˆ๋Ÿฌ ์˜ฌ ์ˆ˜ ์žˆ์œผ๋‹ˆ process.env ์—์„œ ์›ํ•˜๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ ์ด ๊ทœ์น™์„ ์ง€์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Dependencies (libraries)

ํ”„๋กœ์ ํŠธ ์ง„ํ–‰์— ์•ž์„œ ์–ด๋–ค ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ ํƒํ•˜์—ฌ ๊ตฌํ˜„์„ ํ•  ์ง€ ๊ณ ๋ฏผํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด์— PHP ํ’€์Šคํƒ ํ”„๋กœ์ ํŠธ์—์„œ ํ”„๋ก ํŠธ์—”๋“œ๋ฅผ ๋งŒ๋“ค ๋•Œ ์ž์ฃผ ์“ฐ๋˜ jQuery, ๊ฐ€์žฅ ์ตœ๊ทผ์— ์ƒˆ๋กญ๊ฒŒ ๊ณต๋ถ€ํ–ˆ๋˜ Svelte, ๊ทธ๋ฆฌ๊ณ  ํšŒ์‚ฌ์—์„œ ์ผํ•˜๋ฉฐ ์จ๋ณด์•˜๋˜ React, ๋ชจ๋‘ ๋‚˜๋ฆ„์˜ ์žฅ๋‹จ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ ํ”„๋กœ์ ํŠธ์— ๊ฐ€์žฅ ์ ํ•ฉํ•˜๋‹ค๊ณ  ํŒ๋‹จ๋˜๋Š” ๊ฒƒ์„ ๊ณจ๋ผ์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

jQuery๋Š” ์ˆœ์ˆ˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ DOM์„ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์„ ํŒŒ์‚ฌ๋“œ ํŒจํ„ด์œผ๋กœ ๋‹จ์ถ•์‹œ์ผœ๋†“์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž…๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ์•„๋ฌด๋Ÿฐ ํ”„๋ ˆ์ž„์›Œํฌ๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—†์ด ๊ตฌํ˜„์„ ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์ฝ”๋“œ์˜ ์–‘์„ ๋‹ค์†Œ ์ค„์—ฌ์ฃผ๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ๋ชจ๋“ˆํ™”์˜ ์–ด๋ ค์›€๊ณผ ์ˆ˜ ๋งŽ์€ DOM์ด๋ฒคํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ์— ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— jQuery์˜ ์‚ฌ์šฉ์€ ํฌ๊ธฐํ–ˆ์Šต๋‹ˆ๋‹ค.

Svelte๋Š” ์ˆœ์ˆ˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ™•์žฅ์‹œ์ผœ์„œ ๋งค์šฐ ๋‹จ์ˆœํ•œ ๋ฌธ๋ฒ•์œผ๋กœ ๋ชจ๋˜ ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋ ˆ์ž„์›Œํฌ๋“ค์ด ์ œ๊ณตํ•˜๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ๋“ค์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. Context API, Redux์™€ ๊ฐ™์€ ๊ธฐ์ˆ ์ด ์—†์–ด๋„ ์ปดํฌ๋„ŒํŠธ์˜ State๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ๋“ค์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์œผ๋ฉฐ ๋ฌด์—‡๋ณด๋‹ค๋„ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ์ ˆ๋Œ€์ ์œผ๋กœ ์ ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐœ๋ฐœ ์†๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ฐจํŠธ, ํ•„ํ„ฐ๋ง๊ณผ ์ •๋ ฌ์ด ๊ฐ€๋Šฅํ•œ ํ…Œ์ด๋ธ” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Svelte๋กœ ๋งŒ๋“ค์–ด์ง„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์—†๋Š” ํ˜„์žฌ ์‹œ์ ์—์„œ๋Š” ๋ถ€์ ์ ˆ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ React์—์„œ ์Šคํƒ€์ผ๋ง์„ ์œ„ํ•œ Bootstrap 5.X์˜ ๋ž˜ํผ react-bootstrap๊ณผ ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”, ์ฐจํŠธ ๋“ฑ์˜ ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ชจ๋‘ ์ฐพ์„ ์ˆ˜ ์žˆ์—ˆ๊ณ  ํ”„๋กœ์ ํŠธ์˜ ๊ฐ€์žฅ ํฐ ๋ผˆ๋Œ€๋Š” React๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์— CRA์—์„œ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ ์ด์™ธ์— ์‚ฌ์šฉ๋œ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

"dependencies": {
    "ag-grid-community": "^27.1.0", // ag-grid community license
    "ag-grid-enterprise": "^27.1.0", // ag-grid enterprise license(trial)
    "ag-grid-react": "^27.1.0", // react ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
    "apexcharts": "^3.33.2", // ๋‹ค์–‘ํ•œ ์ฐจํŠธ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ react ๋ฒ„์ „
    "axios": "^0.26.1", // rest api ํ˜ธ์ถœ์„ ์œ„ํ•œ http ํด๋ผ์ด์–ธํŠธ
    "bootstrap": "^5.1.3", // bootstrap์˜ react ๋ž˜ํผ
}

Prob & Solution (Implementation)

์ด ํ”„๋กœ์ ํŠธ๋Š” ๋‹ค์Œ์˜ 5ํ•ญ๋ชฉ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ณผ์ œ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ํ•ญ๋ชฉ์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์ œํ•œ๋œ ์‹œ๊ฐ„ ๋‚ด์— ์ตœ๋Œ€ํ•œ ๋งŽ์€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์‹ค์ œ ๋Œ€์‹œ๋ณด๋“œ ํ˜•ํƒœ์˜ ์›น์•ฑ๋“ค์ด ๊ฐ€์ง€๋Š” UI์™€ ๋น„์Šทํ•œ ํ˜•ํƒœ๋กœ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•˜๊ณ  ํ•œ ํ™”๋ฉด์— ๋‹ด๊ธด ๋งŽ์€ ์ •๋ณด๋ฅผ ํŽธ์•ˆํ•˜๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

Q0. ์ค€๋น„๋‹จ๊ณ„

๋ณธ๊ฒฉ์ ์œผ๋กœ ๊ตฌํ˜„์„ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ๊ฐ€์žฅ ํ•ต์‹ฌ์ด ๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ AG Grid ์™€ ApexCharts ์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์‚ดํŽด ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ApexCharts ๋Š” ์ด์ „์— ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์—์„œ ์ˆœ์ˆ˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฒ„์ „์œผ๋กœ ์‚ฌ์šฉํ•  ์ ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— React ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•  ๋•Œ ์–ด๋–ค Component์™€ Prop์„ ์ œ๊ณตํ•˜๋Š”์ง€๋ฅผ ์ฐพ์•„๋ดค์Šต๋‹ˆ๋‹ค.

AG Grid ์˜ ๊ฒฝ์šฐ ๊ณต์‹ ๋ฌธ์„œ์ƒ์— Enterprise ๋ผ์ด์„ผ์Šค์—๋งŒ ์ ์šฉ ๊ฐ€๋Šฅํ•œ ๊ธฐ๋Šฅ์˜ ๋ชฉ๋ก์ด ๋ณ„๋„ ํ‘œ๊ธฐ๋˜์–ด ์žˆ๋Š”๋ฐ ๊ณต์‹ ๋ฌธ์„œ์— ๋‚˜์™€์žˆ๋Š” ์ฝ”๋“œ ์Šค๋‹ˆํŽซ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ด๋„ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ๋ถ€๋ถ„์ด ์žˆ์–ด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ์„ ๋งŒ๋“ค์–ด ์‹ค์ œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ Prop๊ณผ Option์„ ์ฐพ๋Š”๋ฐ ์ƒ๋‹นํ•œ ์‹œ๊ฐ„์„ ์†Œ๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ™”๋ฉด ๋‚ด๋ถ€์˜ ๊ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์ „, react-bootstrap ์„ ํ™œ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”์™€ ๋ณธ๋ฌธ ์˜์—ญ์„ ๋งŒ๋“ค๊ณ  ๋†’์€ ํ•ด์ƒ๋„์—์„œ๋„ ์ •๋ณด๋ฅผ ์ง‘์ค‘ํ•ด์„œ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก Container ์ปดํฌ๋„ŒํŠธ๋กœ ๋ณธ๋ฌธ ์˜์—ญ์„ ์ขํ˜€ ๊ทธ ์•ˆ์— ํ…Œ์ด๋ธ”๊ณผ ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋„ฃ๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ด๋ธ” ์ปดํฌ๋„ŒํŠธ ์œ„ ์ชฝ์— ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์œ„์น˜ํ•ด์•ผ ํ•˜๊ณ  ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ๋Š” 5๊ฐœ์˜ ์ฐจํŠธ๋กœ ๊ตฌ์„ฑ๋œ ์ปจํ…Œ์ด๋„ˆ์— ์ ์ ˆํžˆ ๋ฐฐ์น˜ํ•ด์•ผ ํ•˜๋ฉฐ ํ•˜๋‹จ์— ํ…Œ์ด๋ธ” ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•„์š”ํ•œ ์ปจํŠธ๋กค ์š”์†Œ๋“ค์„ ๊ณ ๋ คํ•˜์—ฌ ์˜์—ญ์„ Row , Col ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ๋ถ„ํ–ˆ์Šต๋‹ˆ๋‹ค.

Swagger UI๋กœ API์˜ ๋ช…์„ธ๊ฐ€ ์ œ๊ณต๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ Endpoint ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ, ๋ฐ˜ํ™˜ ๋ชจ๋ธ์„ ํ™•์ธํ•˜๊ณ  Axios ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

Q1. ํ™˜์ž ์ •๋ณด๋ฅผ ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š” ํ…Œ์ด๋ธ” Component๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. (Solved)

์ฒซ๋ฒˆ ์งธ๋กœ ๊ตฌํ˜„ํ•ด์•ผ ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” API์—์„œ ์ œ๊ณต๋˜๋Š” ํ™˜์ž๋“ค์˜ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ด๋ธ” ํ˜•ํƒœ๋กœ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ” ์ปดํฌ๋„ŒํŠธ ์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ ํ…Œ์ด๋ธ”์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋ถ€ํ„ฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. API๋ฅผ ์š”์ฒญํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ๋Š” ์ด๋ฏธ ๋งŒ๋“ค์–ด ๋‘์—ˆ๊ธฐ ๋•Œ๋ฌธ์— AG Grid ์— ์„œ๋ฒ„์‚ฌ์ด๋“œ ํ”„๋กœ์„ธ์‹ฑ ์˜ต์…˜์„ ์ฃผ๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์—ฐ๋™ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ํ…Œ์ด๋ธ”์— ํ‘œ๊ธฐ๋˜์–ด์•ผ ํ•  ์ •๋ณด๋Š” ํ™˜์ž id, ์„ฑ๋ณ„, ์ƒ๋…„์›”์ผ, ๋‚˜์ด, ์ธ์ข…, ๋ฏผ์กฑ, ์‚ฌ๋ง ์—ฌ๋ถ€ ์ด 7๊ฐœ์˜ ์ปฌ๋Ÿผ์œผ๋กœ AG Grid ์˜ columnDefs ์˜ต์…˜์— ์ถœ๋ ฅํ•  ๋ฐ์ดํ„ฐ ํ•„๋“œ๋ฅผ ๋งคํ•‘ํ•˜๊ณ  ํ•œ๊ธ€๋กœ ํ—ค๋”๊ฐ€ ์ถœ๋ ฅ๋  ์ˆ˜ ์žˆ๋„๋ก ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์„ฑ๋ณ„, ์ƒ๋…„์›”์ผ, ์‚ฌ๋ง ์—ฌ๋ถ€ ์ปฌ๋Ÿผ์˜ ๊ฒฝ์šฐ API ์š”์ฒญ์œผ๋กœ ํš๋“ํ•œ ์ •๋ณด๋ฅผ ๊ฐ€๊ณตํ•ด์„œ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ๋ฐ–์— ์ด ํ›„ ๋ฌธ์ œ์—์„œ ๊ตฌํ˜„ํ•ด์•ผ ๋˜๋Š” pagination , sorting , filtering ๊ธฐ๋Šฅ์„ ์œ„ํ•ด ๊ด€๋ จ ์˜ต์…˜์„ ์ฐพ์•„ ๋ถ€์—ฌํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ…Œ์ด๋ธ”์— SSRM(Server-side Row Model) ์˜ต์…˜์„ ์ ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์—์„œ ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•œ ์˜ต์…˜๋“ค์ด ๋งŽ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ค‘์—์„œ๋„ rowModelType: "serverSide" ์˜ต์…˜์€ Enterprise License ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ pagination: true ์˜ต์…˜๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ํŽ˜์ด์ง•์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํŒจํ‚ค์ง€ ๋ฒ„์ „ ^25.0.1 ์ดํ›„ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์œผ๋กœ serverSideStoreType: "partial" ์˜ต์…˜์œผ๋กœ store ๋ฐฉ์‹์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ๊ธฐ๋ณธ ๊ฐ’์ธ full ๋กœ ์—ฐ๋™๋  ๊ฒฝ์šฐ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•˜์—ฌ ํ˜„์žฌ ํŽ˜์ด์ง€๋ฅผ ์•Œ๋ ค์ฃผ๋Š” endRow ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

// ag-grid options
const gridOptions = useMemo(
    () => ({
        masterDetail: true,
        pagination: true,
        rowModelType: "serverSide", // only available on Enterprise License(include trial)
        serverSideStoreType: "partial", // !important! required for SSRM pagination, sorting, filtering
        domLayout: "autoHeight",
        detailRowHeight: 400,
        )
    }),
    [setFilterModel]
)

๊ณต์‹ Github์— ๊ด€๋ จ ์ด์Šˆ๊ฐ€ ์•ˆ๋‚ด๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. => GitHub Issue

ํŽ˜์ด์ง•๊ณผ ์ •๋ ฌ์„ ์œ„ํ•ด์„œ AG Grid ์˜ columnDefs ์—์„œ ์ •๋ ฌ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•  ์ปฌ๋Ÿผ์„ ์ง€์ •ํ•˜๊ณ , SSRM ๋ฐ์ดํ„ฐ๋ฅผ ์ค€๋น„ํ•˜๋Š” useEffect() ๋‚ด์—์„œ params ๋กœ ์ „๋‹ฌ๋˜๋Š” prop์˜ endRow , sortModel ๊ฐ’์„ ์ฝ์–ด ํ˜„์žฌ ํŽ˜์ด์ง€, ์ „์ฒด ๋ ˆ์ฝ”๋“œ์˜ ์ˆ˜, ๊ทธ๋ฆฌ๊ณ  ํ…Œ์ด๋ธ”์—์„œ ์–ด๋–ค ์ปฌ๋Ÿผ์˜ ์ •๋ ฌ์„ ํ•˜๋ ค๊ณ  ํ•˜๋Š”์ง€๋ฅผ ์•Œ์•„๋‚ด๊ณ  ์ด๋ฅผ API๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ํ•„์š”ํ•œ parameter๋กœ ๋งคํ•‘ํ•ฉ๋‹ˆ๋‹ค.

[ ์ •๋ ฌ์ด ๊ฐ€๋Šฅํ•œ ์ปฌ๋Ÿผ(API ๋ช…์„ธ๋ฅผ ๋”ฐ๋ผ) ]

  • ํ™˜์ž id (personID)
  • ์„ฑ๋ณ„ (gender)
  • ์ƒ๋…„์›”์ผ (birthDatetime)
  • ์ธ์ข… (race)
  • ๋ฏผ์กฑ (ethnicity)
  • ์‚ฌ๋ง ์—ฌ๋ถ€ (isDeath)
// pagination
const page = endRow / pageSize

// sorting
const sortOptions = {
	orderColumn: null,
	orderDesc: false,
}
if (sortModel.length) {
	const { colId, sort } = sortModel[0]
	const columnInfo = {
		personID: "person_id",
		gender: "gender",
		birthDatetime: "birth",
		race: "race",
		ethnicity: "ethnicity",
		isDeath: "death",
	}
	sortOptions.orderColumn = columnInfo[colId]
	sortOptions.orderDesc = sort === "desc"
}

์ถ”๊ฐ€ ๊ธฐ๋Šฅ์œผ๋กœ ํ•œ ํŽ˜์ด์ง€ ๋‹น ๋ณด์—ฌ์ง€๋Š” Row์˜ ์ˆ˜๋ฅผ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ์ปจํŠธ๋กค์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ปจํŠธ๋กค์˜ ํ˜•ํƒœ๋Š” react-ootstrap์˜ Select ๋ฅผ ์‚ฌ์šฉํ–ˆ๊ณ  ํ•ด๋‹น ์ปจํŠธ๋กค์˜ ๊ฐ’์„ ์ฝ์–ด ๋กœ์ปฌ state์— ์ €์žฅํ•œ ๋’ค, AG Grid ์˜ API๋ฅผ ํ˜ธ์ถœํ•ด ๋ Œ๋”๋งํ•  ํŽ˜์ด์ง€ ์‚ฌ์ด์ฆˆ๋ฅผ ๋ณ€๊ฒฝํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

Q2. Q1์—์„œ ์ž‘์„ฑํ•œ ํ…Œ์ด๋ธ”์˜ ํ•„ํ„ฐ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. (Solved)

์ด๋ฒˆ ๊ตฌํ˜„์—์„œ๋Š” ๋‹ค์Œ์˜ ์ปฌ๋Ÿผ์—์„œ ํ•„ํ„ฐ๋ง์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ…Œ์ด๋ธ”์„ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ์„ฑ๋ณ„(gender)
  • ๋‚˜์ด(age)
  • ์ธ์ข…(race)
  • ๋ฏผ์กฑ(ethnicity)
  • ์‚ฌ๋ง ์—ฌ๋ถ€(death)

์ด๋ฅผ ์œ„ํ•ด์„œ AG Grid ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ•„ํ„ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™œ์„ฑํ™” ํ•˜๊ธฐ ์œ„ํ•ด columnDefs ์˜ต์…˜์— filter: [filterClass] ํ”„๋กœํผํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‚˜์ด ์ปฌ๋Ÿผ์„ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ์ปฌ๋Ÿผ์€ ๋ชจ๋‘ ์ฒดํฌ๋ฐ•์Šค๋กœ ๋ฏธ๋ฆฌ ์ง€์ •๋œ ํ•ญ๋ชฉ์„ ์„ ํƒ ๊ฐ€๋Šฅํ•œ agSetColumnFilter ์„ ์‚ฌ์šฉํ•˜๊ณ  ํ•„ํ„ฐ๋ง ์˜ต์…˜์— ๋“ค์–ด๊ฐˆ ํ•ญ๋ชฉ์€ ๊ฐ๊ฐ์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” API์— ์š”์ฒญํ•˜์—ฌ ๋™์ ์œผ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜์ด๋Š” ํ™˜์ž์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ํš๋“ํ•  ์ˆ˜ ์žˆ๋Š” API์—์„œ minAge , maxAge ๋ฅผ ์ œ๊ณตํ•˜๋ฏ€๋กœ ์ˆซ์ž ๋ฒ”์œ„๋ฅผ ํ•„ํ„ฐ๋ง ํ•  ์ˆ˜ ์žˆ๋Š” agNumberColumnFilter ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ฐ ์ปฌ๋Ÿผ์— ์ •์˜๋œ ํ•„ํ„ฐ๋ฅผ ์‹ค์ œ ๋ฐ์ดํ„ฐ์— ๋ฐ˜์—ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ SSRM ๋ฐ์ดํ„ฐ๋ฅผ ์ค€๋น„ํ•˜๋Š” useEffect() ๋‚ด์—์„œ params ๋กœ ์ „๋‹ฌ๋˜๋Š” prop์˜ filterModel ์„ ์ฝ์–ด ์‚ฌ์šฉ์ž๊ฐ€ ์–ด๋–ค ์ปฌ๋Ÿผ์—์„œ ์–ด๋–ค ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ํ•„ํ„ฐ๋ง์„ ํ•˜๊ณ ์ž ํ•˜๋Š” ์ง€๋ฅผ ์•Œ์•„๋‚ด๊ณ  API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์—์„œ ํ•„ํ„ฐ ๊ฐ’๊ณผ ํ•„ํ„ฐ๋งํ•  ์ปฌ๋Ÿผ์˜ ID๋ฅผ ๋งคํ•‘ํ•ด ์ค๋‹ˆ๋‹ค.

// call api
getPatientList({
	page,
	length: pageSize,
	orderColumn: sortOptions.orderColumn,
	orderDesc: sortOptions.orderDesc,
	gender: filterOptions.gender,
	race: filterOptions.race,
	ethnicity: filterOptions.ethnicity,
	ageMin: filterOptions.ageMin,
	ageMax: filterOptions.ageMax,
	death: filterOptions.death,
})
	.then((data) => {
		params.success({
			rowData: data.patient.list,
			rowCount: data.patient.totalLength,
		})
	})
	.catch((err) => {
		console.log(err)
		params.fail()
	})

์ด ๊ณผ์ •์—์„œ API์˜ parameter ๊ฐ€ ์‚ฌ์ „์— ์ •ํ•ด์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, AG Grid ์˜ ํ•„ํ„ฐ ๊ธฐ๋Šฅ์€ ํ•œ ์ปฌ๋Ÿผ์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฐ’์„ ๋™์‹œ์— And ๋‚˜ Or ์—ฐ์‚ฐ์œผ๋กœ ๋ณตํ•ฉ ์กฐ๊ฑด ๊ฒ€์ƒ‰์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ API ์—”๋“œํฌ์ธํŠธ์—์„œ ์ง€์›๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ•œ ๋ฒˆ์— ๋‘ ๊ฐœ ์ด์ƒ์˜ ํ•„ํ„ฐ๋ฅผ agSetColumnFilter ์—์„œ ์„ ํƒํ•˜๊ฑฐ๋‚˜ minAge , maxAge ๊ฐ’์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์กฐ๊ฑด์ด ์•„๋‹Œ ํ•ญ๋ชฉ์„ agNumberColumnFilter ์—์„œ ์„ ํƒํ•  ๋•Œ ํ•ด๋‹นํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ด ์ค„ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์—ฌ๋Ÿฌ๋ฒˆ API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ Merge ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์—ˆ์ง€๋งŒ ๊ทธ๋ ‡๊ฒŒ ๋˜๋ฉด SSRM ์‚ฌ์šฉ์˜ ๋ชฉ์ ์ด ๋ฌด์ƒ‰ํ•ด ์ง€๊ธฐ ๋•Œ๋ฌธ์— ์ ์ ˆํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ถœ๋ ฅํ•˜๊ณ  ํ•ด๋‹น ์ปฌ๋Ÿผ์˜ filter ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ณผ์ œ ์กฐ๊ฑด์—๋Š” ์—†์—ˆ์œผ๋‚˜ ์—ฌ๋Ÿฌ ํ•„ํ„ฐ๋ฅผ ๋™์‹œ์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ…Œ์ด๋ธ”์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•„์—ฐ์ ์œผ๋กœ ์ •๋ ฌ๊ณผ ํ•„ํ„ฐ๋ง ๋ชจ๋‘๋ฅผ ํ•œ๋ฒˆ์— ์ดˆ๊ธฐํ™”ํ•˜์—ฌ ์ดˆ๊ธฐ ์ƒํƒœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ๋ฆฌ์…‹ ์ปจํŠธ๋กค์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ์˜ ํ˜•ํƒœ๋Š” react-bootstrap ์˜ Button ์„ ์‚ฌ์šฉํ•˜์˜€๊ณ  ํ…Œ์ด๋ธ” ์ƒ๋‹จ ํˆด๋ฐ•์Šค ์šฐ์ธก์— ๋ฐฐ์น˜ํ•˜์—ฌ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด AG Grid ์˜ API์—์„œ ๊ฐ ์ปฌ๋Ÿผ์— ์ง€์ •๋œ ํ•„ํ„ฐ์˜ Instance๋ฅผ ํš๋“ํ•˜๊ณ  Filter Model ์„ null ๋กœ ์ดˆ๊ธฐํ™”ํ•œ ๋’ค, ์ •๋ ฌ, ํ•„ํ„ฐ๊ฐ€ ๋ฌ์„ ๊ฒฝ์šฐ ์ „๋‹ฌ๋˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜๋™์œผ๋กœ ํ˜ธ์ถœํ•˜์—ฌ ์ „์ฒด ํŽ˜์ด์ง€์˜ ๋ Œ๋”๋ง ์—†์ด ํ…Œ์ด๋ธ” ๋‚ด์˜ ๋ฐ์ดํ„ฐ๋งŒ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

Q3. ๋ชฉ๋ก์—์„œ ํ™˜์ž ํด๋ฆญ ์‹œ ์ƒ์„ธ ์ •๋ณด๋ฅผ child-row์— ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. (Solved)

์ด๋ฒˆ ๋ฌธ์ œ๋Š” ์ด๋ฏธ ๋งŒ๋“ค์–ด์ง„ full-function ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”์˜ ๊ฐ row ์— child-row ๋ฅผ ์ ‘์—ˆ๋‹ค ํˆ๋‹ค ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค๊ณ  ๊ทธ ์•ˆ์— ๋‘ ๊ฐ€์ง€ API๋กœ ๋ถ€ํ„ฐ ํš๋“ํ•œ ํ™˜์ž์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•˜๋Š”๊ฒŒ ๋ชฉ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. AG Grid ์—์„œ Master Detail ์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์ œ๊ณต๋˜๋Š” ์ƒ์„ธ๋‚ด์šฉ ์˜ต์…˜์ด ์žˆ์—ˆ์ง€๋งŒ ๊ธฐ๋ณธ์ ์œผ๋กœ 1๊ฐœ์˜ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋กœ ๋‹จ์ผ ํ…Œ์ด๋ธ”๋งŒ ์ถœ๋ ฅ ํ•  ์ˆ˜ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ™˜์ž์˜ ๋ฐฉ๋ฌธ ํšŸ์ˆ˜์™€ ์ง„๋‹จ ์ƒ์„ธ์ •๋ณด ํ…Œ์ด๋ธ”์„ ๋™์‹œ์— ์ถœ๋ ฅํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ข€๋” ์ž์„ธํžˆ ํŒŒ๊ณ ๋“ค์–ด ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์–ป์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

AG Grid ์—์„œ child-row ๋ฅผ ๊ทธ๋ฆฌ๋ ค๋ฉด detailCellRendererParams ๋ฅผ ํ†ตํ•ด child-row ์— ๋“ค์–ด๊ฐˆ ํ…Œ์ด๋ธ”์˜ ์˜ต์…˜๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ธฐ๋ณธ์ ์ธ ํ…Œ์ด๋ธ” ํ•˜๋‚˜ ๋ฐ–์— ์ถœ๋ ฅํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ detailCellRenderer prop์„ ํ†ตํ•ด์„œ ์ปค์Šคํ…€ cellRenderer ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ผ๋ฐ˜์ ์ธ React Component ์ฒ˜๋Ÿผ ๊ธฐ๋ณธ์ ์ธ ํ…Œ์ด๋ธ” ์™ธ์— ์›ํ•˜๋Š” ํ˜•์‹์œผ๋กœ child-row ๋‚ด์˜ ์š”์†Œ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด์ง„ customCellRenderer ์—๋Š” ๋ถ€๋ชจ row์˜ data ์™€ rowNode , ๋‚ด๋ถ€์—์„œ AG Grid ์˜ API์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•œ API ๊ฐ์ฒด๊ฐ€ prop์œผ๋กœ ์ „๋‹ฌ ๋ฉ๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ๋ถ€๋ชจ row๋กœ๋ถ€ํ„ฐ rowIndex๋‚˜ ๋ฐ์ดํ„ฐ ์•ˆ์˜ personID ํ•„๋“œ ๊ฐ’์„ ์ฐธ์กฐํ•˜์—ฌ ์ƒ์„ธ ์ •๋ณด API ํ˜ธ์ถœ์— ํ•„์š”ํ•œ parameter๋ฅผ ํš๋“ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const onGridReady = useCallback(
	(params) => {
		const gridInfo = {
			id: node.id,
			api: params.api,
			columnApi: params.columnApi,
		}
		setGridApi(params.api)

		api.addDetailGridInfo(rowId, gridInfo)
	},
	[node, rowId, api]
)

๋‹ค๋งŒ AG Grid ์˜ API ์ฐธ์กฐ ๊ฐ์ฒด๋Š” ์—ฌ๊ธฐ์ €๊ธฐ์„œ ๋ณต์ œ๋˜์–ด ์ „๋‹ฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— prop์œผ๋กœ ์ „๋‹ฌ๋˜๋Š” ๊ฒƒ์„ ์“ฐ์ง€ ์•Š๊ณ  ํ…Œ์ด๋ธ”์ด Mounted ์ƒํƒœ์ผ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” onGridReady() ์ฝœ๋ฐฑ ๋‚ด์—์„œ ํš๋“ํ•œ API ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ ํš๋“ํ•œ ๊ฐ์ฒด๋ฅผ gridAPI ๋กœ ์ €์žฅํ•ด๋‘๊ณ  API๋ฅผ ํ˜ธ์ถœํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” useEffect() ์˜ deps์— ์ถ”๊ฐ€ํ•ด ํ…Œ์ด๋ธ”์ด Mounted ๋ฌ์„ ๋•Œ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

customCellRenderer ๋‚ด์—์„œ ๊ทธ๋ ค์ง€๋Š” ํ…Œ์ด๋ธ”์—๋Š” ์ œ๊ณต๋œ API์— ๋ณ„๋„๋กœ ํ•„ํ„ฐ๋ง, ์ •๋ ฌ์„ ์œ„ํ•œ parameter๋ฅผ ๋ฐ›์ง€ ์•Š๊ณ  ํ•œ ๋ฒˆ์— ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ๋•Œ๋ฌธ์— SSRM์ด ์ ์šฉ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ข์€ ์˜์—ญ์— ๊ทธ๋ ค์ง€๋Š” ํ…Œ์ด๋ธ”์ž„์„ ๊ณ ๋ คํ•˜์—ฌ ํ•œ ๋ฒˆ์— 5๊ฐœ์˜ row๋ฅผ ํ‘œ์‹œํ•˜๋„๋ก ํ•˜๊ณ  ์ด๋ฏธ ๋ถˆ๋Ÿฌ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ pageSize๋กœ ๋‚˜๋ˆ„์–ด ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์™„์„ฑ๋œ ํ™˜์ž์˜ ์ง„๋‹จ ์ƒ์„ธ์ •๋ณด ํ…Œ์ด๋ธ” ์œ„์ชฝ์— react-bootstrap ์˜ Card ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™œ์šฉํ•ด ํ•ด๋‹น ํ™˜์ž๊ฐ€ ์ง€๊ธˆ๊นŒ์ง€ ๋ฐฉ๋ฌธํ•œ ํšŸ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

onRowClicked: (e) => {
    const { id: clickedRowIndex } = e.node
    e.api.forEachNode((row) => {
        if (row.id !== clickedRowIndex) {
            row.setExpanded(false)
        }
    })
    e.api.getDisplayedRowAtIndex(Number(clickedRowIndex)).setExpanded(!e.node.expanded)
},

์ถ”๊ฐ€์ ์œผ๋กœ ์•„์ฝ”๋””์–ธ ํ˜•ํƒœ๋กœ ํŽผ์ณ์กŒ๋‹ค๊ฐ€ ์ ‘ํžˆ๋Š” ๊ตฌ์กฐ์˜ child-row ์˜ ๋†’์ด ๋•Œ๋ฌธ์— ํŽ˜์ด์ง€์˜ ์Šคํฌ๋กค ๊ธธ์ด๊ฐ€ ์ง€๋‚˜์น˜๊ฒŒ ๊ธธ์–ด์ง€๋Š” ์ ์„ ์ธ์ง€ํ•ด ํ…Œ์ด๋ธ”์— ์ œ๊ณตํ•˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋™์‹œ์— ์—ด์–ด๋‘๊ณ  ๋น„๊ตํ•  ์ผ์ด ์—†๋‹ค๋Š” ๊ฒƒ์„ ์ „์ œ๋กœํ•˜์—ฌ ํ•œ ๋ฒˆ์— ํ•œ ๊ฐœ์˜ ์ƒ์„ธ์ •๋ณด child-row ๋งŒ ์—ด๋ ค์žˆ๋„๋ก ํ–ˆ์œผ๋ฉฐ ์—ด๋ ค์žˆ๋Š” child-row ๋‚ด์˜ ๋นˆ ์˜์—ญ์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ ๋ถ€๋ชจ row๋ฅผ ํด๋ฆญํ•  ๊ฒฝ์šฐ ๋‹ค์‹œ ๋‹ซํžˆ๋„๋ก ํ† ๊ธ€ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ child-row ๋˜ํ•œ ๋ถ€๋ชจ ํ…Œ์ด๋ธ”์˜ row๋กœ ์‚ฝ์ž…๋˜๊ธฐ ๋•Œ๋ฌธ์— rowIndex ๊ฐ€ ํ•˜๋‚˜์”ฉ ๋ฐ€๋ ค ์ด๋ฏธ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์ด ์—ด๋ ค ์žˆ๋Š” ์ƒํ™ฉ์—์„œ ๋˜ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์„ ํŽผ์น˜๋ ค๊ณ  ํ•  ๋•Œ ๋ฐ€๋ฆฐ rowIndex ์— ํ•ด๋‹นํ•˜๋Š” row๊ฐ€ ํŽผ์ณ์ง€๋Š” ํ˜„์ƒ์„ onRowClicked() ์ฝœ๋ฐฑ์—์„œ ํด๋ฆญ๋œ row์˜ ๊ณ ์œ  id ์™€ ๋น„๊ตํ•ด ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์›๋ž˜๋ผ๋ฉด gridApi ๋ฅผ ํ†ตํ•ด ํ•œ ๋ฒˆ์— ๋ชจ๋“  row๋ฅผ ์ ‘์„ ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•˜๋‚˜ deprecated ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— forEachNode() ๋กœ ํ˜„์žฌ ํŽ˜์ด์ง€์˜ row๋ฅผ ์ˆœํšŒํ•˜์—ฌ ๋‚ด๋ถ€ prop์—์„œ ํŽผ์ณ์ง„ ์ƒํƒœ๋ฅผ ์ฝ๊ณ  ๋‹ซ์•„์ค„ ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.

Q4. ํ…Œ์ด๋ธ” Component ์œ„์— ๊ทธ๋ž˜ํ”„ Component๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. (Solved)

์ด๋ฒˆ์—๋Š” API์—์„œ ์ œ๊ณตํ•˜๋Š” ํ†ต๊ณ„(์ง‘๊ณ„) ์ •๋ณด๋ฅผ ๋ฐ›์•„ ํŒŒ์ด ํ˜•ํƒœ์˜ ์ฐจํŠธ๋กœ ์ถœ๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ถœ๋ ฅํ•ด์•ผ ํ•˜๋Š” ํ•ญ๋ชฉ์€ ๋‹ค์Œ์˜ 5๊ฐ€์ง€ ์ž…๋‹ˆ๋‹ค.

  • ์„ฑ๋ณ„ ํ™˜์ž ์ˆ˜
  • ์ธ์ข…๋ณ„ ํ™˜์ž ์ˆ˜
  • ๋ฏผ์กฑ๋ณ„ ํ™˜์ž ์ˆ˜
  • (์„ฑ๋ณ„ + ์ธ์ข…)๋ณ„ ํ™˜์ž ์ˆ˜
  • (์„ฑ๋ณ„ + ๋ฏผ์กฑ)๋ณ„ ํ™˜์ž ์ˆ˜

API์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ ํ•ญ๋ชฉ๋ณ„๋กœ ์ง‘๊ณ„๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹Œ ๊ฐ ์กฐ๊ฑด๋ณ„๋กœ ๊ทธ๋ฃนํ™”๋œ ๋ฐ์ดํ„ฐ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌ์ŠคํŠธ ๋‚ด์˜ ๊ฐ์ฒด๋ฅผ ์—ด์–ด ๊ทธ๋ž˜ํ”„์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ์˜ ํ˜•ํƒœ๋กœ ๊ฐ€๊ณตํ•ด ์ค„ ํ•„์š”๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ApexCharts ์˜ Chart ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ํŒŒ์ด ํ˜•ํƒœ์˜ ์ฐจํŠธ๋ฅผ ๊ทธ๋ฆฌ๋ ค๋ฉด ๋‘ ๊ฐ€์ง€ array ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ๋ฐ, ํ•œ ๊ฐ€์ง€๋Š” labels ๋กœ ๊ฐ ํŒŒ์ด๋ณ„ legend(๋ฒ”๋ก€)์— ํ•ด๋‹นํ•˜๋Š” ํ…์ŠคํŠธ ์ด๋ฉฐ ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” series ๋กœ ์‹ค์ œ ์ˆซ์ž ๋ฐ์ดํ„ฐ๊ฐ€ ์ œ๊ณต๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ labels ์™€ series ์˜ ๊ฐ ํ•ญ๋ชฉ์€ 1๋Œ€1๋กœ ์ˆœ์„œ๊ฐ€ ๋งค์นญ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์šฐ์„  labels์— ์ œ๊ณตํ•  ๋ฒ”๋ก€ ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ์–ป๊ธฐ ์œ„ํ•ด์„œ ์ œ๊ณต๋œ API ์ค‘ 3๊ฐ€์ง€๋ฅผ ํ˜ธ์ถœํ•ด์„œ ๋กœ์ปฌ state ์— ์ €์žฅํ•˜๊ณ  ์ฒซ 3๊ฐ€์ง€ ์ฐจํŠธ์— ๊ฐ๊ฐ ์ œ๊ณตํ•˜๊ณ  ๋’ค์ชฝ์˜ ๋ณตํ•ฉ ์กฐ๊ฑด์„ ๊ฐ€์ง„ ์ฐจํŠธ์—๋Š” ์ง์ ‘ string array๋กœ labels ๋ฅผ ์ „๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค. series ์— ์ œ๊ณต๋  ๋ฐ์ดํ„ฐ๋Š” API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” useEffect() ๋‚ด์— ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ reduce() ๋ฅผ ํ†ตํ•ด ํ•„์š”ํ•œ ์กฐ๊ฑด๋ณ„๋กœ ์ง‘๊ณ„ํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋•Œ, ๋‘๋ฒˆ ์งธ-๋„ค๋ฒˆ ์งธ, ์„ธ๋ฒˆ ์งธ, ๋‹ค์„ฏ๋ฒˆ ์งธ ๊ทธ๋ž˜ํ”„๋Š” ๊ฐ๊ฐ ์„ฑ๋ณ„๋งŒ ์ถ”๊ฐ€ ์กฐ๊ฑด์œผ๋กœ ํ•„ํ„ฐ๋งํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ•œ ๋ฒˆ์˜ API ํ˜ธ์ถœ์—์„œ ๊ฐ๊ฐ ๋‘ ์ฐจํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋งŒ๋“ค์–ด ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

return data ? (
    <div className="pie-chart">
        <Chart
            options={chartOptions.options}
            series={chartOptions.series}
            type="pie"
            width="100%"
        />
    </div>
) : (
    <div>No data.</div>
)

์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด์ง„ 5๊ฐœ์˜ Chart ์ปดํฌ๋„ŒํŠธ๋ฅผ react-bootstrap ์˜ Row , Col, Card ๋กœ ๋งŒ๋“ค์–ด์ง„ ChartContainer ์ปดํฌ๋„ŒํŠธ์— ์ ์ ˆํžˆ ๋ฐฐ์น˜ํ•˜์—ฌ ํ…Œ์ด๋ธ” ์ปดํฌ๋„ŒํŠธ ์œ„์ชฝ์— ์ถœ๋ ฅ๋˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ •์ƒ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์ง€ ๋ชปํ•  ๊ฒฝ์šฐ์—๋Š” ์ ์ ˆํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ๋Œ€์‹ ํ•˜์—ฌ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

Q5. Q2์—์„œ ๊ตฌํ˜„ํ•œ ํ•„ํ„ฐ ์„ค์ •์— ๋”ฐ๋ผ Q4์˜ ๊ทธ๋ž˜ํ”„์˜ ๊ฐ’์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. (Timeout)

๋งˆ์ง€๋ง‰ ๋ฌธ์ œ์ธ ํ…Œ์ด๋ธ” ํ•„ํ„ฐ๋ง์„ ๊ทธ๋ž˜ํ”„ ๋ Œ๋”๋ง์— ๋ฐ˜์˜ํ•˜๋Š” ๊ฒƒ์€ ์‹œ๊ฐ„์ƒ์˜ ๋ฌธ์ œ์™€ ์ œ๊ณต๋˜๋Š” API์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ง‘๊ณ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ๊ฐ€๊ณตํ•ด์„œ ํ™˜์ž ๋ฆฌ์ŠคํŠธ API๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ ๋„ ๊ตฌํ˜„ํ•  ๋ฐฉ๋ฒ•์„ ์ฐพ์ง€ ๋ชปํ•ด ๊ตฌํ˜„ํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ƒ๊ฐํ–ˆ๋˜ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. App.jsx ์— AG Grid ํ…Œ์ด๋ธ”์—์„œ ํ•„ํ„ฐ๋ง์„ ํ•  ๋•Œ ๋ฐ˜ํ™˜๋˜๋Š” filterModel์„ ์ €์žฅํ•  state๋ฅผ ์„ ์–ธํ•œ๋‹ค.
  2. ํ…Œ์ด๋ธ” ์ปดํฌ๋„ŒํŠธ์— filterModel ์„ ์“ธ ์ˆ˜ ์žˆ๋Š” setFilterModel ๋ฉ”์„œ๋“œ๋ฅผ prop์œผ๋กœ ์ „๋‹ฌํ•˜๊ณ  ํ…Œ์ด๋ธ”์—์„œ ํ•„ํ„ฐ๋ง์„ ํ•  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰๋˜๋Š” onFilterChanged() ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์—์„œ setFilterModel(gridApi.getFilterModel()) ์™€ ๊ฐ™์ด filterModel ์„ ์ €์žฅํ•œ๋‹ค.
  3. ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ์—๋Š” filterModel ์„ prop์œผ๋กœ ์ „๋‹ฌํ•ด ์˜์กด์„ฑ์„ ๋งŒ๋“ค๊ณ  ์ฐจํŠธ ๋ฐ์ดํ„ฐ ํš๋“์„ ์œ„ํ•ด API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” useEffect() ์— filterModel ์„ deps๋กœ ์ถ”๊ฐ€ํ•ด ์‹ค์ œ ํ•„ํ„ฐ๋ง์ด ์ผ์–ด๋‚  ๋•Œ๋งˆ๋‹ค ๋‹ค์‹œ API๋ฅผ ํ˜ธ์ถœํ•ด ๋ Œ๋”๋งํ•˜๊ฒŒ ํ•˜๊ณ  filterModel ๋กœ ๋ถ€ํ„ฐ ์–ด๋–ค ์ปฌ๋Ÿผ์— ์–ด๋–ค ๊ฐ’์˜ ํ•„ํ„ฐ๊ฐ€ ์„ค์ • ๋˜์–ด์žˆ๋Š”์ง€๋ฅผ ์–ป์–ด๋‚ธ๋‹ค.
  4. ํ•„ํ„ฐ๊ฐ’์— ๋”ฐ๋ผ stats ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•˜๊ณ  ์žฌ ๊ฐ€๊ณตํ•ด์„œ ๊ฐ ์ฐจํŠธ์— ๋ฐ˜์˜ํ•œ๋‹ค.
// filterModel
{
    age: {
    filter: 10
    filterTo: 30
    filterType: "number"
    type: "inRange"
    }
    ethnicity: {
        filterType: "set"
        values: ['hispanic']
    }
    gender: {
        filterType: "set"
        values: ['M']
    }
    race: {
        filterType: "set"
        values: ['white']
    }
}

๋‹ค๋งŒ ์ด ๋ฐฉ๋ฒ•์€ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” useEffect() ์— ์ง€๋‚˜์น˜๊ฒŒ ๋งŽ์€ ์ฑ…์ž„์„ ๋ถ€์—ฌํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ๋„ˆ๋ฌด ๋งŽ์€ ์—ฐ์‚ฐ์„ ํ•„์š”๋กœ ํ•˜๊ณ  stats ๋ฐ์ดํ„ฐ๋ฅผ ๋„˜๊ฒจ์ฃผ๋Š” API์— ๊ฐ ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ์ง‘๊ณ„๊ฐ€ ์ฃผ์–ด์ง€๋Š” ํŽธ์ด ๋” ๋‚˜์€ ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ–ˆ์„ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ํ™˜์ž ๋ฆฌ์ŠคํŠธ API๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ง์ ‘ ํ•„ํ„ฐ๋ง๋œ ๋ฐ์ดํ„ฐ์…‹ ์ „์ฒด๋ฅผ ์ง‘๊ณ„ํ•ด์„œ ์ฐจํŠธ์— ๋ฐ˜์˜ํ•˜๋Š” ๊ฒƒ์ธ๋ฐ ์ด ๋˜ํ•œ ๋น„ํšจ์œจ ์ ์ž…๋‹ˆ๋‹ค.

์•„์‰ฌ์› ๋˜ ์ 

React๋ฅผ ์‚ฌ์šฉํ–ˆ๋˜ ๊ธฐ๊ฐ„์ด ๊ธธ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ ์ •ํ•˜๋Š”๋ฐ ์–ด๋ ค์›€์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. jQuery์—์„œ ๋Š˜์ƒ ์‚ฌ์šฉํ•˜๋˜ Datatables.js์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ๊ธฐ๋Œ€ํ•œ AG Grid ๋Š” ํ›จ์”ฌ ๋งŽ์€ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ์—๋„ ์ •ํ™•ํ•˜์ง€ ์•Š์€ ๋ฌธ์„œํƒ“์— ํ™œ์šฉ์ด ์‰ฝ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ตœ๋Œ€ํ•œ ๋งŽ์ด, ์ฃผ์–ด์ง„ ๊ณผ์ œ์—์„œ ์š”๊ตฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์— ์ดˆ์ ์„ ๋งž์ท„๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌ๋ Œ๋”๋ง ๋ฐฉ์ง€์™€ ๊ฐ™์€ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋‚˜ API ํ˜ธ์ถœ, ๋ฐ์ดํ„ฐ ํ™œ์šฉ ๋ถ€๋ถ„์—์„œ ๋ฆฌํŒฉํ† ๋ง์ด ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๊ฐ€ ๋‚จ๊ฒŒ ๋˜์—ˆ๊ณ  ์ž‘์€ ๋‹จ์œ„๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚˜๋ˆ  ํ™”๋ฉด์„ ์ข€ ๋” ์น˜๋ฐ€ํ•˜๊ฒŒ ๊ตฌ์„ฑํ•˜์ง€ ๋ชปํ•œ ์ ์ด ์•„์‰ฌ์›€์œผ๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค.

About

Medical Data Table App

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors