Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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
50 changes: 50 additions & 0 deletions src/api/workspace/org.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { toWorkspace, toWorkspaceDetail } from "@/types/workspace/mapper";
import type {
TApiResult,
TCreateOrgRequest,
TCreateOrgResponse,
TGetOrgResponse,
TMyOrgsData,
TUpdateWorkspaceRequest,
TWorkspace,
TWorkspaceDetail,
} from "@/types/workspace/workspace";

import { axiosInstance } from "@/lib/axiosInstance";

export const getMyWorkspaces = async (): Promise<TWorkspace[]> => {
const { data } =
await axiosInstance.get<TApiResult<TMyOrgsData>>("/api/org/my");
return data.data.organizations.map(toWorkspace);
};

export const createWorkspace = async (
body: TCreateOrgRequest,
): Promise<TCreateOrgResponse> => {
const { data } = await axiosInstance.post<TApiResult<TCreateOrgResponse>>(
"/api/org/create",
body,
);
return data.data;
};

export const getWorkspace = async (
orgId: number,
): Promise<TWorkspaceDetail> => {
const { data } = await axiosInstance.get<TApiResult<TGetOrgResponse>>(
`/api/org/${orgId}`,
);

return toWorkspaceDetail(data.data);
};

export const updateWorkspace = async (
orgId: number,
body: TUpdateWorkspaceRequest,
): Promise<void> => {
await axiosInstance.patch<TApiResult<string>>(`/api/org/${orgId}`, body);
};

export const deleteWorkspace = async (orgId: number): Promise<void> => {
await axiosInstance.delete<TApiResult<string>>(`/api/org/${orgId}`);
};
150 changes: 95 additions & 55 deletions src/pages/workspace/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Modal from "@/components/common/modal/Modal";
import TextareaField from "@/components/common/textarea/TextareaField";
import WorkspaceCard from "@/components/workspace/WorkspaceCard";

