Skip to content
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';
import { Draggable } from 'react-beautiful-dnd';
import { CardData } from '@/types/dashboardView';
import { Cards } from '@/types/dashboardView';
import { useModal } from '@/app/(with-header-sidebar)/mydashboard/_hooks/useModal';
import Modal from '@/app/(with-header-sidebar)/mydashboard/_components/modal/Modal';
import CardInfo from './card-detail/CardInfo';
import styles from './Card.module.css';

interface Props {
item: CardData;
item: Cards;
index: number;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,22 @@
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 {
height: 100%;
overflow: hidden;
min-height: auto;
display: flex;
flex-direction: column;
flex-grow: 1;
}
102 changes: 78 additions & 24 deletions src/app/(with-header-sidebar)/dashboard/[id]/components/Column.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,70 @@
import React from 'react';
import React, { useRef, useEffect, useState } from 'react';
import { Droppable } from 'react-beautiful-dnd';
import { ColumnData } from '@/types/dashboardView';
import { Columns } from '@/types/dashboardView';
import Button from '@/components/Button';
import Image from 'next/image';
import Card from './Card';
import useModalStore from '@/store/modalStore';
import CreateTaskModal from './CreateTaskModal';
import styles from './Column.module.css';

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

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]);

const handleCreateTask = () => {
openModal(<CreateTaskModal />);
};

return (
<div className={styles.column}>
<div className={styles.column} ref={columnRef}>
<div className={styles.header}>
<div className={styles.status}>
<div style={{ background: color }} className={styles.dot}></div>
Expand Down Expand Up @@ -52,26 +100,32 @@ function Column({ color, title, totalCount, id, items }: ColumnData) {
</Button>
</div>

<Droppable
droppableId={`${id}`}
isDropDisabled={false}
isCombineEnabled={false}
ignoreContainerClipping={true}
direction="vertical"
>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
className={styles.dropContext}
>
{items.map((item, index) =>
item ? <Card key={item.id} item={item} index={index} /> : null
)}
{provided.placeholder}
</div>
)}
</Droppable>
<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>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { CardData } from '@/types/dashboardView';
import type { Cards } from '@/types/dashboardView';
import Avatar from '@/components/Avatar';
import { formatDateToCustomFormat } from '@/utils/dateUtils';
import styles from './Assignment.module.css';

interface AssignmentProps {
card: Pick<CardData, 'dueDate' | 'assignee'>;
card: Pick<Cards, 'dueDate' | 'assignee'>;
}

export default function Assignment({ card }: AssignmentProps) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CardData } from '@/types/dashboardView';
import { Cards } from '@/types/dashboardView';
import Assignment from './Assignment';
import styles from './CardInfo.module.css';

