Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
12416ba
fix: Input 스타일에 맞게 textarea 스타일 변경
jjjsun Mar 23, 2026
ad8b06e
fix: placeholder 내 내용 수정
jjjsun Mar 23, 2026
0df82b7
style: 아이콘 SVG 색상과 동일하게 dropdownMenu 색상 수정
jjjsun Mar 23, 2026
b6cd7ba
refactor: 워크스페이스 관련 페이지 태블릿 반응형 UI 보완
jjjsun Mar 24, 2026
aaacf54
fix: 삭제된 조직이 새로고침없이 목록 화면에서 바로 반영되도록 수정
jjjsun Mar 24, 2026
9771350
feat: 현재 선택된 대시보드 기준 워크스페이스 나타내는 UI 추가, 상단으로 끌어올리기 추가
jjjsun Mar 24, 2026
7469444
chore: 워크스페이스 목록/관리 페이지 내 로고 이미지 크기 및 UI 수정
jjjsun Mar 24, 2026
734f55e
chore: 생성하기 모달 내 재배치 진행, 이미지 크기 변경
jjjsun Mar 24, 2026
3547b35
refactor: 역할 라벨을 공용 상수로 분리하고, 한국어 표시로 변경
jjjsun Mar 24, 2026
b6cf104
refactor: 생성하기 모달 태블릿 반응형 모달 크기 조정
jjjsun Mar 24, 2026
008e801
fix: 코드래빗 수정사항 반영
jjjsun Mar 24, 2026
195105b
fix: build에러 수정(충돌 수정)
jjjsun Mar 25, 2026
0d3df49
fix: TextareaFiled 충돌수정시 생긴 오류 수정
jjjsun Mar 25, 2026
68e8917
fix: 충돌수정하면서 리셋된 Textarea UI수정 재진행
jjjsun Mar 25, 2026
14725ac
fix: 워크스페이스 생성모달 내 로고이미지 텍스트 다른 label과통일되도록 수정
jjjsun Mar 25, 2026
056eecf
fix: build에러 수정
jjjsun Mar 25, 2026
a3668b7
chore: WorkspaceCard 클릭시 WorkspaceSetting 페이지로 이동
jjjsun Mar 25, 2026
a35e06f
feat: WorkspaceSetting페이지 내 멤버관리 이동 버튼 추가, 아이템별 간격조정
jjjsun Mar 25, 2026
634be58
feat: 뒤로가기 버튼 추가
jjjsun Mar 25, 2026
8207f22
Merge branch 'develop' into refactor/#127
jjjsun Mar 26, 2026
55fa111
fix: Build 오류해결
jjjsun Mar 26, 2026
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
7 changes: 6 additions & 1 deletion src/components/common/PageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ const PageHeader = memo(function PageHeader({
className,
}: IPageHeaderProps) {
return (
<header className={twMerge("flex items-center justify-between", className)}>
<header
className={twMerge(
"flex items-center justify-between tablet:items-start tablet:gap-3",
className,
)}
>
<div className="flex flex-col gap-1">
<h1 className="font-heading2 text-text-main">{title}</h1>
{description && (
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/controlbox/ControlBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export default function ControlBox({
return (
<div
className={twMerge(
"flex w-full min-w-0 items-center justify-between gap-6 px-7 py-6 border-[0.5px] rounded-component-lg",
"border-chart-3 bg-chart-3/7",
"flex w-full min-w-0 items-center justify-between gap-6 px-7 py-6 border-[0.5px] border-chart-3/7 rounded-component-lg",
"tablet:flex-col tablet:items-start tablet:px-5 tablet:py-5",
containerClassName,
className,
)}
Expand Down Expand Up @@ -78,7 +78,7 @@ export default function ControlBox({
onClick={onButtonClick}
disabled={buttonDisabled}
className={twMerge(
"shrink-0 px-8 rounded-component-md!",
"shrink-0 px-8 rounded-component-md! tablet:w-full tablet:justify-center",
buttonClassName,
)}
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/dropdownmenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function DropdownMenu({
"group flex w-full items-center gap-3 rounded-component-md px-5 py-4 text-left font-body2 transition-fast",
it.active
? "bg-brand-300 text-status-blue"
: "text-text-auth-sub hover:bg-brand-300 hover:text-status-blue",
: "text-text-main hover:bg-brand-300 hover:text-status-blue",
)}
>
{it.icon ? (
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const Input = forwardRef<HTMLInputElement, IInputProps>(
<input
ref={ref}
className={twMerge(
"flex-1 h-full w-full bg-transparent border-none outline-none text-body1 text-text-main placeholder:text-text-placeholder px-5",
"flex-1 h-full w-full bg-transparent border-none outline-none font-body1 text-text-main placeholder:text-text-placeholder px-5",
"disabled:cursor-not-allowed disabled:text-text-disabled",
rightElement ? "pr-2" : "",
inputClassName,
Expand Down
5 changes: 4 additions & 1 deletion src/components/common/textarea/TextareaField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ export default function TextareaField({
rows={minRows}
value={value}
className={twMerge(
"w-full resize-none overflow-hidden rounded-component-md bg-gray-50 px-5 py-4 outline-none transition-smooth hover:bg-gray-100 focus:bg-white focus:ring-2 focus:ring-logo-1/30 text-body1 text-text-main placeholder:text-text-placeholder",
"w-full rounded-component-md px-5 py-4 ring-1 ring-logo-1/30 outline-none transition-colors duration-200 ease-out overflow-hidden placeholder:text-text-placeholder text-text-main font-body1",
disabled
? "bg-gray-100 cursor-not-allowed"
: "hover:bg-gray-100 hover:ring-logo-1/40 focus-within:bg-white focus-within:ring-2 focus-within:ring-logo-1/50",
className,
)}
placeholder={placeholder}
Expand Down
10 changes: 5 additions & 5 deletions src/components/workspace/MemberItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export default function MemberItem({
onDeleteClick,
}: TProps) {
return (
<li className="flex items-center justify-between py-5 gap-4">
<div className="flex items-center gap-4">
<li className="flex items-center justify-between py-5 gap-4 tablet:items-start">
<div className="flex items-center gap-4 w-full min-w-0">
<div className="flex bg-text-placeholder/30 h-12 w-12 items-center justify-center shrink-0 rounded-component-lg overflow-hidden">
{member.profileImageUrl ? (
<img
Expand All @@ -34,9 +34,9 @@ export default function MemberItem({
<UserIcon className="text-text-auth-sub h-6 w-6" />
)}
</div>
<div className="min-w-0">
<div className="min-w-0 flex-1">
<p className="truncate text-text-main font-body1">{member.name}</p>
<div className=" flex text-text-auth-sub items-center gap-2">
<div className="flex text-text-auth-sub items-center gap-2 min-w-0">
<MailIcon className="w-4 h-4" />
<p className="truncate">{member.email}</p>
</div>
Expand All @@ -45,7 +45,7 @@ export default function MemberItem({
<div className="flex items-center gap-4">
{member.isMe ? (
<span
className={`inline-flex h-10 min-w-24.5 items-center justify-center rounded-[22px] px-4 font-body2 ${
className={`inline-flex h-10 min-w-24.5 items-center justify-center rounded-component-lg px-4 font-body2 ${
member.role === "ADMIN"
? "bg-status-blue/80 text-white shadow-sm"
: "bg-chart-3/15 text-text-auth-sub"
Expand Down
2 changes: 1 addition & 1 deletion src/components/workspace/MemberList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default function MemberList({

return (
<div className="bg-white border border-gray-100 rounded-component-lg p-8 shadow-Soft">
<header className="mb-7 flex justify-between">
<header className="mb-7 flex items-start justify-between gap-4">
<div>
<h2 className="font-heading4 text-text-main font-semibold!">
팀 구성원
Expand Down
12 changes: 4 additions & 8 deletions src/components/workspace/MemberRoleSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { TMemberRole } from "@/types/workspace/workspace";
import { ROLE_LABEL_MAP } from "@/constants/workspaceRole";

import {
DropdownMenu,
Expand All @@ -13,11 +14,6 @@ type TProps = {
disabled?: boolean;
};

const roleLabelMap: Record<TMemberRole, string> = {
ADMIN: "관리자",
MEMBER: "멤버",
};

const triggerStyleMap: Record<TMemberRole, string> = {
ADMIN: "bg-status-blue/80 text-white shadow-sm",
MEMBER: "bg-chart-3/15 text-text-auth-sub",
Expand All @@ -32,12 +28,12 @@ export default function MemberRoleSelect({
? []
: [
{
label: "관리자",
label: ROLE_LABEL_MAP.ADMIN,
onClick: () => onChange("ADMIN"),
active: role === "ADMIN",
},
{
label: "멤버",
label: ROLE_LABEL_MAP.MEMBER,
onClick: () => onChange("MEMBER"),
active: role === "MEMBER",
},
Expand All @@ -51,7 +47,7 @@ export default function MemberRoleSelect({
<div
className={`flex h-10 min-w-25 items-center justify-between gap-3 rounded-component-lg px-4 font-body2 ${triggerStyleMap[role]} cursor-pointer`}
>
<span>{roleLabelMap[role]}</span>
<span>{ROLE_LABEL_MAP[role]}</span>
<ChevonIcon
className={`h-4 w-4 shrink-0 transition-transform ${open ? "rotate-0" : "-rotate-180"}`}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/components/workspace/PermissionTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ export default function PermissionTable() {
</p>
</header>

<div className="overflow-hidden">
<table className="w-full border-collapse table-fixed">
<div className="w-full overflow-hidden">
<table className="w-full table-fixed">
<thead>
<tr className="border-b border-gray-100">
<th className="text-text-auth-sub py-2 text-left">
Expand Down
52 changes: 43 additions & 9 deletions src/components/workspace/WorkspaceCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";

import type { TWorkspace } from "@/types/workspace/workspace";
import { ROLE_LABEL_MAP } from "@/constants/workspaceRole";

import {
DropdownMenu,
Expand All @@ -14,9 +16,16 @@ import { getImageUrl } from "@/lib/getImageUrl";
type TProps = {
workspace: TWorkspace;
menuItems: TMenuItem[];
isSelected?: boolean;
onClick?: () => void;
};

export default function WorkspaceCard({ workspace: w, menuItems }: TProps) {
function WorkspaceCard({
workspace: w,
menuItems,
isSelected = false,
onClick,
}: TProps) {
const [imageError, setImageError] = useState(false);

useEffect(() => {
Expand All @@ -25,10 +34,19 @@ export default function WorkspaceCard({ workspace: w, menuItems }: TProps) {

const imageSrc = w.logoUrl ? getImageUrl(w.logoUrl) : null;
const showPlaceholder = !imageSrc || imageError;

return (
<li className="flex items-center justify-between rounded-component-md bg-white px-6 py-5 shadow-Soft border border-gray-100">
<div className="flex items-center gap-5 min-w-0">
<div className="w-20 h-20 bg-gray-100 shrink-0 rounded-component-sm">
<li
className={twMerge(
"flex items-center justify-between rounded-component-md bg-white px-6 py-5 shadow-Soft border tablet:px-4 tablet:py-4",
onClick &&
"cursor-pointer hover:bg-gray-50 active:scale-93 duration-50 transition-transform",
isSelected ? "border-chart-3/80 bg-chart-3/2" : "border-gray-100",
)}
onClick={onClick}
>
<div className="flex items-center gap-5 min-w-0 tablet:gap-3">
<div className="w-24 h-24 bg-gray-100 shrink-0 rounded-component-sm tablet:h-16 tablet:w-16">
{showPlaceholder ? (
<div className="w-full h-full flex items-center justify-center">
<BuildingIcon className="w-8 h-8 text-text-placeholder" />
Expand All @@ -46,19 +64,33 @@ export default function WorkspaceCard({ workspace: w, menuItems }: TProps) {
</div>

<div className="min-w-0">
<div className="font-heading4 font-semibold! text-text-main truncate">
{w.name}
<div className="flex items-center gap-2">
<div className="font-heading4 font-semibold! text-text-main truncate">
{w.name}
</div>
{isSelected && (
<span
role="status"
className="shrink-0 rounded-full bg-chart-3/12 px-2 py-1 font-caption text-chart-3"
>
현재 대시보드 기준
</span>
)}
</div>

<div className="font-body2 text-text-main mt-1 truncate">
{w.description ?? ""}
</div>
<div className="font-body1 text-text-sub mt-2">
{w.myRole ?? "내 직책 및 역할"}
{ROLE_LABEL_MAP[w.myRole]}
</div>
</div>
</div>

<div className="flex items-center gap-4 shrink-0">
<div
className="flex items-center gap-4 shrink-0"
onClick={(e) => e.stopPropagation()}
>
<DropdownMenu
trigger={<VectorIcon aria-hidden="true" />}
aria-label={`${w.name} 워크스페이스 메뉴`}
Expand All @@ -69,3 +101,5 @@ export default function WorkspaceCard({ workspace: w, menuItems }: TProps) {
</li>
);
}

export default React.memo(WorkspaceCard);
6 changes: 6 additions & 0 deletions src/constants/workspaceRole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { TMemberRole } from "@/types/workspace/workspace";

export const ROLE_LABEL_MAP: Record<TMemberRole, string> = {
ADMIN: "관리자",
MEMBER: "멤버",
};
28 changes: 21 additions & 7 deletions src/pages/workspace/MemberManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "@/types/workspace/workspace";

import ControlBox from "@/components/common/controlbox/ControlBox";
import PageHeader from "@/components/common/PageHeader";
import DeleteMemberModal from "@/components/workspace/DeleteMemberModal";
import MemberList from "@/components/workspace/MemberList";
import PermissionTable from "@/components/workspace/PermissionTable";
Expand Down Expand Up @@ -258,6 +259,7 @@ export default function MemberManagement() {
</section>
);
}

if (memberCountQuery.isLoading || membersQuery.isLoading) {
return (
<section className="w-full min-w-0">
Expand All @@ -270,6 +272,7 @@ export default function MemberManagement() {
</section>
);
}

if (memberCountQuery.isError || membersQuery.isError) {
return (
<section className="w-full min-w-0">
Expand All @@ -282,14 +285,25 @@ export default function MemberManagement() {
</section>
);
}

return (
<section className="w-full min-w-0">
<header className="mb-7">
<h1 className="font-heading2">멤버 관리</h1>
<p className="font-body1 text-text-sub">
팀 구성원을 효율적으로 관리하세요
</p>
</header>
<section className="w-full min-w-0 flex flex-col gap-8">
<div className="flex flex-col gap-3">
<button
type="button"
onClick={() => {
void navigate(-1);
}}
className="inline-flex w-fit items-center gap-1 text-text-sub transition-colors hover:text-text-main"
>
<span aria-hidden="true">←</span>
<span className="font-body2">뒤로 이동</span>
</button>
<PageHeader
title="멤버 관리"
description="팀 멤버를 효율적으로 관리하세요"
/>
</div>

<div className="flex w-full min-w-0 flex-col gap-10">
<MemberList
Expand Down
Loading
Loading