import { createWorkspace, getMyWorkspaces } from "@/api/workspace/org";
import EditContainIcon from "@/assets/icon/workspace/edit-contained.svg?react";
import PlusIcon from "@/assets/icon/workspace/plus.svg?react";
import SearchIcon from "@/assets/icon/workspace/search.svg?react";
Expand All @@ -24,37 +25,22 @@ export default function WorkspacePage() {

const [newName, setNewName] = useState("");
const [newDesc, setNewDesc] = useState("");

const [workspaces, setWorkspaces] = useState<TWorkspace[]>([]);
const [loading, setLoading] = useState(false);
const [listErrorMsg, setListErrorMsg] = useState<string | null>(null);
const [creating, setCreating] = useState(false);
const [createErrorMsg, setCreateErrorMsg] = useState<string | null>(null);

Comment thread
jjjsun marked this conversation as resolved.
Resolved
const fileRef = useRef<HTMLInputElement | null>(null);
const openFile = () => {
if (!fileRef.current) return;
fileRef.current.value = "";
fileRef.current.click();
};
const [logoFile, setLogoFile] = useState<File | null>(null);

const [logoPreview, setLogoPreview] = useState<string | null>(null);
Comment thread
jjjsun marked this conversation as resolved.
Resolved
const workspaces: TWorkspace[] = useMemo(
() => [
{
id: "1",
name: "광고회사1",
description: "광고회사1입니다.",
myRole: "admin",
},
{
id: "2",
name: "광고회사2",
description: "광고회사2입니다.",
myRole: "admin",
},
{
id: "3",
name: "광고회사3",
description: "광고회사3입니다.",
myRole: "admin",
},
],
[],
);

const filtered = useMemo(() => {
const q = query.trim().toLowerCase();
if (!q) return workspaces;
Expand All @@ -78,36 +64,68 @@ export default function WorkspacePage() {
const onOpenCreate = () => {
setNewName("");
setNewDesc("");
setLogoPreview(null);
setCreateErrorMsg(null);
setCreateOpen(true);
};

const onPickFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setLogoFile(file);
setLogoPreview(URL.createObjectURL(file));
};

// TODO: 모달 닫힐때 preview URL 해제필요
useEffect(() => {
return () => {
if (logoPreview) URL.revokeObjectURL(logoPreview);
};
}, [logoPreview]);

// TODO: API 연동 후에 생성 동작 연결하기
const onSubmitCreate = () => {
const form = new FormData();
form.append("name", newName.trim());
form.append("description", newDesc.trim());
if (logoFile) form.append("logo", logoFile);

console.log("create payload", {
name: newName.trim(),
description: newDesc.trim(),
logoFile,
});
alert("API 연동 후에 생성 기능 연결예정");
const fetchWorkspaces = async () => {
setLoading(true);
setListErrorMsg(null);
try {
const list = await getMyWorkspaces();
setWorkspaces(list);
} catch (e) {
const message =
e instanceof Error
? e.message
: "워크스페이스 목록 조회중 오류가 발생했습니다";
setListErrorMsg(message);
setWorkspaces([]);
} finally {
setLoading(false);
}
};

useEffect(() => {
void fetchWorkspaces();
}, []);

const onSubmitCreate = async () => {
const name = newName.trim();
const description = newDesc.trim();
if (!name) return;
setCreating(true);
setCreateErrorMsg(null);
try {
await createWorkspace({
name,
description,
logoUrl: null,
});
setCreateOpen(false);
await fetchWorkspaces();
} catch (e) {
let message = "워크스페이스 생성 중 오류가 발생했습니다";
if (e instanceof Error) {
message = e.message;
}
setCreateErrorMsg(message);
} finally {
setCreating(false);
}
};

return (
Expand Down Expand Up @@ -139,20 +157,39 @@ export default function WorkspacePage() {
워크스페이스 생성하기
</Button>
</div>
<ul className="space-y-5">
{filtered.map((w) => (
<WorkspaceCard
key={String(w.id)}
workspace={w}
menuItems={menuItems(w.id)}
/>
))}
{filtered.length === 0 && (
<li className="rounded-component-lg bg-white p-10 text-center border border-gray-100">
<p className="font-body2 text-text-sub">워크스페이스가 없습니다.</p>
</li>
)}
</ul>

{loading && (
<div className="bg-white p-10 text-center border border-gray-100 rounded-component-lg">
<p className="font-body2 text-text-sub">불러오는중..</p>
</div>
)}
{!loading && listErrorMsg && (
<div className="bg-white p-10 text-center border border-gray-100 rounded-component-lg space-y-4">
<p className="font-body2 text-status-red">{listErrorMsg}</p>
<Button type="button" variant="primary" onClick={fetchWorkspaces}>
다시 시도
</Button>
</div>
)}
{!loading && !listErrorMsg && (
<ul className="space-y-5">
{filtered.map((w) => (
<WorkspaceCard
key={String(w.id)}
workspace={w}
menuItems={menuItems(w.id)}
/>
))}
{filtered.length === 0 && (
<li className="rounded-component-lg bg-white p-10 text-center border border-gray-100">
<p className="font-body2 text-text-sub">
워크스페이스가 없습니다.
</p>
</li>
)}
</ul>
)}

<Modal
isOpen={createOpen}
onClose={() => setCreateOpen(false)}
Expand Down Expand Up @@ -212,15 +249,18 @@ export default function WorkspacePage() {
value={newDesc}
onChange={(e) => setNewDesc(e.target.value)}
/>
{createErrorMsg && (
<p className="font-body2 text-status-red">{createErrorMsg}</p>
)}
<Button
size="big"
variant="primary"
onClick={onSubmitCreate}
disabled={!newName.trim()}
disabled={!newName.trim() || creating}
className="mx-auto px-10 mt-10"
type="button"
>
생성하기
{creating ? "생성 중.. " : "생성하기"}
</Button>
</div>
</div>
Expand Down
Loading