116 lines
3.3 KiB
TypeScript
116 lines
3.3 KiB
TypeScript
export type ItemType = 'E' | 'F' | 'U' | 'T' | 'B'
|
|
|
|
export interface HierarchyInfo {
|
|
fullPath: string
|
|
items: { type: ItemType; number: number }[]
|
|
epicCode: string | null
|
|
featureCode: string | null
|
|
itemCode: string | null
|
|
}
|
|
|
|
const TYPE_LABELS: Record<ItemType, string> = {
|
|
E: 'Épica',
|
|
F: 'Feature',
|
|
U: 'HU',
|
|
T: 'Tarea',
|
|
B: 'Bug',
|
|
}
|
|
|
|
const TYPE_COLORS: Record<ItemType, string> = {
|
|
E: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400',
|
|
F: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
|
|
U: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400',
|
|
T: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400',
|
|
B: 'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-400',
|
|
}
|
|
|
|
const TYPE_ICONS: Record<ItemType, string> = {
|
|
E: '📦',
|
|
F: '⭐',
|
|
U: '📋',
|
|
T: '⚙️',
|
|
B: '🐛',
|
|
}
|
|
|
|
export function getTypeLabel(type: ItemType): string {
|
|
return TYPE_LABELS[type] || type
|
|
}
|
|
|
|
export function getTypeColor(type: ItemType): string {
|
|
return TYPE_COLORS[type] || 'bg-muted text-muted-foreground'
|
|
}
|
|
|
|
export function getTypeIcon(type: ItemType): string {
|
|
return TYPE_ICONS[type] || '📄'
|
|
}
|
|
|
|
const HIERARCHY_REGEX = /\[(([EFUTB]\d+)(?:-([EFUTB]\d+))?(?:-([EFUTB]\d+))?)\]/i
|
|
|
|
export function parseHierarchy(title: string): HierarchyInfo | null {
|
|
const match = title.match(HIERARCHY_REGEX)
|
|
if (!match) return null
|
|
|
|
const fullPath = match[1].toUpperCase()
|
|
const segments = [match[2]]
|
|
if (match[3]) segments.push(match[3])
|
|
if (match[4]) segments.push(match[4])
|
|
|
|
const items = segments.map(s => ({
|
|
type: s[0] as ItemType,
|
|
number: parseInt(s.slice(1), 10),
|
|
}))
|
|
|
|
const epic = items.find(i => i.type === 'E')
|
|
const feature = items.find(i => i.type === 'F')
|
|
const item = items[items.length - 1]
|
|
|
|
return {
|
|
fullPath,
|
|
items,
|
|
epicCode: epic ? `E${String(epic.number).padStart(2, '0')}` : null,
|
|
featureCode: feature ? `F${String(feature.number).padStart(2, '0')}` : null,
|
|
itemCode: item ? `${item.type}${String(item.number).padStart(2, '0')}` : null,
|
|
}
|
|
}
|
|
|
|
export function stripHierarchy(title: string): string {
|
|
return title.replace(HIERARCHY_REGEX, '').trim()
|
|
}
|
|
|
|
export function getItemType(title: string): ItemType {
|
|
const h = parseHierarchy(title)
|
|
if (h?.items.length) return h.items[h.items.length - 1].type
|
|
return 'U'
|
|
}
|
|
|
|
export function buildHierarchyPath(
|
|
epicNum?: number,
|
|
featureNum?: number,
|
|
itemType?: ItemType,
|
|
itemNum?: number,
|
|
): string {
|
|
const parts: string[] = []
|
|
if (epicNum !== undefined) parts.push(`E${String(epicNum).padStart(2, '0')}`)
|
|
if (featureNum !== undefined) parts.push(`F${String(featureNum).padStart(2, '0')}`)
|
|
if (itemType && itemNum !== undefined) parts.push(`${itemType}${String(itemNum).padStart(2, '0')}`)
|
|
return parts.length ? `[${parts.join('-')}]` : ''
|
|
}
|
|
|
|
export function buildFullTitle(
|
|
name: string,
|
|
epicNum?: number,
|
|
featureNum?: number,
|
|
itemType?: ItemType,
|
|
itemNum?: number,
|
|
): string {
|
|
const path = buildHierarchyPath(epicNum, featureNum, itemType, itemNum)
|
|
return path ? `${path} ${name}` : name
|
|
}
|
|
|
|
export function extractItemNumber(title: string, type: ItemType): number | null {
|
|
const h = parseHierarchy(title)
|
|
if (!h) return null
|
|
const found = h.items.find(i => i.type === type)
|
|
return found?.number ?? null
|
|
}
|