Skip to content
Merged
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"@svgr/webpack": "^8.1.0",
"@types/react-beautiful-dnd": "^13.1.8",
"axios": "^1.7.7",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
"next": "15.0.3",
"react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1",
Expand All @@ -21,6 +23,7 @@
"zustand": "^5.0.1"
},
"devDependencies": {
"@types/lodash": "^4.17.13",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.card {
padding: 16px;
border-radius: 6px;
border: 1px solid var(--gray-300);
background: var(--white);
margin-bottom: 20px;
height: 150px;
}

.card.dragging {
margin: 0;
}
32 changes: 32 additions & 0 deletions src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { Draggable } from 'react-beautiful-dnd';
import { CardData } from '@/types/dashboardView';
import styles from './Card.module.css';

interface Props {
item: CardData;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μ œκ°€ cardμͺ½ κ°€μ Έλ‹€ μ“°λ©΄μ„œ λ“  의문인데 이거 μΈν„°νŽ˜μ΄μŠ€ 뒀에 Dataμ™œ λΆ™μ΄μ‹ κ±°μ—μš”..?γ…‹γ…‹γ…‹
λͺ¨λ“ κ²Œ 데이터인데.. μ—¬κΈ°λ§Œ λΆ™μ–΄μžˆκΈΈλž˜ γ…‹γ…‹γ…‹ μ™œλΆ™μ˜€μ„κΉŒ... ν•˜λŠ” 의문...?πŸ˜‚

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μ•„ μ²˜μŒμ— κ·Έλƒ₯ card둜 μ“°λ‹€κ°€ 이름 쀑볡이라고 μ—λŸ¬ 뜨길래 μž„μ‹œλ‘œ λˆ„λ”κΈ° λ³΄μˆ˜ν•œ κ±°μ˜ˆμš” γ…‹γ…‹ 제일 많이 μ“Έ κ±° κ°™μ•„μ„œ 길게 μ“΄λ‹€λŠ” 게 γ…‹γ…‹γ…‹

index: number;
}

function Card({ item, index }: Props) {
if (!item || !item.id) {
return null;
}

return (
<Draggable draggableId={`${item.id}`} index={index}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

item.id ν•˜λ‚˜λ°–μ—μ—†μœΌλ©΄ λ°±ν‹± 빼도 λ™μΌν•˜κ²Œ μž‘λ™..λ§žλ‚˜μš”?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

draggableId에 string λ„£μ–΄μ•Ό ν•΄μ„œ μ €λ ‡κ²Œ λ„£μ—ˆμ–΄μš”

{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={`${styles.card} ${snapshot.isDragging ? styles.dragging : ''}`}
>
<div>{item.id}</div>
</div>
)}
</Draggable>
);
}

export default React.memo(Card);
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
.column {
display: flex;
flex-direction: column;
width: 354px;
height: 1010px;
padding: 20px;
border-right: 1px solid var(--gray-200);
}

.header {
display: flex;
justify-content: space-between;
align-items: center;
}

.status {
display: flex;
align-items: center;
}

.dot {
width: 8px;
height: 8px;
border-radius: 50%;
}

.title {
color: var(--black-100);
font-size: 18px;
font-weight: 700;
margin-left: 8px;
margin-right: 12px;
}

.totalCount {
display: flex;
width: 20px;
height: 20px;
padding: 3px 6px;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
flex-shrink: 0;
border-radius: 4px;
background: var(--gray-200);
color: var(--gray-500);
font-size: 12px;
font-weight: 500;
}

.settingsWrapper {
width: 24px;
height: 24px;
}

.settings {
background-color: transparent;
width: 100%;
height: 100%;
}

.createCardSection {
margin-top: 25px;
margin-bottom: 16px;
}

.createCard {
display: flex;
align-items: center;
justify-content: center;
width: 314px;
height: 40px;
border: 1px solid var(--gray-300);
background: var(--white);
font-size: 18px;
font-weight: 700;
color: var(--black-100);
line-height: 26px;
gap: 12px;
border-radius: 8px;
}

.createCardIcon {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
padding: 3px;
border-radius: 4px;
background: var(--violet-light);
}

.scrollContext {
overflow-x: hidden;
overflow-y: auto;
max-height: calc(100vh - 124px);
height: auto;
display: flex;
flex-direction: column;
}

.scrollContext::-webkit-scrollbar {
width: 0;
}

.dropContext {
min-height: auto;
display: flex;
flex-direction: column;
flex-grow: 1;
}
125 changes: 125 additions & 0 deletions src/app/(with-header-sidebar)/dashboard/[id]/components/Column.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { useRef, useEffect, useState } from 'react';
import { Droppable } from 'react-beautiful-dnd';
import { ColumnData } from '@/types/dashboardView';
import Button from '@/components/Button';
import Image from 'next/image';
import Card from './Card';
import styles from './Column.module.css';