export default function CardInfo({ card }: { card: CardData }) {
export default function CardInfo({ card }: { card: Cards }) {
return (
<div className={styles.cardInfo}>
<div className={styles.assignmentContainer}>
Expand Down
145 changes: 93 additions & 52 deletions src/app/(with-header-sidebar)/dashboard/[id]/hooks/useDashBoardView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { useState, useEffect, useCallback } from 'react';
import debounce from 'lodash/debounce';
import axiosInstance from '@/lib/axiosInstance';
import { DropResult } from 'react-beautiful-dnd';
import { ColumnData, CardData } from '@/types/dashboardView';
import { Columns, Cards } from '@/types/dashboardView';
import { COLUMN_URL, CARD_URL } from '@/constants/urls';

export default function useDashBoardView(dashboardId: string | undefined) {
const [columns, setColumns] = useState<ColumnData[]>([]);
const [columns, setColumns] = useState<Columns[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [cursor, setCursor] = useState<string | null>(null);
const [cursors, setCursors] = useState<Record<number, number | null>>({});
Copy link
Owner

@najitwo najitwo Nov 26, 2024

Choose a reason for hiding this comment

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

μ½”λ“œ λŒ€λž΅μ μœΌλ‘œ λ³Έ 것 같은데 columsλž‘ cursors 같이 λ°€μ ‘ν•˜κ²Œ κ΄€λ ¨μžˆλŠ” 애듀은
ν†΅ν•©ν•΄μ„œ μƒνƒœ κ΄€λ¦¬ν•˜λŠ”κ²Œ 동기화 λ©΄μ—μ„œ 쒋은거 κ°™μŠ΅λ‹ˆλ‹€!
λ‚˜μ€‘μ— κ³ λ―Όν•΄λ³΄μ‹œλ©΄ 쒋을 κ±° κ°™μ•„μš”!


const fetchData = useCallback(async () => {
setLoading(true);
Expand All @@ -19,67 +19,58 @@ export default function useDashBoardView(dashboardId: string | undefined) {
);

const columns = columnData.data;

const columnIds: number[] = columns.map(
(column: ColumnData) => column.id
);
const columnIds: number[] = columns.map((column: Columns) => column.id);

const cardRequests = columnIds.map((columnId) =>
axiosInstance.get(
`${CARD_URL}?size=10&columnId=${columnId}&cursor=${cursor || ''}`
)
axiosInstance.get(`${CARD_URL}?size=10&columnId=${columnId}`)
);

const cardResponses = await Promise.all(cardRequests);
console.log(cardResponses[0].data.totalCount);

const updatedColumns = columns.map(
(column: ColumnData, index: number) => {
const cardData = cardResponses[index].data.cards;
const totalCount = cardResponses[index].data.totalCount;
return {
...column,
items: cardData || [],
totalCount: totalCount || 0,
};
}

const updatedColumns = columns.map((column: Columns, index: number) => {
const cardData = cardResponses[index].data.cards;
const totalCount = cardResponses[index].data.totalCount;

return {
...column,
items: cardData || [],
totalCount: totalCount || 0,
};
});

const initialCursors = updatedColumns.reduce(
(acc: Record<number, number | null>, column: Columns) => {
const lastCard = column.items[column.items.length - 1];
acc[column.id] = lastCard ? lastCard.id : null;
return acc;
},
{} as Record<number, number | null>
);

setColumns(updatedColumns);
setCursor(cardResponses[0].data.cursor);
} catch (err: unknown) {
setCursors(initialCursors);
} catch (err) {
if (err instanceof Error) setError(err.message);
} finally {
setLoading(false);
}
}, [dashboardId, cursor]);
//체크
}, [dashboardId]);

useEffect(() => {
console.log('λ Œλ”λ§ 체크');
if (!dashboardId) return;

fetchData();
}, [fetchData, dashboardId, cursor]);
//체크
}, [fetchData, dashboardId]);

const sendCardUpdateRequest = useCallback(
debounce(async (cardId: string, updatedCardData: CardData) => {
debounce(async (cardId: string, updatedCardData: Cards) => {
try {
const response = await axiosInstance.put(
`${CARD_URL}/${cardId}`,
updatedCardData
);
console.log('μ—…λŽƒμΉ΄λ“œλ°μ΄ν„°', updatedCardData);
if (response.status === 200 || response.status === 204) {
console.log('μΉ΄λ“œκ°€ μ„±κ³΅μ μœΌλ‘œ μ—…λ°μ΄νŠΈλ˜μ—ˆμŠ΅λ‹ˆλ‹€.');
}
} catch (err: unknown) {
await axiosInstance.put(`${CARD_URL}/${cardId}`, updatedCardData);
} catch (err) {
if (err instanceof Error) setError(err.message);
}
}, 200),
[]
);
//체크

const handleOnDragEnd = useCallback(
async (result: DropResult) => {
Expand Down Expand Up @@ -126,24 +117,74 @@ export default function useDashBoardView(dashboardId: string | undefined) {
updatedColumns[sourceColumnIndex].totalCount -= 1;
updatedColumns[destinationColumnIndex].totalCount += 1;

console.log(updatedColumns[sourceColumnIndex].totalCount);
try {
await sendCardUpdateRequest(`${removed.id}`, {
...removed,
columnId: removed.columnId,
});
} catch (err) {
if (err instanceof Error) setError(err.message);
}
}

const updatedCursors = updatedColumns.reduce(
(acc, column) => {
const lastCard = column.items[column.items.length - 1];
acc[column.id] = lastCard ? lastCard.id : null;
return acc;
},
{} as Record<number, number | null>
);

setColumns(updatedColumns);
setCursors(updatedCursors);
},
[columns, sendCardUpdateRequest]
);

const loadMoreData = useCallback(
debounce(async (columnId: number) => {
try {
await sendCardUpdateRequest(`${removed.id}`, {
...removed,
columnId: removed.columnId,
});
} catch (err: unknown) {
const currentCursor = cursors[columnId];

if (currentCursor === null) return;

const { data: cardResponses } = await axiosInstance.get(
`${CARD_URL}?size=10&cursorId=${currentCursor}&columnId=${columnId}`
Copy link
Collaborator

Choose a reason for hiding this comment

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

size 값도 μƒμˆ˜λ‘œ λΉΌμ„œ κ΄€λ¦¬ν•˜λ©΄ μ’‹μ„κ²ƒκ°™μ•„μš”

);

const newCards = cardResponses.cards;
const newCursorId = cardResponses.cursorId;

setColumns((prevColumns) =>
prevColumns.map((column) =>
column.id === columnId
? {
...column,
items: [
...column.items,
...newCards.filter(
(newCard: Cards) =>
!column.items.some(
(prevCard) => prevCard.id === newCard.id
)
),
],
}
: column
)
);

setCursors((prevCursors) => ({
...prevCursors,
[columnId]: newCursorId || null,
}));
} catch (err) {
if (err instanceof Error) setError(err.message);
}
},
[columns, sendCardUpdateRequest]
}, 200),
[cursors]
);

//체크

return { columns, loading, error, handleOnDragEnd };
return { columns, loading, error, handleOnDragEnd, loadMoreData };
}
Loading