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
5 changes: 4 additions & 1 deletion src/app/taskmate/trash/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React from "react";

import AsyncBoundary from "@/components/common/AsyncBoundary";
import Trash from "@/components/trash";

const TrashPage = () => {
return (
<div className="tablet:max-w-[560px] mx-auto w-full max-w-[335px]">
<Trash />
<AsyncBoundary>
<Trash />
</AsyncBoundary>
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/SoftButton/SoftButton.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const Default: Story = {
export const Gray: Story = {
args: {
children: "회색버튼",
variant: "gray",
variant: "grayActive",
},
};

Expand Down
3 changes: 2 additions & 1 deletion src/components/common/SoftButton/SoftButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const softButtonVariants = cva(
variants: {
variant: {
purple: "bg-blue-100 text-blue-800 hover:bg-blue-200",
gray: "bg-background-normal-alternative-2 text-gray-400 ring-1 ring-gray-200 hover:ring-1 hover:bg-background-elevated-normal hover:text-gray-500 active:ring-1 active:ring-blue-900 active:text-blue-800",
grayActive:
"bg-background-normal-alternative-2 ring-1 ring-blue-900 text-blue-800",
},
},
defaultVariants: {
Expand Down
26 changes: 22 additions & 4 deletions src/components/trash/PersonalTrash/PersonalTrash.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import React from "react";

"use client";
import TrashEmpty from "@/components/trash/TrashEmpty";
import TrashList from "@/components/trash/TrashList";
import { trashQueries } from "@/constants/queryKeys/trash.queryKey";
import { useInfiniteScroll } from "@/hooks/useInfiniteScroll";

function PersonalTrash() {
const isEmpty = false;
return <div>{isEmpty ? <TrashEmpty /> : <TrashList />}</div>;
const { ref, data, isFetchingNextPage } = useInfiniteScroll(
trashQueries.personalTrashList(),
);

const items = data.pages.flatMap((page) => page.content);
const isEmpty = data.pages[0].totalElements === 0;
return (
<div>
{isEmpty ? (
<TrashEmpty />
) : (
<TrashList
items={items}
bottomRef={ref}
isFetchingNextPage={isFetchingNextPage}
/>
)}
</div>
);
}

export default PersonalTrash;
32 changes: 27 additions & 5 deletions src/components/trash/TeamTrash/TeamTrash.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
import React from "react";

import TrashEmpty from "../TrashEmpty";
import TrashList from "../TrashList";
import TrashEmpty from "@/components/trash/TrashEmpty";
import TrashList from "@/components/trash/TrashList";
import { trashQueries } from "@/constants/queryKeys/trash.queryKey";
import { useInfiniteScroll } from "@/hooks/useInfiniteScroll";

function TeamTrash() {
const isEmpty = true;
return <div>{isEmpty ? <TrashEmpty /> : <TrashList />}</div>;
interface TeamTrashProp {
selectedTeamId: number;
}

function TeamTrash({ selectedTeamId }: TeamTrashProp) {
const { ref, data, isFetchingNextPage } = useInfiniteScroll(
trashQueries.teamTrashList(selectedTeamId),
);
const items = data.pages.flatMap((page) => page.content);
const isEmpty = data.pages[0].totalElements === 0;
return (
<div>
{isEmpty ? (
<TrashEmpty />
) : (
<TrashList
items={items}
bottomRef={ref}
isFetchingNextPage={isFetchingNextPage}
/>
)}
</div>
);
}

export default TeamTrash;
45 changes: 33 additions & 12 deletions src/components/trash/TeamTrash/TeamTrashDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,38 @@
import { Icon } from "@/components/common/Icon";
import { useDropdown } from "@/hooks/useDropdown";

function TeamTrashDropdown() {
const options = [
"프론트엔드 1팀",
"기획 1팀",
"백엔드 2팀",
"프론테엔드프론테엔드프론테엔드프론테엔드",
];
interface Team {
teamId: number;
teamName: string;
}

interface TeamTrashDropdownProps {
teams: Team[];
selectedTeamId: number;
onSelect: (teamId: number) => void;
}

function TeamTrashDropdown({
teams,
selectedTeamId,
onSelect,
}: TeamTrashDropdownProps) {
const options = teams.map((team) => team.teamName);
const selectedTeamName = teams.find(
(team) => team.teamId === selectedTeamId,
)?.teamName;
const { isOpen, selected, toggle, selectItem, containerRef } = useDropdown(
options,
options[0],
selectedTeamName,
);

const handleSelect = (teamName: string) => {
const team = teams.find((team) => team.teamName === teamName);
if (!team) return;
onSelect(team.teamId);
selectItem(teamName);
};

return (
<div
ref={containerRef}
Expand All @@ -32,13 +53,13 @@ function TeamTrashDropdown() {
</button>
{isOpen && (
<ul className="bg-background-normal absolute w-full rounded-xl shadow-[0_4px_16px_-2px_rgba(0,0,0,0.1)]">
{options.map((option) => (
{teams.map((team) => (
<li
key={option}
onClick={() => selectItem(option)}
key={team.teamId}
onClick={() => handleSelect(team.teamName)}
className="text-label-neutral text-label-1 h-9 cursor-pointer truncate px-[11px] py-[5px] font-medium"
>
{option}
{team.teamName}
</li>
))}
</ul>
Expand Down
41 changes: 34 additions & 7 deletions src/components/trash/Trash.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,54 @@
"use client";
import { useSuspenseQuery } from "@tanstack/react-query";
import React, { useState } from "react";

import PersonalTrash from "./PersonalTrash";
import TeamTrash from "./TeamTrash";
import TeamTrashDropdown from "./TeamTrash/TeamTrashDropdown";
import TrashTabs, { TrashTab } from "./TrashTabs";
import PersonalTrash from "@/components/trash/PersonalTrash";
import TeamTrash from "@/components/trash/TeamTrash";
import TeamTrashDropdown from "@/components/trash/TeamTrash/TeamTrashDropdown";
import TrashEmpty from "@/components/trash/TrashEmpty";
import TrashTabs, { TrashTab } from "@/components/trash/TrashTabs";
import { teamQueries } from "@/features/team/query/team.queryKey";

import AsyncBoundary from "../common/AsyncBoundary";

function Trash() {
const [activeTab, setActiveTab] = useState<TrashTab>("team");
const [activeTab, setActiveTab] = useState<TrashTab>("personal");
const { data: teams } = useSuspenseQuery(teamQueries.all());
const [selectedTeamId, setSeletedTeamId] = useState<number | undefined>(
teams[0]?.teamId,
);

return (
<div className="mt-20 flex w-full flex-col">
<h1 className="text-title-3 text-label-neutral tablet:block tablet:mt-0 mt-[106px] mb-8 hidden font-semibold">
삭제된 할 일
</h1>
<div className="tablet:flex-row tablet:items-center tablet:justify-end flex flex-col">
<div className="tablet:w-full w-[209px] pr-2">
<TrashTabs
activeTab={activeTab}
onTabChange={setActiveTab}
/>
</div>
{activeTab === "team" && <TeamTrashDropdown />}
{activeTab === "team" && selectedTeamId !== undefined && (
<TeamTrashDropdown
teams={teams}
selectedTeamId={selectedTeamId}
onSelect={setSeletedTeamId}
/>
)}
</div>

<div className="w-full py-5">
{activeTab === "personal" ? <PersonalTrash /> : <TeamTrash />}
<AsyncBoundary>
{activeTab === "personal" ? (
<PersonalTrash />
) : selectedTeamId !== undefined ? (
<TeamTrash selectedTeamId={selectedTeamId} />
) : (
<TrashEmpty />
)}
</AsyncBoundary>
</div>
</div>
);
Expand Down
74 changes: 35 additions & 39 deletions src/components/trash/TrashItem/TrashItem.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,51 @@
import React from "react";

import { Icon } from "@/components/common/Icon";
import TrashBadge from "@/components/trash/TrashItem/TrashBadge";
import { TrashItemData } from "@/features/trash/types/trash.types";

import TrashBadge from "./TrashBadge";

const data = {
content: [
{
itemType: "GOAL",
id: 42,
deletedAt: "2026-04-15T02:17:29.290Z",
goalName: "이번 주 운동",
todoTitle: "러닝 30분",
},
],
page: 0,
size: 7,
totalElements: 24,
totalPages: 4,
};
interface TrashItemProps extends TrashItemData {
isSelected: boolean;
onToggle: (id: number) => void;
}

function TrashItem() {
function TrashItem({ isSelected, onToggle, ...item }: TrashItemProps) {
return (
<div className="bg-background-normal flex items-center gap-4 rounded-2xl px-2.5 py-2">
<div className="flex">
<Icon
name={"InactiveFilledCheckBox"}
className="tablet:size-6 size-5 cursor-pointer"
/>
<Icon
name={"ActiveFilledCheckBox"}
name={isSelected ? "ActiveFilledCheckBox" : "InactiveFilledCheckBox"}
className="tablet:size-6 size-5 cursor-pointer"
onClick={() => onToggle(item.id)}
/>
</div>
<div className="flex min-w-0 flex-col">
<div className="flex min-w-0 items-center">
<TrashBadge type={"goal"} />
<TrashBadge type={"todo"} />
<span className="text-label-1 truncate font-semibold text-slate-800">
강의 내용을 Notion이나 문서에 요약 정리 강의
</span>
</div>
<div className="flex items-center gap-1.5">
<Icon
name="Paper"
className="size-3 text-gray-300"
/>
<span className="text-label-2 font-medium text-gray-500">
디자인 시스템
</span>
</div>
{item.itemType === "GOAL" ? (
<div className="flex min-w-0 items-center gap-2">
<TrashBadge type={"goal"} />
<span className="text-label-1 truncate font-semibold text-slate-800">
{item.goalName}
</span>
</div>
) : (
<div className="flex flex-col gap-2">
<div className="flex min-w-0 items-center gap-2">
<TrashBadge type={"todo"} />
<span className="text-label-1 truncate font-semibold text-slate-800">
{item.todoTitle}
</span>
</div>
<div className="flex items-center gap-1.5">
<Icon
name="Paper"
className="size-3 text-gray-300"
/>
<span className="text-label-2 font-medium text-gray-500">
{item.goalName}
</span>
</div>
</div>
)}
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/trash/TrashItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as TrashBadge } from "./TrashBadge";
export { default } from "./TrashItem";
Loading
Loading