function Column({
color,
title,
totalCount,
id,
items,
loadMoreData,
}: ColumnData) {
const observerRef = useRef<IntersectionObserver | null>(null);
const loadMoreRef = useRef<HTMLDivElement | null>(null);
const columnRef = useRef<HTMLDivElement | null>(null);
const [minHeight, setMinHeight] = useState<string>('auto');

useEffect(() => {
observerRef.current = new IntersectionObserver(
(entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
loadMoreData(id);
}
Comment on lines +26 to +31
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μ»΄ν¬λ„ŒνŠΈκ°€ λ Œλ”λ§ λ λ•Œλ§ˆλ‹€ IntersectionObserver 객체가 μƒˆλ‘œ μƒμ„±λ˜λŠ”κ²Œ λ§žλ‚˜μš”?
μ˜λ„ 된게 μ•„λ‹ˆλΌλ©΄

if (!observerRef.current) {

μ΄λŸ°μ‹μœΌλ‘œ 막아 μ£ΌλŠ”κ²ƒμ„ μΆ”μ²œλ“œλ €μš”!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μƒˆλ‘œ μƒμ„±λ˜λŠ” 게 λ§žμŠ΅λ‹ˆλ‹€ !
생각해보면 컬럼 μžμ²΄κ°€ λ¦¬λ Œλ”λ§μ΄ μž¦μ„ κ±° κ°™μœΌλ‹ˆ μ§€μ›λ‹˜ 말씀이 λ§žλŠ” 것 κ°™μ•„μš”! μˆ˜μ •ν• κ²Œμš”!

},
{
root: document.querySelector('.scrollContext'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

였.. 이거 styles.scrollContext μ΄λ ‡κ²Œ 클래슀λͺ… 넣어버리면 λ Œλ”λ§λœ html보면 ν•΄μ‹œκ°’ μ–΄μ©Œκ³ λ‘œ λ°”λ€ŒλŠ”λ°
μ΄λ ‡κ²Œ μ„ νƒμž μ“°λ©΄ 원본(?)이 잘 μ„ νƒλ˜λŠ”κ±΄κ°€λ³΄λ„€μš”..?
(그럼 클래슀λͺ…은 μ–Έμ œ λ°”λ€ŒλŠ”κ±°μ§€...πŸ€”)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

root: ~λŠ” DOM λ…Έλ“œλ‘œ μ„€μ •ν•˜λ©΄ λœλ‹€κ³  ν•΄μ„œ μ €λ ‡κ²Œ λ°”λ‘œ λ„£μ—ˆκ³ ,
μ œκ°€ 이해λ₯Ό 잘 λͺ»ν•΄μ„œ λ§žλŠ” λŒ€λ‹΅μΈ μ§€ λͺ¨λ₯΄κ² μ–΄μš”.
DOM 트리 생성 후에 CSS > ν•˜μ΄λ“œλ ˆμ΄μ…˜ λ“€μ–΄κ°€λ‹ˆκΉŒ 이미 DOM νŠΈλ¦¬μ— μ‘΄μž¬ν•˜λŠ” μš”μ†ŒμΌ κ±°κ³ , μŠ€νƒ€μΌλ§ 이후에 ν•΄λ‹Ή λ…Έλ“œκ°€ root둜 트리거 λ˜λŠ” κ±° μ•„λ‹κΉŒμš”?

threshold: 0.4,
}
);

const loadMoreElement = loadMoreRef.current;

if (totalCount === 0) return;

if (loadMoreElement && observerRef.current && totalCount >= 10) {
observerRef.current.observe(loadMoreElement);
}

return () => {
if (loadMoreElement && observerRef.current) {
observerRef.current.unobserve(loadMoreElement);
}
};
}, [id, loadMoreData]);
Comment on lines +51 to +52
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totalCountκ°€ λˆ„λ½λœ 것 같은데 μ˜λ„ ν•˜μ‹ κ±΄κ°€μš”?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

흠 μž¬μƒμ„± 막고, 이거 μΆ”κ°€ν•΄μ„œ λΆ€κ°€ 쑰건도 μΆ”κ°€ν•΄λ³Όκ²Œμš”.


useEffect(() => {
if (items.length === 0 || items.length <= 5) {
setMinHeight('866px');
} else {
setMinHeight('auto');
}
}, [items]);

return (
<div className={styles.column} ref={columnRef}>
<div className={styles.header}>
<div className={styles.status}>
<div style={{ background: color }} className={styles.dot}></div>
<div className={styles.title}>{title}</div>
<div className={styles.totalCount}>{totalCount}</div>
</div>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

νƒœκ·Έ 쀑간쀑간에 μ—”ν„°λŠ” μ—†μœΌλ©΄ λ”μ’‹μ„κ²ƒκ°™μ•„μš”...γ…Žγ…Ž

<div className={styles.settingsWrapper}>
<Button
type="button"
aria-label="컬럼 μ„€μ • λ²„νŠΌ"
className={styles.settings}
>
<Image src="/icons/settings.svg" width={24} height={24} alt="" />
</Button>
</div>
</div>

<div className={styles.createCardSection}>
<Button
type="button"
className={styles.createCard}
aria-label="컬럼 생성 λ²„νŠΌ"
>
<Image
src="/icons/add.svg"
width={22}
height={22}
alt=""
className={styles.createCardIcon}
/>
</Button>
</div>

<div className={styles.scrollContext}>
<Droppable
droppableId={`${id}`}
isDropDisabled={false}
isCombineEnabled={false}
ignoreContainerClipping={true}
direction="vertical"
>
{(provided) => (
<div
className={styles.dropContext}
ref={provided.innerRef}
{...provided.droppableProps}
style={{ minHeight }}
>
{items.map((item, index) =>
item ? <Card key={item.id} item={item} index={index} /> : null
)}

{provided.placeholder}

<div ref={loadMoreRef} style={{ height: '1px' }} />
</div>
)}
</Droppable>
</div>
</div>
);
}

export default React.memo(Column);
Loading