Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions public/images/arrow-dropdown.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/drop-more.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 15 additions & 3 deletions src/app/dashboard/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import Image from 'next/image'
import { useParams } from 'next/navigation'
import { useRef } from 'react'
import { useEffect, useRef } from 'react'

import { useCardMutation } from '@/app/features/dashboard_Id/api/useCardMutation'
import useColumns from '@/app/features/dashboard_Id/api/useColumns'
import Column from '@/app/features/dashboard_Id/Column/Column'
import { useColumnsStore } from '@/app/features/dashboard_Id/store/useColumnsStore'
import { useDragStore } from '@/app/features/dashboard_Id/store/useDragStore'
import { Card } from '@/app/features/dashboard_Id/type/Card.type'

Expand All @@ -17,12 +18,23 @@ export default function DashboardID() {
// const { data: columns, isLoading, error } = useColumns(id)

const { draggingCard, setDraggingCard } = useDragStore()
const { setColumns } = useColumnsStore()
const cardMutation = useCardMutation()
const touchPos = useRef({ x: 0, y: 0 })
const prevColumn = useRef<HTMLElement | null>(null)
const longPressTimer = useRef<number | null>(null)
const isLongPressActive = useRef(false)

useEffect(() => {
if (columns) {
const transformed = columns.map((column) => ({
columnId: column.id,
columnTitle: column.title,
}))
setColumns(transformed)
}
}, [columns, setColumns])

const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
// 1. ํ„ฐ์น˜ ๋Œ€์ƒ ์ฐพ๊ธฐ
const target = e.target as HTMLElement
Expand Down Expand Up @@ -127,9 +139,9 @@ export default function DashboardID() {
if (error) return <p>error...{error.message}</p>
return (
<>
<div className="ml-300 select-none">
<div className="select-none">
<div
className="flex min-h-[calc(100vh-100px)] tablet:flex-col"
className="flex min-h-[calc(100vh-100px)] mobile:flex-row tablet:flex-col"
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
Expand Down
51 changes: 34 additions & 17 deletions src/app/features/dashboard_Id/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import Image from 'next/image'
import { useState } from 'react'

import { Avatar } from '@/app/shared/components/common/Avatar'

import { useDragStore } from '../store/useDragStore'
import type { Card as CardType } from '../type/Card.type'
import type { Column as ColumnType } from '../type/Column.type'
import CardContent from './cardModal/CardContent'
import CardModal from './cardModal/CardModal'
import Tags from './Tags'

export default function Card({
card,
columnId,
column,
}: {
card: CardType
columnId: number
column: ColumnType
}) {
const { id, imageUrl, title, tags, dueDate, assignee } = card
const { setDraggingCard } = useDragStore()
const [openCard, setOpenCard] = useState(false) //card.tsx

return (
<div
data-card-id={id}
Expand All @@ -23,6 +29,7 @@ export default function Card({
onDragStart={() => setDraggingCard({ cardData: card })}
onContextMenu={(e: React.MouseEvent) => e.preventDefault()}
className="BG-white Border-section relative rounded-6 border-solid px-20 py-16"
onClick={() => setOpenCard(true)}
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

๋“œ๋ž˜๊ทธ์™€ ํด๋ฆญ ์ด๋ฒคํŠธ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•˜์„ธ์š”.

์นด๋“œ์— ๋“œ๋ž˜๊ทธ ๊ธฐ๋Šฅ๊ณผ ํด๋ฆญ ์ด๋ฒคํŠธ๊ฐ€ ๋ชจ๋‘ ์žˆ์–ด์„œ ์˜๋„ํ•˜์ง€ ์•Š์€ ๋ชจ๋‹ฌ ์—ด๋ฆผ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

-      onClick={() => setOpenCard(true)}
+      onClick={(e) => {
+        // ๋“œ๋ž˜๊ทธ ์ค‘์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ๋ชจ๋‹ฌ ์—ด๊ธฐ
+        if (!isLongPressActive.current) {
+          setOpenCard(true)
+        }
+      }}

๋˜๋Š” ๋” ์ •๊ตํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ๋“œ๋ž˜๊ทธ ์‹œ์ž‘/์ข…๋ฃŒ ์ƒํƒœ๋ฅผ ์ถ”์ ํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Committable suggestion skipped: line range outside the PR's diff.

๐Ÿค– Prompt for AI Agents
In src/app/features/dashboard_Id/Card/Card.tsx at line 32, the onClick event
directly opens the modal, which conflicts with the drag functionality causing
unintended modal openings. To fix this, implement logic to track drag start and
end states, and only trigger setOpenCard(true) on click if a drag has not
occurred. This prevents the click event from firing during or immediately after
a drag action.

>
{imageUrl && (
<Image
Expand All @@ -42,36 +49,46 @@ export default function Card({
</h3>

{/* ํƒœ๊ทธ */}
<Tags tags={tags} />
{tags.length !== 0 && <Tags tags={tags} />}

{/* ๋งˆ๊ฐ์ผ & ๋‹ด๋‹น์ž */}
<div className="mt-8 flex content-around items-center">
{/* :๋งˆ๊ฐ์ผ */}
{dueDate && (
<div className="flex size-full items-center gap-6">
<Image
src={'/images/calendar.svg'}
alt="๋งˆ๊ฐ์ผ"
width={18}
height={18}
/>
<div className="flex size-full items-center gap-6">
<Image
src={'/images/calendar.svg'}
alt="๋งˆ๊ฐ์ผ"
width={18}
height={18}
/>
{dueDate && (
<div className="Text-gray mt-4 text-12 font-medium leading-none">
{dueDate}
{dueDate.split(' ')[0]}
</div>
</div>
)}

)}
</div>
{/* :๋‹ด๋‹น์ž */}
{assignee && (
<div className="shrink-0">
<Avatar
nickname={assignee.nickname}
imageUrl={assignee.profileImageUrl}
size={24}
name={assignee.nickname}
imageUrl={assignee.profileImageUrl}
/>
</div>
)}
</div>

{/* ์นด๋“œ ๋ชจ๋‹ฌ */}
{openCard && (
<CardModal>
<CardContent
onClose={() => setOpenCard(false)}
card={card}
column={column}
/>
</CardModal>
)}
</div>
)
}
10 changes: 10 additions & 0 deletions src/app/features/dashboard_Id/Card/ColumnTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function ColumnTitle({ title }: { title: string }) {
return (
<div className="BG-lightblue flex w-fit items-center gap-6 rounded-16 px-10 py-4">
<div className="size-6 rounded-25 bg-[#228DFF]"></div>
<div className="Text-deepblue pb-4 pt-6 text-14 font-medium leading-none">
{title}
</div>
</div>
)
}
18 changes: 18 additions & 0 deletions src/app/features/dashboard_Id/Card/MyAssignee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Avatar } from '@/app/shared/components/common/Avatar'

import { Assignee } from '../type/Card.type'

export default function MyAssignee({ assignee }: { assignee: Assignee }) {
return (
<div className="flex items-center gap-6">
<Avatar
size={26}
name={assignee.nickname}
imageUrl={assignee.profileImageUrl}
/>
<span className="regular Text-black block pt-1 text-16 leading-none">
{assignee.nickname}
</span>
</div>
)
}
22 changes: 19 additions & 3 deletions src/app/features/dashboard_Id/Card/Tags.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
import { useTheme } from 'next-themes'

import { getColor } from '@/app/shared/lib/getColor'

export default function Tags({ tags }: { tags: string[] }) {
//ํƒœ๊ทธ ์ปฌ๋Ÿฌ - ๋žœ๋ค ๋ฐฐ์ •
//์นด๋“œ ์ƒ์„ฑ ์‹œ - ๋™์ผ ํƒœ๊ทธ ์ž…๋ ฅ ๋ถˆ๊ฐ€ํ•˜๋„๋ก
const bgColors = ['#F9EEE3', '#E7F7DB', '#F7DBF0', '#DBE6F7']
const bgColorsDark = ['#774212', '#366712', '#711E5C', '#0F3167']
const textColors = ['#D58D49', '#86D549', '#D549B6', '#4981D5']

const { resolvedTheme } = useTheme()
const isDark = resolvedTheme === 'dark'

return (
<div className="flex flex-wrap gap-6">
{tags.map((tag) => {
const colorIndex = getColor(tag, bgColors.length)
// const colorIndex = getColor(tag, bgColors.length)
// getColorsํ•จ์ˆ˜ ์‚ฌ์šฉํ•˜๋ฉด NaN๊ฐ’์ด ๋– ์„œ ์ž‘๋™์„ ์•ˆํ•จ.. ์›๋ž˜ ๋ฌธ์ œ ์—†์—ˆ๋Š”๋ฐ ์ด์œ ๋ฅผ ๋ชจ๋ฅด๊ฒ ์Œ.
const hash = tag
.split('')
.reduce((acc, char) => acc + char.charCodeAt(0), 0)
const colorIndex = hash % 4
Comment on lines +18 to +23
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

getColor ํ•จ์ˆ˜ ๋ฌธ์ œ์˜ ๊ทผ๋ณธ ์›์ธ์„ ํ•ด๊ฒฐํ•˜์„ธ์š”.

์ฃผ์„์—์„œ ์–ธ๊ธ‰ํ•œ getColor ํ•จ์ˆ˜์˜ NaN ๋ฌธ์ œ๋ฅผ ๊ทผ๋ณธ์ ์œผ๋กœ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ธ๋ผ์ธ์œผ๋กœ ๊ตฌํ˜„ํ•œ ํ•ด์‹œ ๊ณ„์‚ฐ์€ getColor ํ•จ์ˆ˜์™€ ๋™์ผํ•œ ๋กœ์ง์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

-        // const colorIndex = getColor(tag, bgColors.length)
-        // getColorsํ•จ์ˆ˜ ์‚ฌ์šฉํ•˜๋ฉด NaN๊ฐ’์ด ๋– ์„œ ์ž‘๋™์„ ์•ˆํ•จ.. ์›๋ž˜ ๋ฌธ์ œ ์—†์—ˆ๋Š”๋ฐ ์ด์œ ๋ฅผ ๋ชจ๋ฅด๊ฒ ์Œ.
-        const hash = tag
-          .split('')
-          .reduce((acc, char) => acc + char.charCodeAt(0), 0)
-        const colorIndex = hash % 4
+        const colorIndex = getColor(tag, bgColors.length)

getColor ํ•จ์ˆ˜์˜ NaN ๋ฌธ์ œ๋ฅผ ๋จผ์ € ๋””๋ฒ„๊น…ํ•˜์—ฌ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// const colorIndex = getColor(tag, bgColors.length)
// getColorsํ•จ์ˆ˜ ์‚ฌ์šฉํ•˜๋ฉด NaN๊ฐ’์ด ๋– ์„œ ์ž‘๋™์„ ์•ˆํ•จ.. ์›๋ž˜ ๋ฌธ์ œ ์—†์—ˆ๋Š”๋ฐ ์ด์œ ๋ฅผ ๋ชจ๋ฅด๊ฒ ์Œ.
const hash = tag
.split('')
.reduce((acc, char) => acc + char.charCodeAt(0), 0)
const colorIndex = hash % 4
const colorIndex = getColor(tag, bgColors.length)
๐Ÿค– Prompt for AI Agents
In src/app/features/dashboard_Id/Card/Tags.tsx around lines 18 to 23, the
getColor function causes NaN errors, so it was replaced with inline hash
calculation. To fix this properly, debug the getColor function to identify why
it returns NaN, such as checking input validity and array length usage. Then
restore the use of getColor by ensuring it handles inputs correctly and returns
a valid index within the color array bounds.


const backgroundColor = isDark
? bgColorsDark[colorIndex]
: bgColors[colorIndex]
const textColor = isDark ? bgColors[colorIndex] : textColors[colorIndex]

return (
<span
key={tag}
className="inline-block whitespace-nowrap rounded-4 px-9 pb-3 pt-5"
style={{
backgroundColor: bgColors[colorIndex],
color: textColors[colorIndex],
backgroundColor: backgroundColor,
color: textColor,
}}
>
{tag}
Expand Down
49 changes: 49 additions & 0 deletions src/app/features/dashboard_Id/Card/TagsCanDelete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useTheme } from 'next-themes'

import { getColor } from '@/app/shared/lib/getColor'

export default function TagsCanDelete({
tags,
setTags,
}: {
tags: string[]
setTags: React.Dispatch<React.SetStateAction<string[]>>
}) {
//ํƒœ๊ทธ ์ปฌ๋Ÿฌ - ๋žœ๋ค ๋ฐฐ์ •
//์นด๋“œ ์ƒ์„ฑ ์‹œ - ๋™์ผ ํƒœ๊ทธ ์ž…๋ ฅ ๋ถˆ๊ฐ€ํ•˜๋„๋ก
const bgColors = ['#F9EEE3', '#E7F7DB', '#F7DBF0', '#DBE6F7']
const textColors = ['#D58D49', '#86D549', '#D549B6', '#4981D5']

const { resolvedTheme } = useTheme()
const isDark = resolvedTheme === 'dark'

return (
<div className="flex flex-wrap gap-6">
{tags.map((tag) => {
const hash = tag
.split('')
.reduce((acc, char) => acc + char.charCodeAt(0), 0)
const colorIndex = hash % 4

const backgroundColor = isDark
? textColors[colorIndex]
: bgColors[colorIndex]
const textColor = isDark ? bgColors[colorIndex] : textColors[colorIndex]

return (
<span
key={tag}
className="inline-block whitespace-nowrap rounded-4 px-9 pb-3 pt-5"
style={{
backgroundColor: backgroundColor,
color: textColor,
}}
onClick={() => setTags(tags.filter((t) => t !== tag))}
>
{tag}
</span>
)
})}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { ControllerRenderProps } from 'react-hook-form'

import { Avatar } from '@/app/shared/components/common/Avatar'
import { cn } from '@/app/shared/lib/cn'

import getDashboardMembers from '../../lib/getDashboardMembers'
import { Assignee } from '../../type/Card.type'
import { CardFormData } from '../../type/CardFormData.type'
import { Member } from '../../type/Member.type'
import MyAssignee from '../MyAssignee'

export interface Assignee {
userId: number
nickname: string
}
interface AssigneeListProps {
members: Member[] | undefined
setAssignee: (assignee: Assignee) => void
Expand All @@ -36,13 +35,13 @@ export default function AssigneeList({
'BG-Input-hovered w-full cursor-pointer px-16 py-11 pt-14 placeholder-gray-400 caret-transparent',
index !== 0 && 'border-t',
)}
key={assignee.userId}
key={assignee.id}
onClick={() => {
setAssignee(assignee) // ๋‹ด๋‹น์ž ์—…๋ฐ์ดํŠธ
controlField.onChange(assignee.userId) // ๋ฆฌ์•กํŠธ ํ›…์—๋Š” .userId ๊ฐ’ ์—ฐ๊ฒฐ
controlField.onChange(assignee.id) // ๋ฆฌ์•กํŠธ ํ›…์—๋Š” .userId ๊ฐ’ ์—ฐ๊ฒฐ
}}
>
{assignee.nickname}
<MyAssignee assignee={assignee} />
</div>
))}
</div>
Expand Down
44 changes: 44 additions & 0 deletions src/app/features/dashboard_Id/Card/cardFormModals/ColumnList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ControllerRenderProps } from 'react-hook-form'

import { cn } from '@/app/shared/lib/cn'

import { SimpleColumn, useColumnsStore } from '../../store/useColumnsStore'
import { CardFormData } from '../../type/CardFormData.type'
import ColumnTitle from '../ColumnTitle'

interface ColumnListProps {
setColumn: (selectedColumn: SimpleColumn) => void
controlField: ControllerRenderProps<CardFormData, 'columnId'>
}

// โœ… ColumnList ์ปดํฌ๋„ŒํŠธ: ์ปฌ๋Ÿผ ๋ชฉ๋ก์„ ๋ณด์—ฌ์ฃผ๋Š” ๋“œ๋กญ๋‹ค์šด ๋ฆฌ์ŠคํŠธ
// 1. ์ปฌ๋Ÿผ ๋ชฉ๋ก์„ ํด๋ฆญํ•˜๋ฉด:
// - setColumn(column) ์‹คํ–‰ โ†’ ์„ ํƒ๋œ ๋‹ด๋‹น์ž ๊ฐ์ฒด๋ฅผ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ํ•˜์— ๊ด€๋ฆฌ (ex. UI์—์„œ ๋‹‰๋„ค์ž„ ํ‘œ์‹œ์šฉ)
// - controlField.onChange(column.columnId) ์‹คํ–‰ โ†’ react-hook-form์— columnId ๊ฐ’์„ ์ „๋‹ฌ (form ์ œ์ถœ์—๋Š” Id ๋ฐ์ดํ„ฐ๋งŒ ์ „๋‹ฌํ•จ)

export default function ColumnList({
setColumn,
controlField,
}: ColumnListProps) {
const { ColumnsInDashboard } = useColumnsStore() // ์ปฌ๋Ÿผ ๋ชฉ๋ก ๋ฐ์ดํ„ฐ๋Š” store์—์„œ ๋ถˆ๋Ÿฌ์˜ด

return (
<div className="BG-white Border-btn absolute left-0 top-full z-10 mt-4 w-full rounded-6">
{ColumnsInDashboard.map((column, index) => (
<div
className={cn(
'BG-Input-hovered w-full cursor-pointer px-16 py-11 pt-14 placeholder-gray-400 caret-transparent',
index !== 0 && 'border-t',
)}
key={column.columnId}
onClick={() => {
setColumn(column) // ๋‹ด๋‹น์ž ์—…๋ฐ์ดํŠธ
controlField.onChange(column.columnId) // controlField: ํผ์˜ 'columnId' ํ•„๋“œ์™€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Œ. .columnId ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธ
}}
>
<ColumnTitle title={column.columnTitle} />
</div>
))}
</div>
)
}
Loading
Loading