theorose49 65bcb69374
All checks were successful
build-and-push / build (push) Successful in 31s
feat: 메일함·프로필 사진·근무상태 드롭다운·접이식 사이드바 + #03143F 팔레트 + 인센티브 게이지
- 브랜드 포인트컬러 #03143F로 팔레트 전면 재설정, 회사 로고 흰 wrap 제거+크롭, 로고 블렌딩
- 사이드바 접기/펼치기(localStorage), 로고 아래 근무상태 드롭다운(출근/퇴근/휴식/미팅/이동)
- 대시보드 역할 무관 동일(회계/전사 위젯 제거)
- 유저 근무화면 단순화(남은연차 소수점·기록·휴가/공가만), 관리자 근무관리(/admin/attendance)
- 프로젝트: 관리자 전용 관리창(/admin/projects), 나의 업무는 본인 참여분 read-only
- 메일함(/inbox)+탑바 벨(미확인), 프로필(부서·연락처·사진 업로드)
- 인센티브 유저: BE/non-BE·환율 숨김, 할당량 세그먼트 게이지(지급완료→반영완료→반영중→예정, 할당량 화살표)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 09:38:51 +09:00

201 lines
12 KiB
TypeScript

import axios from "axios";
import type {
Account, AcctSummary, ApprovalQueue, Attendance, AuditLog, ClientContact,
Company, Contract, ContractFile, Dashboard, Department, IncentiveConfig,
LeaveBalance, LeaveRequest, Me, Member, MyIncentive, NavItem, Notification,
OvertimeRequest, PaymentSplit, PaymentStage, Product, Project, ProjectMember,
ProjectTask, Settlement, SimResult, TaxRecord, Timesheet, Transaction,
UserIncentive, Version, WorkPolicy, WorkStatusEvent, WorkStatusKind,
} from "@/types";
export const api = axios.create({
baseURL: "/api",
headers: { "Content-Type": "application/json" },
});
// In dev, ?as=user can be appended to simulate a non-admin (see backend auth).
const asParam = new URLSearchParams(window.location.search).get("as");
if (asParam === "user") api.defaults.params = { as: "user" };
/* ---- identity / nav ---- */
export const getMe = () => api.get<Me>("/me").then((r) => r.data);
export const getNav = () => api.get<NavItem[]>("/me/nav").then((r) => r.data);
export const getDashboard = () => api.get<Dashboard>("/dashboard").then((r) => r.data);
/* ---- inbox / notifications ---- */
export const getNotifications = (unread?: boolean) =>
api.get<Notification[]>("/notifications", { params: { unread } }).then((r) => r.data);
export const getUnreadCount = () =>
api.get<{ count: number }>("/notifications/unread-count").then((r) => r.data.count);
export const markNotificationRead = (id: string) =>
api.post(`/notifications/${id}/read`).then((r) => r.data);
export const markAllNotificationsRead = () =>
api.post("/notifications/read-all").then((r) => r.data);
/* ---- profile photo ---- */
export const uploadAvatar = (file: File) => {
const form = new FormData();
form.append("file", file);
return api.post<Member>("/me/avatar", form, { headers: { "Content-Type": "multipart/form-data" } }).then((r) => r.data);
};
// Avatar image URL (cache-busted by avatarKey). Empty avatarKey → no image.
export const avatarUrl = (memberId?: string, avatarKey?: string) =>
memberId && avatarKey ? `/api/members/${memberId}/avatar?v=${encodeURIComponent(avatarKey)}` : "";
/* ---- members / org ---- */
export const getMembers = () => api.get<Member[]>("/members").then((r) => r.data);
export const getMember = (id: string) => api.get<Member>(`/members/${id}`).then((r) => r.data);
export const createMember = (b: Partial<Member>) => api.post<Member>("/members", b).then((r) => r.data);
export const updateMember = (id: string, b: Partial<Member>) =>
api.patch<Member>(`/members/${id}`, b).then((r) => r.data);
export const deleteMember = (id: string) => api.delete(`/members/${id}`).then((r) => r.data);
export const getDepartments = () => api.get<Department[]>("/departments").then((r) => r.data);
export const createDepartment = (b: Partial<Department>) =>
api.post<Department>("/departments", b).then((r) => r.data);
export const updateDepartment = (id: string, b: Partial<Department>) =>
api.patch<Department>(`/departments/${id}`, b).then((r) => r.data);
export const deleteDepartment = (id: string) => api.delete(`/departments/${id}`).then((r) => r.data);
export const getAudit = () => api.get<AuditLog[]>("/audit").then((r) => r.data);
/* ---- attendance ---- */
export const getAttendance = (params: { month?: string; email?: string }) =>
api.get<Attendance[]>("/attendance", { params }).then((r) => r.data);
export const punch = () => api.post<Attendance>("/attendance/punch").then((r) => r.data);
export const setWorkStatus = (status: WorkStatusKind, note?: string) =>
api.post("/attendance/status", { status, note }).then((r) => r.data);
export const getWorkStatusLog = (params?: { email?: string; date?: string }) =>
api.get<WorkStatusEvent[]>("/attendance/status", { params }).then((r) => r.data);
export const getLeaveBalance = (params?: { year?: number; email?: string }) =>
api.get<LeaveBalance>("/leave/balance", { params }).then((r) => r.data);
export const getTimesheet = (params: { year?: number; month?: number; email?: string }) =>
api.get<Timesheet>("/attendance/timesheet", { params }).then((r) => r.data);
export const getLeave = (params?: { status?: string; email?: string }) =>
api.get<LeaveRequest[]>("/leave", { params }).then((r) => r.data);
export const createLeave = (b: Partial<LeaveRequest>) =>
api.post<LeaveRequest>("/leave", b).then((r) => r.data);
export const decideLeave = (id: string, approve: boolean, memo?: string) =>
api.post(`/leave/${id}/decide`, { approve, memo }).then((r) => r.data);
export const cancelLeave = (id: string) => api.post(`/leave/${id}/cancel`).then((r) => r.data);
export const getOvertime = (params?: { email?: string }) =>
api.get<OvertimeRequest[]>("/overtime", { params }).then((r) => r.data);
export const createOvertime = (b: Partial<OvertimeRequest>) =>
api.post<OvertimeRequest>("/overtime", b).then((r) => r.data);
export const decideOvertime = (id: string, approve: boolean, memo?: string) =>
api.post(`/overtime/${id}/decide`, { approve, memo }).then((r) => r.data);
export const getWorkPolicy = () => api.get<WorkPolicy>("/work-policy").then((r) => r.data);
export const putWorkPolicy = (b: Partial<WorkPolicy>) =>
api.put<WorkPolicy>("/work-policy", b).then((r) => r.data);
export const getApprovals = () => api.get<ApprovalQueue>("/approvals").then((r) => r.data);
/* ---- projects ---- */
export const getCompanies = () => api.get<Company[]>("/companies").then((r) => r.data);
export const createCompany = (b: Partial<Company>) => api.post<Company>("/companies", b).then((r) => r.data);
export const getProducts = (companyId?: string) =>
api.get<Product[]>("/products", { params: { companyId } }).then((r) => r.data);
export const createProduct = (b: Partial<Product>) => api.post<Product>("/products", b).then((r) => r.data);
export const getVersions = (productId?: string) =>
api.get<Version[]>("/versions", { params: { productId } }).then((r) => r.data);
export const createVersion = (b: Partial<Version>) => api.post<Version>("/versions", b).then((r) => r.data);
export const getProjects = (params?: { companyId?: string; status?: string; scope?: "mine" }) =>
api.get<Project[]>("/projects", { params }).then((r) => r.data);
export const getProject = (id: string) => api.get<Project>(`/projects/${id}`).then((r) => r.data);
export const createProject = (b: Partial<Project>) => api.post<Project>("/projects", b).then((r) => r.data);
export const updateProject = (id: string, b: Partial<Project>) =>
api.patch<Project>(`/projects/${id}`, b).then((r) => r.data);
export const deleteProject = (id: string) => api.delete(`/projects/${id}`).then((r) => r.data);
export const getProjectMembers = (id: string) =>
api.get<ProjectMember[]>(`/projects/${id}/members`).then((r) => r.data);
export const upsertProjectMember = (id: string, b: Partial<ProjectMember>) =>
api.post<ProjectMember>(`/projects/${id}/members`, b).then((r) => r.data);
export const deleteProjectMember = (pmId: string) =>
api.delete(`/project-members/${pmId}`).then((r) => r.data);
export const getContacts = (id: string) =>
api.get<ClientContact[]>(`/projects/${id}/contacts`).then((r) => r.data);
export const upsertContact = (id: string, b: Partial<ClientContact>) =>
api.post<ClientContact>(`/projects/${id}/contacts`, b).then((r) => r.data);
export const deleteContact = (cId: string) => api.delete(`/contacts/${cId}`).then((r) => r.data);
export const getTasks = (id: string) =>
api.get<ProjectTask[]>(`/projects/${id}/tasks`).then((r) => r.data);
export const createTask = (id: string, b: Partial<ProjectTask>) =>
api.post<ProjectTask>(`/projects/${id}/tasks`, b).then((r) => r.data);
export const updateTask = (tId: string, b: Partial<ProjectTask>) =>
api.patch<ProjectTask>(`/tasks/${tId}`, b).then((r) => r.data);
export const deleteTask = (tId: string) => api.delete(`/tasks/${tId}`).then((r) => r.data);
/* ---- contract (admin) ---- */
export const getContract = (id: string) =>
api.get<Contract | null>(`/projects/${id}/contract`).then((r) => r.data);
export const putContract = (id: string, b: Partial<Contract>) =>
api.put<Contract>(`/projects/${id}/contract`, b).then((r) => r.data);
export const getContractFiles = (id: string) =>
api.get<ContractFile[]>(`/projects/${id}/files`).then((r) => r.data);
export const uploadContractFile = (id: string, file: File, kind = "contract") => {
const form = new FormData();
form.append("file", file);
form.append("kind", kind);
return api
.post<ContractFile>(`/projects/${id}/files`, form, { headers: { "Content-Type": "multipart/form-data" } })
.then((r) => r.data);
};
export const getFileDownloadUrl = (fId: string) =>
api.get<{ url: string }>(`/files/${fId}/download`).then((r) => r.data.url);
export const deleteContractFile = (fId: string) => api.delete(`/files/${fId}`).then((r) => r.data);
export const getPayments = (id: string) =>
api.get<PaymentSplit[]>(`/projects/${id}/payments`).then((r) => r.data);
export const createPayment = (id: string, b: Partial<PaymentSplit>) =>
api.post<PaymentSplit>(`/projects/${id}/payments`, b).then((r) => r.data);
export const updatePayment = (payId: string, b: Partial<PaymentSplit>) =>
api.patch<PaymentSplit>(`/payments/${payId}`, b).then((r) => r.data);
export const deletePayment = (payId: string) => api.delete(`/payments/${payId}`).then((r) => r.data);
/* ---- incentive ---- */
export const getIncentiveConfig = (year?: number) =>
api.get<IncentiveConfig>("/incentive/config", { params: { year } }).then((r) => r.data);
export const putIncentiveConfig = (b: Partial<IncentiveConfig>) =>
api.put<IncentiveConfig>("/incentive/config", b).then((r) => r.data);
export const getMyIncentive = (params?: { year?: number; email?: string }) =>
api.get<MyIncentive>("/incentive/me", { params }).then((r) => r.data);
export const getStages = (projectId: string) =>
api.get<PaymentStage[]>("/incentive/stages", { params: { projectId } }).then((r) => r.data);
export const recomputeProject = (id: string, year?: number) =>
api.post(`/incentive/projects/${id}/recompute`, null, { params: { year } }).then((r) => r.data);
export const setStageStatus = (stId: string, status: string, fixedDate?: string) =>
api.post(`/incentive/stages/${stId}/status`, { status, fixedDate }).then((r) => r.data);
export const getUserIncentives = (params: { projectId?: string; email?: string }) =>
api.get<UserIncentive[]>("/incentive/user-incentives", { params }).then((r) => r.data);
export const patchUserIncentive = (uiId: string, b: Partial<UserIncentive>) =>
api.patch<UserIncentive>(`/incentive/user-incentives/${uiId}`, b).then((r) => r.data);
export const getSettlements = (year?: number) =>
api.get<Settlement[]>("/incentive/settlements", { params: { year } }).then((r) => r.data);
export const runSettlement = (year: number, quarter: number) =>
api.post<Settlement[]>("/incentive/settlements/run", { year, quarter }).then((r) => r.data);
export const fixSettlement = (sId: string) =>
api.post<Settlement>(`/incentive/settlements/${sId}/fix`).then((r) => r.data);
export const simulate = (b: {
total: number; be: number;
members: { email: string; portion: number; isPartner: boolean }[];
config?: Partial<IncentiveConfig>;
}) => api.post<SimResult>("/incentive/simulate", b).then((r) => r.data);
/* ---- accounting ---- */
export const getAccounts = () => api.get<Account[]>("/accounts").then((r) => r.data);
export const createAccount = (b: Partial<Account>) => api.post<Account>("/accounts", b).then((r) => r.data);
export const getTransactions = (params?: { kind?: string; projectId?: string; from?: string; to?: string }) =>
api.get<Transaction[]>("/transactions", { params }).then((r) => r.data);
export const createTransaction = (b: Partial<Transaction>) =>
api.post<Transaction>("/transactions", b).then((r) => r.data);
export const updateTransaction = (txId: string, b: Partial<Transaction>) =>
api.patch<Transaction>(`/transactions/${txId}`, b).then((r) => r.data);
export const deleteTransaction = (txId: string) => api.delete(`/transactions/${txId}`).then((r) => r.data);
export const getTaxes = () => api.get<TaxRecord[]>("/taxes").then((r) => r.data);
export const createTax = (b: Partial<TaxRecord>) => api.post<TaxRecord>("/taxes", b).then((r) => r.data);
export const updateTax = (taxId: string, b: Partial<TaxRecord>) =>
api.patch<TaxRecord>(`/taxes/${taxId}`, b).then((r) => r.data);
export const getAccountingSummary = (year?: number) =>
api.get<AcctSummary>("/accounting/summary", { params: { year } }).then((r) => r.data);