diff --git a/src/components/common/PageHeader.tsx b/src/components/common/PageHeader.tsx index 6acac19b..27fdb560 100644 --- a/src/components/common/PageHeader.tsx +++ b/src/components/common/PageHeader.tsx @@ -16,7 +16,12 @@ const PageHeader = memo(function PageHeader({ className, }: IPageHeaderProps) { return ( -
+

{title}

{description && ( diff --git a/src/components/common/controlbox/ControlBox.tsx b/src/components/common/controlbox/ControlBox.tsx index 6c9c49ae..33a40651 100644 --- a/src/components/common/controlbox/ControlBox.tsx +++ b/src/components/common/controlbox/ControlBox.tsx @@ -42,8 +42,8 @@ export default function ControlBox({ return (
diff --git a/src/components/common/dropdownmenu/DropdownMenu.tsx b/src/components/common/dropdownmenu/DropdownMenu.tsx index 7fbe7ee9..896adf9e 100644 --- a/src/components/common/dropdownmenu/DropdownMenu.tsx +++ b/src/components/common/dropdownmenu/DropdownMenu.tsx @@ -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 ? ( diff --git a/src/components/common/input/Input.tsx b/src/components/common/input/Input.tsx index cd0e449a..4d7f8bb5 100644 --- a/src/components/common/input/Input.tsx +++ b/src/components/common/input/Input.tsx @@ -66,7 +66,7 @@ const Input = forwardRef( -
+
  • +
    {member.profileImageUrl ? ( )}
    -
    +

    {member.name}

    -
    +

    {member.email}

    @@ -45,7 +45,7 @@ export default function MemberItem({
    {member.isMe ? ( -
    +

    팀 구성원 diff --git a/src/components/workspace/MemberRoleSelect.tsx b/src/components/workspace/MemberRoleSelect.tsx index 3f96787a..71b0e59f 100644 --- a/src/components/workspace/MemberRoleSelect.tsx +++ b/src/components/workspace/MemberRoleSelect.tsx @@ -1,4 +1,5 @@ import type { TMemberRole } from "@/types/workspace/workspace"; +import { ROLE_LABEL_MAP } from "@/constants/workspaceRole"; import { DropdownMenu, @@ -13,11 +14,6 @@ type TProps = { disabled?: boolean; }; -const roleLabelMap: Record = { - ADMIN: "관리자", - MEMBER: "멤버", -}; - const triggerStyleMap: Record = { ADMIN: "bg-status-blue/80 text-white shadow-sm", MEMBER: "bg-chart-3/15 text-text-auth-sub", @@ -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", }, @@ -51,7 +47,7 @@ export default function MemberRoleSelect({
    - {roleLabelMap[role]} + {ROLE_LABEL_MAP[role]} diff --git a/src/components/workspace/PermissionTable.tsx b/src/components/workspace/PermissionTable.tsx index a0e8386e..41b0afd3 100644 --- a/src/components/workspace/PermissionTable.tsx +++ b/src/components/workspace/PermissionTable.tsx @@ -120,8 +120,8 @@ export default function PermissionTable() {

    -
    - +
    +
    diff --git a/src/components/workspace/WorkspaceCard.tsx b/src/components/workspace/WorkspaceCard.tsx index e84b6231..f13d8854 100644 --- a/src/components/workspace/WorkspaceCard.tsx +++ b/src/components/workspace/WorkspaceCard.tsx @@ -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, @@ -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(() => { @@ -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 ( -
  • -
    -
    +
  • +
    +
    {showPlaceholder ? (
    @@ -46,19 +64,33 @@ export default function WorkspaceCard({ workspace: w, menuItems }: TProps) {
    -
    - {w.name} +
    +
    + {w.name} +
    + {isSelected && ( + + 현재 대시보드 기준 + + )}
    +
    {w.description ?? ""}
    - {w.myRole ?? "내 직책 및 역할"} + {ROLE_LABEL_MAP[w.myRole]}
    -
    +
    e.stopPropagation()} + >
  • ); } + +export default React.memo(WorkspaceCard); diff --git a/src/constants/workspaceRole.ts b/src/constants/workspaceRole.ts new file mode 100644 index 00000000..328357fe --- /dev/null +++ b/src/constants/workspaceRole.ts @@ -0,0 +1,6 @@ +import type { TMemberRole } from "@/types/workspace/workspace"; + +export const ROLE_LABEL_MAP: Record = { + ADMIN: "관리자", + MEMBER: "멤버", +}; diff --git a/src/pages/workspace/MemberManagement.tsx b/src/pages/workspace/MemberManagement.tsx index f9960a8d..d511f082 100644 --- a/src/pages/workspace/MemberManagement.tsx +++ b/src/pages/workspace/MemberManagement.tsx @@ -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"; @@ -258,6 +259,7 @@ export default function MemberManagement() { ); } + if (memberCountQuery.isLoading || membersQuery.isLoading) { return (
    @@ -270,6 +272,7 @@ export default function MemberManagement() {
    ); } + if (memberCountQuery.isError || membersQuery.isError) { return (
    @@ -282,14 +285,25 @@ export default function MemberManagement() {
    ); } + return ( -
    -
    -

    멤버 관리

    -

    - 팀 구성원을 효율적으로 관리하세요 -

    -
    +
    +
    + + +
    s.selectedOrgId); const filtered = useMemo(() => { const q = query.trim().toLowerCase(); @@ -93,6 +95,14 @@ export default function WorkspacePage() { return workspaces.filter((w) => w.name.toLowerCase().includes(q)); }, [query, workspaces]); + const sortedWorkspaces = useMemo(() => { + return [...filtered].sort((a, b) => { + if (a.orgId === selectedOrgId) return -1; + if (b.orgId === selectedOrgId) return 1; + return 0; + }); + }, [filtered, selectedOrgId]); + const menuItems = (id: TWorkspace["orgId"]): TMenuItem[] => [ { icon: , @@ -164,7 +174,7 @@ export default function WorkspacePage() { /> ); } - if (filtered.length === 0) { + if (sortedWorkspaces.length === 0) { return (
      @@ -173,11 +183,15 @@ export default function WorkspacePage() { } return (
        - {filtered.map((w) => ( + {sortedWorkspaces.map((w) => ( { + void navigate(`/workspace/${w.orgId}/settings`); + }} /> ))}
      @@ -190,11 +204,11 @@ export default function WorkspacePage() { title="워크스페이스 관리" description="워크스페이스 정보를 확인하고 관리하세요." /> -
      -
      +
      +
      setQuery(e.target.value)} rightElement={} @@ -205,7 +219,7 @@ export default function WorkspacePage() { onClick={onOpenCreate} size="big" variant="primary" - className="bg-chart-3 flex shrink-0 whitespace-nowrap items-center justify-center gap-2" + className="bg-chart-3 flex shrink-0 whitespace-nowrap items-center justify-center gap-2 tablet:w-full tablet:justify-center" > 워크스페이스 생성하기 @@ -215,17 +229,26 @@ export default function WorkspacePage() { {renderWorkspaceContent()} - -
      + +

      워크스페이스 생성

      -

      +

      워크스페이스를 생성한 사용자는 자동으로 관리자 권한을 갖습니다.{" "}
      로고 이미지와 기본 정보를 입력해 주세요.

      -
      -
      +
      +
      +
      + 로고 이미지 +
      -
      -
      로고 이미지
      - -
      - -
      - setNewName(e.target.value)} - disabled={isCreating} - /> - setNewDesc(e.target.value)} - disabled={isCreating} - /> - {createErrorMsg && ( -

      {createErrorMsg}

      - )} + +
      +
      + setNewName(e.target.value)} + disabled={isCreating} + /> + setNewDesc(e.target.value)} + disabled={isCreating} + /> + {createErrorMsg && ( +

      {createErrorMsg}

      + )} +
      +
      +
      + +
      {loading && (
      @@ -201,9 +216,9 @@ export default function WorkspaceSetting() {

      워크스페이스의 대표적인 정보를 설정합니다.

      -
      -
      -
      +
      +
      +
      로고 이미지
      -
      +
      {logoPreview ? ( {"새 ) : resolvedLogoUrl ? ( {`${name { - console.log("이미지 로드 실패:", e.currentTarget.src); + className="w-full h-full object-cover rounded-component-sm" + onError={() => { setImageError(true); }} /> ) : (
      -
      +
      -
      +
      setDesc(e.target.value)} minRows={4} - className="min-h-93 tablet:min-h-55" + className="min-h-90" disabled={saving || deleting || uploading} />
      +
      + + +
      -
      - -
      +