spin-frontend/src/types.ts
theorose49 09713f5e23
All checks were successful
build-and-push / build (push) Successful in 32s
feat: 대시보드 '내 이슈' 보드(프로젝트별 색) + 캘린더 페이지 + 메일 AI요약 표시
- 대시보드: 내게 배정된 작업을 상태별 JIRA 보드로, 프로젝트별 색상 카드(/my/tasks)
- 캘린더 페이지(/calendar): 월 그리드, 분류(프로젝트/기타/개인)별 색 일정 추가·수정·삭제
- 메일 리스트 메모칸 위에 AI 자동 요약 표시
- projectColor() 헬퍼, 사이드바 CalendarDays 아이콘, 라우트

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

493 lines
9.9 KiB
TypeScript

// Types mirror the Go models (camelCase JSON). Kept in one file for clarity.
export interface User {
id: string;
name: string;
email: string;
role: string;
groups?: string[];
isSuperAdmin: boolean;
}
export type Rank = "인턴" | "주임" | "선임" | "책임" | "파트너";
export interface Member {
id: string;
email: string;
displayName: string;
rank: Rank | "";
departmentId?: string | null;
role: "admin" | "user";
isPartner: boolean;
phone: string;
position: string;
status: string;
joinDate?: string | null;
annualLeave: number;
avatarKey: string;
createdAt: string;
updatedAt: string;
}
export interface Notification {
id: string;
recipient: string;
type: "project" | "leave" | "overtime" | "incentive" | "settlement";
title: string;
body: string;
link: string;
read: boolean;
createdAt: string;
}
export type WorkStatusKind = "in" | "out" | "break" | "meeting" | "move";
export interface WorkStatusEvent {
id: string;
memberEmail: string;
date: string;
status: WorkStatusKind;
at: string;
note: string;
}
export interface LeaveBalance {
year: number;
granted: number;
used: number;
remaining: number;
}
export interface Me {
user: User;
member: Member | null;
isAdmin: boolean;
logoutUrl: string;
}
export interface NavItem {
key: string;
label: string;
path: string;
icon: string;
adminOnly: boolean;
section: string;
}
export interface Department {
id: string;
name: string;
leadEmail: string;
}
export interface AuditLog {
id: string;
actor: string;
action: string;
entity: string;
entityId: string;
detail: string;
createdAt: string;
}
/* ---- attendance ---- */
export type ReqStatus = "pending" | "approved" | "rejected" | "canceled";
export interface Attendance {
id: string;
memberEmail: string;
date: string;
clockIn?: string | null;
clockOut?: string | null;
workMinutes: number;
source: string;
note: string;
}
export type LeaveType =
| "annual" | "half_am" | "half_pm" | "public" | "sick" | "family" | "unpaid";
export interface LeaveRequest {
id: string;
memberEmail: string;
type: LeaveType;
startDate: string;
endDate: string;
days: number;
reason: string;
status: ReqStatus;
approver: string;
decidedAt?: string | null;
decisionMemo: string;
createdAt: string;
}
export interface OvertimeRequest {
id: string;
memberEmail: string;
date: string;
minutes: number;
reason: string;
status: ReqStatus;
approver: string;
decidedAt?: string | null;
decisionMemo: string;
createdAt: string;
}
export interface WorkPolicy {
id: string;
name: string;
weeklyHours: number;
dailyStandardMin: number;
coreStart: string;
coreEnd: string;
lunchMinutes: number;
annualLeaveBase: number;
active: boolean;
}
export interface Timesheet {
year: number;
month: number;
businessDays: number;
standardMinutes: number;
workedMinutes: number;
leaveMinutes: number;
overtimeMinutes: number;
recognizedTotal: number;
fulfillmentPct: number;
daysPresent: number;
}
export interface ApprovalQueue {
leave: LeaveRequest[];
overtime: OvertimeRequest[];
}
/* ---- projects ---- */
export interface Company { id: string; name: string; code: string; note: string; }
export interface Product { id: string; companyId: string; name: string; code: string; }
export interface Version { id: string; productId: string; label: string; }
export type ProjectStatus = "planned" | "active" | "hold" | "done" | "dropped";
export interface Project {
id: string;
name: string;
companyId: string;
productId: string;
versionId: string;
companyName: string;
productName: string;
versionName: string;
consultingType: string;
country: string;
scopeText: string; // 글 계약 범위
scopeGraphic: string; // 그림 계약 범위
pmEmail: string;
clientDomain: string; // 고객사 메일 도메인(postfix)
cautions: string;
status: ProjectStatus;
startDate: string;
dueDate: string;
createdAt: string;
updatedAt: string;
}
export interface ProjectMail {
id: string;
messageId: string;
threadId: string;
from: string;
to: string;
cc: string;
subject: string;
date: string;
snippet: string;
summary: string; // AI 자동 요약
mailbox: string;
ts: number;
hidden: boolean;
hiddenBy: string;
note: string; // 공동 메모 본문 (인라인 표시)
noteEditedBy: string; // 메모 마지막 수정자
threadIndex: number; // 스레드 내 순번 (1=원문)
threadCount: number; // 스레드 총 메일 수
}
export interface ProjectMailsResponse {
enabled: boolean;
domain: string;
messages: ProjectMail[];
error?: string;
lastSyncedAt?: string;
syncing?: boolean;
}
export interface MailNote {
id: string;
projectId: string;
messageId: string;
body: string;
lastEditedBy: string;
createdAt: string;
updatedAt: string;
}
export interface MailAttachment {
filename: string;
mimeType: string;
size: number;
attachmentId: string;
}
export interface MailFull {
gmailId: string;
body: string;
isHtml: boolean;
attachments: MailAttachment[];
}
export interface ProjectMember {
id: string;
projectId: string;
memberEmail: string;
portion: number;
role: string;
}
export interface ClientContact {
id: string;
projectId: string;
name: string;
title: string;
phone: string;
email: string;
}
export type Lane = "todo" | "doing" | "review" | "done";
// 전 구성원 공개 최소 디렉터리 (이메일 → 이름 표시용)
export interface DirectoryEntry {
id: string;
email: string;
displayName: string;
avatarKey: string;
}
export type TaskPriority = "low" | "medium" | "high" | "urgent";
export interface ProjectTask {
id: string;
projectId: string;
title: string;
description: string;
lane: Lane;
priority: TaskPriority | "";
labels: string[] | null;
start: string;
end: string;
assignee: string;
orderIdx: number;
progress: number;
dependsOn: string[] | null;
color: string;
}
export interface MyTask extends ProjectTask {
projectName: string;
}
export interface CalendarEvent {
id: string;
ownerEmail: string;
title: string;
category: string; // project | etc | personal
projectId: string;
color: string;
start: string; // YYYY-MM-DD
end: string;
allDay: boolean;
memo: string;
createdAt: string;
updatedAt: string;
}
export interface TaskComment {
id: string;
taskId: string;
authorEmail: string;
body: string;
createdAt: string;
}
export interface Contract {
id: string;
projectId: string;
totalAmount: number;
beAmount: number;
adminCaution: string;
memo: string;
}
export interface ContractFile {
id: string;
projectId: string;
kind: string;
filename: string;
s3Key: string;
size: number;
uploadedBy: string;
createdAt: string;
}
export interface PaymentSplit {
id: string;
projectId: string;
label: string;
amount: number;
expectedDate: string;
paidDate: string;
paid: boolean;
memo: string;
orderIdx: number;
}
/* ---- incentive ---- */
export type StageKind = "deposit" | "middle" | "final";
export type StageScope = "be" | "non_be";
export type FixStatus = "planned" | "applying" | "applied" | "paid";
export interface IncentiveConfig {
id: string;
year: number;
pointRate: number;
depositPct: number;
middlePct: number;
finalPct: number;
nonBeCompanyPct: number;
nonBePartnerPct: number;
rankQuota: Record<string, number>;
frozen: boolean;
}
export interface PaymentStage {
id: string;
projectId: string;
kind: StageKind;
scope: StageScope;
amount: number;
pct: number;
expectedDate: string;
fixedDate: string;
status: FixStatus;
}
export interface UserIncentive {
id: string;
projectId: string;
memberEmail: string;
stageId: string;
kind: StageKind;
scope: StageScope;
year: number;
quarter: number;
portion: number;
amount: number;
points: number;
fixStatus: FixStatus;
override: boolean;
memo: string;
appliedAt?: string | null;
paidAt?: string | null;
}
export interface MyIncentive {
year: number;
rank: string;
quota: number;
pointsTotal: number;
pointsApplied: number;
excessPoints: number;
pointRate: number;
estPayout: number;
items: UserIncentive[];
byProject: Record<string, number>;
}
export interface Settlement {
id: string;
memberEmail: string;
year: number;
quarter: number;
rank: string;
quota: number;
pointsCumul: number;
excessPoints: number;
paidPointsYtd: number;
payoutPoints: number;
payoutAmount: number;
fixed: boolean;
fixedAt?: string | null;
}
export interface SimResult {
stages: { kind: StageKind; scope: StageScope; amount: number; pct: number }[];
allocs: { email: string; kind: StageKind; scope: StageScope; portion: number; amount: number; points: number }[];
byMember: Record<string, number>;
}
/* ---- accounting ---- */
export type TxnKind = "income" | "expense" | "tax" | "payroll" | "incentive";
export interface Account { id: string; code: string; name: string; type: string; }
export interface Transaction {
id: string;
date: string;
kind: TxnKind;
accountId?: string | null;
amount: number;
projectId?: string | null;
memberEmail?: string | null;
counterparty: string;
memo: string;
createdBy: string;
}
export interface TaxRecord {
id: string;
period: string;
type: string;
base: number;
amount: number;
dueDate: string;
paid: boolean;
memo: string;
}
export interface AcctSummary {
year: number;
cashIn: number;
cashOut: number;
net: number;
incentiveApplied: number;
incentivePaid: number;
gap: number;
monthly: { month: string; income: number; expense: number; net: number }[];
byKind: Record<string, number>;
}
export interface Dashboard {
isAdmin: boolean;
year: number;
myProjects: number;
myPoints: number;
myPendingRequests: number;
pendingApprovals?: number;
activeProjects?: number;
cashIn?: number;
cashOut?: number;
cashNet?: number;
upcomingPayments?: PaymentSplit[];
}