File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
import { useMemo } from 'react'
import type { Code } from '../api/client'
import { useCodes } from './useCodes'
/** 원문자 ①(U+2460)부터 ⑳까지. 그 밖은 번호를 그대로 쓴다. */
const CIRCLED_BASE = 0x2460
const CIRCLED_MAX = 20
/**
* 진행단계를 다루는 헬퍼. 단계 개수·라벨·구간·진행상태·KPI 임계값이 전부 코드표에서 온다.
*
* 예전에는 stages.ts에 12개 라벨 배열이 박혀 있어서 단계를 하나 늘리려면 백엔드 4곳,
* 프론트 8곳을 같이 고쳐야 했다. 이제 코드표 한 줄이면 된다.
*/
export interface Stages {
/** 등록된 단계 수. 지금은 13. */
count: number
/** [1, 2, ... count] */
numbers: number[]
/** 화면 표시명. 모르는 번호면 번호를 문자열로 돌려준다. */
label(stage: number): string
/** ①②③… 20을 넘으면 번호 문자열. */
symbol(stage: number): string
/** 단계가 속한 업무구간 key. 미지정(null)이면 null. */
groupKey(stage: number | null): string | null
/** 업무구간 목록(정렬순). 칸반 색과 필터가 쓴다. */
groups: Code[]
/** 업무구간의 tone. 모르면 null. */
groupTone(key: string | null): string | null
/** 진행상태 - 'applied' | 'active' | 'done'. 단계 미지정이면 null. */
progressOf(stage: number | null): string | null
/** 이 KPI에 집계하기 시작하는 단계 번호. 지정이 없으면 null. */
kpiThreshold(kpi: string): number | null
/** 해당 단계가 이 KPI 집계 대상인지(임계값 이상인지). */
countsToward(kpi: string, stage: number | null): boolean
}
export function useStages(): Stages {
const stageCodes = useCodes('STAGE')
const groups = useCodes('STAGE_GROUP')
return useMemo(() => {
const byNumber = new Map<number, Code>()
for (const code of stageCodes) {
byNumber.set(Number(code.code), code)
}
const numbers = stageCodes.map((code) => Number(code.code)).sort((a, b) => a - b)
function attrOf(stage: number | null, key: string): string | null {
if (stage === null) {
return null
}
const value = byNumber.get(stage)?.attrs[key]
return value == null ? null : String(value)
}
function kpiThreshold(kpi: string): number | null {
for (const [number, code] of byNumber) {
if (code.attrs.kpiFrom === kpi) {
return number
}
}
return null
}
return {
count: numbers.length,
numbers,
label: (stage) => byNumber.get(stage)?.label ?? String(stage),
symbol: (stage) =>
stage >= 1 && stage <= CIRCLED_MAX
? String.fromCharCode(CIRCLED_BASE + stage - 1)
: String(stage),
groupKey: (stage) => attrOf(stage, 'groupKey'),
groups,
groupTone: (key) => {
const found = groups.find((group) => group.code === key)
const tone = found?.attrs.tone
return tone == null ? null : String(tone)
},
progressOf: (stage) => attrOf(stage, 'progress'),
kpiThreshold,
countsToward: (kpi, stage) => {
const from = kpiThreshold(kpi)
return from !== null && stage !== null && stage >= from
},
}
}, [stageCodes, groups])
}