sozsoft-platform/ui/src/utils/workflow/workflowHelpers.ts

529 lines
15 KiB
TypeScript
Raw Normal View History

import { useLocalization } from '../hooks/useLocalization'
2026-05-22 20:41:36 +00:00
import { getNodeHeight, nodeSize } from './workflowConstants'
2026-05-22 11:50:09 +00:00
import type {
CompareOutcomeDto,
SaveCriteriaInput,
WorkflowConditionDto,
WorkflowCriteriaDto,
2026-05-22 20:41:36 +00:00
} from '@/services/workflow.service'
2026-05-22 11:50:09 +00:00
export type WorkflowCriteriaForm = Partial<WorkflowCriteriaDto> & {
2026-05-22 20:41:36 +00:00
id?: string | null
listFormCode: string
compareOutcomes: CompareOutcomeDto[]
}
2026-05-22 11:50:09 +00:00
export type WorkflowOutcome = {
2026-05-22 20:41:36 +00:00
field: string
label: string
targetId?: string | null
}
2026-05-22 11:50:09 +00:00
export type WorkflowLinkPort = {
2026-05-22 20:41:36 +00:00
field?: string
index?: number
count?: number
sourceSlotIndex?: number
sourceSlotCount?: number
targetSlotIndex?: number
targetSlotCount?: number
routeIndex?: number
routeCount?: number
}
2026-05-22 11:50:09 +00:00
export type WorkflowLink = {
2026-05-22 20:41:36 +00:00
key: string
source: WorkflowCriteriaDto
target: WorkflowCriteriaDto
label: string
sourcePort: WorkflowLinkPort
}
2026-05-22 11:50:09 +00:00
type Endpoint = {
2026-05-22 20:41:36 +00:00
link: WorkflowLink
role: 'source' | 'target'
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function buildFitLayout(criteria: WorkflowCriteriaDto[]) {
2026-05-22 20:41:36 +00:00
const links = collectLinks(criteria)
const rankById = buildTraversalRanks(criteria, links)
const groups = new Map<number, WorkflowCriteriaDto[]>()
2026-05-22 09:40:35 +00:00
criteria.forEach((item) => {
2026-05-22 20:41:36 +00:00
const column = fitColumn(item)
if (!groups.has(column)) groups.set(column, [])
groups.get(column)?.push(item)
})
2026-05-22 09:40:35 +00:00
2026-05-22 20:41:36 +00:00
const sortedColumns = [...groups.keys()].sort((a, b) => a - b)
const yGap = 74
2026-05-22 09:40:35 +00:00
const maxGroupHeight = Math.max(
1,
...[...groups.values()].map(
(items) =>
items.reduce((sum, item) => sum + getNodeHeight(item), 0) +
Math.max(0, items.length - 1) * yGap,
),
2026-05-22 20:41:36 +00:00
)
const top = 72
const left = 72
const xGap = 128
const positions = new Map<string, { x: number; y: number }>()
2026-05-22 09:40:35 +00:00
sortedColumns.forEach((column, columnIndex) => {
2026-05-22 20:41:36 +00:00
const items = (groups.get(column) || []).sort((a, b) => compareLayoutNodes(a, b, rankById))
2026-05-22 09:40:35 +00:00
const groupHeight =
items.reduce((sum, item) => sum + getNodeHeight(item), 0) +
2026-05-22 20:41:36 +00:00
Math.max(0, items.length - 1) * yGap
let y = top + Math.max(0, (maxGroupHeight - groupHeight) / 2)
2026-05-22 09:40:35 +00:00
items.forEach((item) => {
positions.set(item.id, {
x: left + columnIndex * (nodeSize.width + xGap),
y: Math.round(y),
2026-05-22 20:41:36 +00:00
})
y += getNodeHeight(item) + yGap
})
})
2026-05-22 09:40:35 +00:00
2026-05-22 20:41:36 +00:00
return positions
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
function fitColumn(item: WorkflowCriteriaDto) {
const priority: Record<string, number> = {
2026-05-22 09:40:35 +00:00
Start: 0,
Compare: 1,
Approval: 2,
Inform: 3,
End: 4,
2026-05-22 20:41:36 +00:00
}
2026-05-22 09:40:35 +00:00
2026-05-22 20:41:36 +00:00
return priority[item.kind] ?? 2
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
function compareLayoutNodes(
a: WorkflowCriteriaDto,
b: WorkflowCriteriaDto,
rankById = new Map<string, number>(),
) {
2026-05-22 09:40:35 +00:00
return (
(rankById.get(a.id) ?? 999) - (rankById.get(b.id) ?? 999) ||
2026-05-22 20:41:36 +00:00
a.title.localeCompare(b.title, 'tr')
)
2026-05-22 09:40:35 +00:00
}
2026-05-22 20:41:36 +00:00
function buildTraversalRanks(criteria: WorkflowCriteriaDto[], links: WorkflowLink[]) {
const rankById = new Map<string, number>()
const outgoing = new Map<string, string[]>(criteria.map((item) => [item.id, []]))
2026-05-22 09:40:35 +00:00
links.forEach((link) => {
2026-05-22 20:41:36 +00:00
outgoing.get(link.source.id)?.push(link.target.id)
})
2026-05-22 09:40:35 +00:00
2026-05-22 20:41:36 +00:00
const roots = criteria.filter((item) => item.kind === 'Start')
const queue = roots.length ? roots.map((item) => item.id) : criteria.map((item) => item.id)
2026-05-22 09:40:35 +00:00
while (queue.length) {
2026-05-22 20:41:36 +00:00
const id = queue.shift()
if (!id) continue
if (rankById.has(id)) continue
rankById.set(id, rankById.size)
;(outgoing.get(id) || []).forEach((targetId) => {
if (targetId && !rankById.has(targetId)) queue.push(targetId)
})
2026-05-22 09:40:35 +00:00
}
criteria.forEach((item) => {
2026-05-22 20:41:36 +00:00
if (!rankById.has(item.id)) rankById.set(item.id, rankById.size)
})
2026-05-22 09:40:35 +00:00
2026-05-22 20:41:36 +00:00
return rankById
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function collectLinks(criteria: WorkflowCriteriaDto[]) {
2026-05-22 20:41:36 +00:00
const links: WorkflowLink[] = []
2026-05-22 09:40:35 +00:00
criteria.forEach((source) => {
2026-05-22 20:41:36 +00:00
if (source.kind === 'Compare' && source.compareOutcomes?.length) {
2026-05-22 09:40:35 +00:00
source.compareOutcomes.forEach((outcome, index) => {
2026-05-22 20:41:36 +00:00
addLink(links, criteria, source, outcome.targetId, outcome.label, `compare-${index}`, {
index,
count: source.compareOutcomes.length,
field: `compareOutcomes:${index}`,
})
})
return
2026-05-22 09:40:35 +00:00
}
2026-05-22 20:41:36 +00:00
addLink(links, criteria, source, source.nextOnStart, 'Sonraki', 'next', {
2026-05-22 09:40:35 +00:00
index: 0,
count: 1,
2026-05-22 20:41:36 +00:00
field: 'nextOnStart',
})
addLink(links, criteria, source, source.nextOnTrue, 'Doğru', 'true', {
2026-05-22 09:40:35 +00:00
index: 0,
count: 2,
2026-05-22 20:41:36 +00:00
field: 'nextOnTrue',
})
addLink(links, criteria, source, source.nextOnFalse, 'Yanlış', 'false', {
2026-05-22 09:40:35 +00:00
index: 1,
count: 2,
2026-05-22 20:41:36 +00:00
field: 'nextOnFalse',
})
addLink(links, criteria, source, source.nextOnApprove, 'Onay', 'approve', {
2026-05-22 09:40:35 +00:00
index: 0,
count: 2,
2026-05-22 20:41:36 +00:00
field: 'nextOnApprove',
})
addLink(links, criteria, source, source.nextOnReject, 'Red', 'reject', {
2026-05-22 09:40:35 +00:00
index: 1,
count: 2,
2026-05-22 20:41:36 +00:00
field: 'nextOnReject',
})
})
return assignLinkSlots(links, criteria)
2026-05-22 09:40:35 +00:00
}
2026-05-22 20:41:36 +00:00
export function assignLinkSlots(links: WorkflowLink[], criteria: WorkflowCriteriaDto[]) {
const endpointGroups = new Map<string, Endpoint[]>()
2026-05-22 11:50:09 +00:00
const addEndpoint = (nodeId: string, side: string, endpoint: Endpoint) => {
2026-05-22 20:41:36 +00:00
const key = `${nodeId}:${side}`
if (!endpointGroups.has(key)) endpointGroups.set(key, [])
endpointGroups.get(key)?.push(endpoint)
}
2026-05-22 09:40:35 +00:00
links.forEach((link) => {
addEndpoint(link.source.id, sideToward(link.source, link.target), {
link,
2026-05-22 20:41:36 +00:00
role: 'source',
})
2026-05-22 09:40:35 +00:00
addEndpoint(link.target.id, sideToward(link.target, link.source), {
link,
2026-05-22 20:41:36 +00:00
role: 'target',
})
})
2026-05-22 09:40:35 +00:00
endpointGroups.forEach((endpoints) => {
endpoints.forEach((endpoint, index) => {
2026-05-22 20:41:36 +00:00
if (endpoint.role === 'source') {
endpoint.link.sourcePort.sourceSlotIndex = index
endpoint.link.sourcePort.sourceSlotCount = endpoints.length
2026-05-22 09:40:35 +00:00
} else {
2026-05-22 20:41:36 +00:00
endpoint.link.sourcePort.targetSlotIndex = index
endpoint.link.sourcePort.targetSlotCount = endpoints.length
2026-05-22 09:40:35 +00:00
}
2026-05-22 20:41:36 +00:00
})
})
2026-05-22 09:40:35 +00:00
links.forEach((link) => {
2026-05-22 20:41:36 +00:00
link.sourcePort.routeIndex = link.sourcePort.targetSlotIndex ?? 0
link.sourcePort.routeCount = link.sourcePort.targetSlotCount ?? 1
})
2026-05-22 09:40:35 +00:00
2026-05-22 20:41:36 +00:00
return links
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
function sideToward(from: WorkflowCriteriaDto, to: WorkflowCriteriaDto) {
2026-05-22 20:41:36 +00:00
const fromLeft = Number(from.positionX || 0)
const fromTop = Number(from.positionY || 0)
2026-05-22 09:40:35 +00:00
const fromCenter = {
x: fromLeft + nodeSize.width / 2,
y: fromTop + getNodeHeight(from) / 2,
2026-05-22 20:41:36 +00:00
}
2026-05-22 09:40:35 +00:00
const toCenter = {
x: Number(to.positionX || 0) + nodeSize.width / 2,
y: Number(to.positionY || 0) + getNodeHeight(to) / 2,
2026-05-22 20:41:36 +00:00
}
2026-05-22 09:40:35 +00:00
2026-05-22 20:41:36 +00:00
const dx = toCenter.x - fromCenter.x
const dy = toCenter.y - fromCenter.y
2026-05-22 09:40:35 +00:00
2026-05-22 20:41:36 +00:00
const horizontalDistance = Math.abs(dx) / (nodeSize.width / 2)
const verticalDistance = Math.abs(dy) / (getNodeHeight(from) / 2)
if (horizontalDistance >= verticalDistance) return dx >= 0 ? 'right' : 'left'
return dy >= 0 ? 'bottom' : 'top'
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function getNodeOutcomes(item: WorkflowCriteriaDto): WorkflowOutcome[] {
2026-05-22 20:41:36 +00:00
if (item.kind === 'Compare') {
2026-05-22 09:40:35 +00:00
const outcomes = item.compareOutcomes?.length
? item.compareOutcomes
: [
2026-05-22 20:41:36 +00:00
{ label: 'Doğru', targetId: item.nextOnTrue },
{ label: 'Yanlış', targetId: item.nextOnFalse },
]
2026-05-22 09:40:35 +00:00
return outcomes.slice(0, 4).map((outcome, index) => ({
field: `compareOutcomes:${index}`,
label: outcome.label || `Durum ${index + 1}`,
targetId: outcome.targetId,
2026-05-22 20:41:36 +00:00
}))
2026-05-22 09:40:35 +00:00
}
2026-05-22 20:41:36 +00:00
if (item.kind === 'Approval') {
2026-05-22 09:40:35 +00:00
return [
2026-05-22 20:41:36 +00:00
{ field: 'nextOnApprove', label: 'Onay', targetId: item.nextOnApprove },
{ field: 'nextOnReject', label: 'Red', targetId: item.nextOnReject },
]
2026-05-22 09:40:35 +00:00
}
2026-05-22 20:41:36 +00:00
if (item.kind === 'End') return []
2026-05-22 09:40:35 +00:00
2026-05-22 20:41:36 +00:00
return [{ field: 'nextOnStart', label: 'Sonraki', targetId: item.nextOnStart }]
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function outcomeLabel(field?: string) {
2026-05-22 20:41:36 +00:00
if (field?.startsWith('compareOutcomes:')) return 'Karşılaştırma durumu'
2026-05-22 09:40:35 +00:00
2026-05-22 11:50:09 +00:00
const labels: Record<string, string> = {
2026-05-22 20:41:36 +00:00
nextOnStart: 'Sonraki',
nextOnTrue: 'Doğru',
nextOnFalse: 'Yanlış',
nextOnApprove: 'Onay',
nextOnReject: 'Red',
}
return field ? labels[field] : undefined
2026-05-22 09:40:35 +00:00
}
export function addLink(
2026-05-22 11:50:09 +00:00
links: WorkflowLink[],
criteria: WorkflowCriteriaDto[],
source: WorkflowCriteriaDto,
targetId: string | null | undefined,
label: string,
type: string,
sourcePort: WorkflowLinkPort = {},
2026-05-22 09:40:35 +00:00
) {
2026-05-22 20:41:36 +00:00
if (!targetId) return
const target = criteria.find((item) => item.id === targetId)
2026-05-22 09:40:35 +00:00
if (target) {
links.push({
key: `${source.id}-${target.id}-${type}`,
source,
target,
label,
sourcePort,
2026-05-22 20:41:36 +00:00
})
2026-05-22 09:40:35 +00:00
}
}
2026-05-22 20:41:36 +00:00
export function emptyCriteria(kind = 'Compare', listFormCode = ''): WorkflowCriteriaForm {
2026-05-22 09:40:35 +00:00
return {
2026-05-22 20:41:36 +00:00
id: '',
listFormCode,
2026-05-22 09:40:35 +00:00
kind,
title: defaultTitle(kind),
compareColumn: 'Price',
2026-05-22 20:41:36 +00:00
compareOperator: '>',
2026-05-22 09:40:35 +00:00
compareValue: 5000,
2026-05-22 20:41:36 +00:00
approver: '',
nextOnStart: '',
nextOnTrue: '',
nextOnFalse: '',
nextOnApprove: '',
nextOnReject: '',
2026-05-22 09:40:35 +00:00
compareOutcomes:
2026-05-23 13:41:52 +00:00
kind === 'Compare' ? [emptyCompareOutcome1('>5000'), emptyCompareOutcome2('<=5000')] : [],
2026-05-22 09:40:35 +00:00
positionX: 32,
positionY: 150,
2026-05-22 20:41:36 +00:00
}
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm {
2026-05-22 20:41:36 +00:00
const sharedPerson = item.approver || ''
2026-05-22 09:40:35 +00:00
return {
...emptyCriteria(item.kind),
...item,
approver: sharedPerson,
2026-05-22 20:41:36 +00:00
nextOnStart: item.nextOnStart || '',
nextOnTrue: item.nextOnTrue || '',
nextOnFalse: item.nextOnFalse || '',
nextOnApprove: item.nextOnApprove || '',
nextOnReject: item.nextOnReject || '',
2026-05-22 09:40:35 +00:00
compareOutcomes: item.compareOutcomes?.length
? item.compareOutcomes.map(toCompareOutcomeForm)
: emptyCriteria(item.kind).compareOutcomes,
2026-05-22 20:41:36 +00:00
}
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
2026-05-22 20:41:36 +00:00
const sharedPerson = item.approver || ''
const compareOutcomes = (item.compareOutcomes || [])
.slice(0, 4)
.filter((outcome) => outcome.label?.trim())
.map(normalizeCompareOutcome)
const firstCompareColumn = compareOutcomes
.flatMap((outcome) => outcome.conditions || [])
.find((condition) => condition.compareColumn)?.compareColumn
2026-05-22 09:40:35 +00:00
return {
id: item.id || null,
2026-05-22 20:41:36 +00:00
listFormCode: item.listFormCode || '',
kind: item.kind || 'Compare',
title: item.title || defaultTitle(item.kind || 'Compare'),
compareColumn: firstCompareColumn || item.compareColumn || 'Price',
compareOperator: item.compareOperator || '>',
2026-05-22 09:40:35 +00:00
compareValue: Number(item.compareValue || 0),
approver: sharedPerson,
nextOnStart: item.nextOnStart || '',
nextOnTrue: compareOutcomes[0]?.targetId || item.nextOnTrue || '',
nextOnFalse: compareOutcomes[1]?.targetId || item.nextOnFalse || '',
nextOnApprove: item.nextOnApprove || '',
nextOnReject: item.nextOnReject || '',
2026-05-22 09:40:35 +00:00
positionX: Number(item.positionX || 32),
positionY: Number(item.positionY || 150),
compareOutcomes,
2026-05-22 20:41:36 +00:00
}
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function defaultTitle(kind: string) {
2026-05-22 20:41:36 +00:00
return (
{
Start: 'İş Akışı Başlat',
Compare: 'Karşılaştırma',
Approval: 'Onay',
Inform: 'Bilgilendirme',
End: 'İş Akışı Bitir',
2026-05-22 20:41:36 +00:00
}[kind] ?? 'İş Akışı Adımı'
)
2026-05-22 09:40:35 +00:00
}
export function emptyCompareOutcome1(label = 'Durum', compareColumn = 'Price'): CompareOutcomeDto {
2026-05-22 09:40:35 +00:00
return {
label,
2026-05-22 20:41:36 +00:00
targetId: '',
conditions: [{ compareColumn, compareOperator: '>', compareValue: 5000 }],
2026-05-22 20:41:36 +00:00
}
2026-05-22 09:40:35 +00:00
}
export function emptyCompareOutcome2(label = 'Durum', compareColumn = 'Price'): CompareOutcomeDto {
2026-05-23 13:41:52 +00:00
return {
label,
targetId: '',
conditions: [{ compareColumn, compareOperator: '<=', compareValue: 5000 }],
2026-05-23 13:41:52 +00:00
}
}
2026-05-22 11:50:09 +00:00
export function toCompareOutcomeForm(
outcome: Partial<CompareOutcomeDto> &
Partial<WorkflowConditionDto> & {
2026-05-22 20:41:36 +00:00
conditions?: Partial<WorkflowConditionDto>[]
2026-05-22 11:50:09 +00:00
},
): CompareOutcomeDto {
2026-05-22 09:40:35 +00:00
const conditions = outcome.conditions?.length
? outcome.conditions
: [
{
compareColumn: outcome.compareColumn || 'Price',
2026-05-22 20:41:36 +00:00
compareOperator: outcome.compareOperator || '>',
2026-05-22 09:40:35 +00:00
compareValue: outcome.compareValue || 0,
},
2026-05-22 20:41:36 +00:00
]
2026-05-22 09:40:35 +00:00
return {
2026-05-22 20:41:36 +00:00
label: outcome.label || '',
targetId: outcome.targetId || '',
2026-05-22 09:40:35 +00:00
conditions: conditions.map((condition) => ({
compareColumn: condition.compareColumn || 'Price',
2026-05-22 20:41:36 +00:00
compareOperator: condition.compareOperator || '>',
2026-05-22 09:40:35 +00:00
compareValue: condition.compareValue ?? 0,
})),
2026-05-22 20:41:36 +00:00
}
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function normalizeCompareOutcome(
outcome: Partial<CompareOutcomeDto> &
Partial<WorkflowConditionDto> & {
2026-05-22 20:41:36 +00:00
conditions?: Partial<WorkflowConditionDto>[]
2026-05-22 11:50:09 +00:00
},
): CompareOutcomeDto {
2026-05-22 09:40:35 +00:00
const conditions = (
outcome.conditions?.length
? outcome.conditions
: [
{
compareColumn: outcome.compareColumn || 'Price',
2026-05-22 20:41:36 +00:00
compareOperator: outcome.compareOperator || '>',
2026-05-22 09:40:35 +00:00
compareValue: outcome.compareValue || 0,
},
]
)
2026-05-22 20:41:36 +00:00
.filter((condition) => condition.compareOperator && String(condition.compareValue ?? '') !== '')
2026-05-22 09:40:35 +00:00
.map((condition) => ({
compareColumn: condition.compareColumn || 'Price',
2026-05-22 20:41:36 +00:00
compareOperator: condition.compareOperator || '>',
2026-05-22 09:40:35 +00:00
compareValue: Number(condition.compareValue || 0),
2026-05-22 20:41:36 +00:00
}))
2026-05-22 09:40:35 +00:00
return {
2026-05-22 20:41:36 +00:00
label: (outcome.label || '').trim(),
2026-05-22 09:40:35 +00:00
targetId: outcome.targetId || null,
conditions,
2026-05-22 20:41:36 +00:00
}
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function compareOutcomeRuleText(
outcome: Partial<CompareOutcomeDto> & Partial<WorkflowConditionDto>,
) {
2026-05-22 09:40:35 +00:00
const conditions = outcome.conditions?.length
? outcome.conditions
2026-05-22 20:41:36 +00:00
: outcome.compareOperator
2026-05-22 09:40:35 +00:00
? [
{
2026-05-22 20:41:36 +00:00
compareColumn: outcome.compareColumn,
compareOperator: outcome.compareOperator,
2026-05-22 09:40:35 +00:00
compareValue: outcome.compareValue,
},
]
2026-05-22 20:41:36 +00:00
: []
2026-05-22 09:40:35 +00:00
return conditions.length
? conditions
.map(
(condition) =>
2026-05-22 20:41:36 +00:00
`${condition.compareColumn} ${condition.compareOperator} ${formatCompactValue(condition.compareValue)}`,
2026-05-22 09:40:35 +00:00
)
2026-05-22 20:41:36 +00:00
.join(' ve ')
: 'Kural yok'
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function formatCompactValue(value: number | string | null | undefined) {
2026-05-22 20:41:36 +00:00
return new Intl.NumberFormat('tr-TR', {
2026-05-22 09:40:35 +00:00
maximumFractionDigits: 2,
2026-05-22 20:41:36 +00:00
}).format(Number(value || 0))
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function criteriaSummary(item: WorkflowCriteriaDto) {
2026-05-22 20:41:36 +00:00
if (item.kind === 'Compare') {
2026-05-22 09:40:35 +00:00
return (
(item.compareOutcomes || [])
2026-05-22 20:41:36 +00:00
.map((outcome) => `${outcome.label}: ${compareOutcomeRuleText(outcome)}`)
.join(' / ') || '-'
)
2026-05-22 09:40:35 +00:00
}
if (item.kind === 'Approval' || item.kind === 'Inform') {
return `${item.title} ${item.approver ? `- ${item.approver}` : ''}`
}
return `${item.title} ${item.approver ? `- ${item.approver}` : ''}`
2026-05-22 09:40:35 +00:00
}
2026-05-22 20:41:36 +00:00
export function targetTitle(criteria: WorkflowCriteriaDto[], id?: string | null) {
if (!id) return '-'
const item = criteria.find((candidate) => candidate.id === id)
return item ? `${item.id} - ${item.title}` : id
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function statusClass(status?: string) {
2026-05-22 20:41:36 +00:00
if (status === 'Onay Bekliyor') return 'pending'
if (status === 'Bitti') return 'done'
if (status === 'Bilgilendirildi') return 'info'
return ''
2026-05-22 09:40:35 +00:00
}
2026-05-22 11:50:09 +00:00
export function formatMoney(value?: number | string | null) {
2026-05-22 20:41:36 +00:00
return new Intl.NumberFormat('tr-TR', {
style: 'currency',
currency: 'TRY',
}).format(Number(value || 0))
2026-05-22 09:40:35 +00:00
}