ui fixers in 10/16
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Collapses serial / warranty numbers into consecutive runs for display:
|
||||
* SN-001, SN-002 … SN-050, SN-077 → "SN-001 – SN-050" (50) and "SN-077" (1).
|
||||
*
|
||||
* Uses the same "prefix + trailing number" reading as the GRN form's range builder, so a run
|
||||
* entered there as From/adding comes back out as exactly one range here.
|
||||
*/
|
||||
export type SerialRange = { from: string; to: string; count: number }
|
||||
|
||||
function parseTrailingNumber(s: string): { prefix: string; num: number; width: number } | null {
|
||||
const m = s.match(/^(.*?)(\d+)$/)
|
||||
if (!m) return null
|
||||
return { prefix: m[1], num: parseInt(m[2], 10), width: m[2].length }
|
||||
}
|
||||
|
||||
/** Natural order: by prefix, then by the trailing number (so SN-9 sorts before SN-10). */
|
||||
function compareSerials(a: string, b: string): number {
|
||||
const pa = parseTrailingNumber(a)
|
||||
const pb = parseTrailingNumber(b)
|
||||
if (pa && pb && pa.prefix === pb.prefix) return pa.num - pb.num
|
||||
return a.localeCompare(b, undefined, { numeric: true })
|
||||
}
|
||||
|
||||
export function toSerialRanges(values: string[]): SerialRange[] {
|
||||
const sorted = [...new Set(values.map((v) => v.trim()).filter(Boolean))].sort(compareSerials)
|
||||
const ranges: SerialRange[] = []
|
||||
let run: { start: string; prefix: string; width: number; last: number; lastText: string; count: number } | null = null
|
||||
|
||||
for (const value of sorted) {
|
||||
const parsed = parseTrailingNumber(value)
|
||||
// Consecutive = same prefix, next number, written with the run's zero-padding.
|
||||
const continues =
|
||||
run !== null &&
|
||||
parsed !== null &&
|
||||
parsed.prefix === run.prefix &&
|
||||
parsed.num === run.last + 1 &&
|
||||
value === `${run.prefix}${String(parsed.num).padStart(run.width, "0")}`
|
||||
|
||||
if (continues && run && parsed) {
|
||||
run.last = parsed.num
|
||||
run.lastText = value
|
||||
run.count += 1
|
||||
continue
|
||||
}
|
||||
if (run) ranges.push({ from: run.start, to: run.lastText, count: run.count })
|
||||
run = parsed
|
||||
? { start: value, prefix: parsed.prefix, width: parsed.width, last: parsed.num, lastText: value, count: 1 }
|
||||
: { start: value, prefix: `\u0000${value}`, width: 0, last: Number.NaN, lastText: value, count: 1 }
|
||||
}
|
||||
if (run) ranges.push({ from: run.start, to: run.lastText, count: run.count })
|
||||
return ranges
|
||||
}
|
||||
|
||||
/** One-line summary, e.g. "SN-001 – SN-050" or "SN-001 – SN-050, SN-077 +2 more". */
|
||||
export function formatSerialRanges(ranges: SerialRange[], max = 2): string {
|
||||
const label = (r: SerialRange) => (r.count === 1 ? r.from : `${r.from} – ${r.to}`)
|
||||
const shown = ranges.slice(0, max).map(label).join(", ")
|
||||
return ranges.length > max ? `${shown} +${ranges.length - max} more` : shown
|
||||
}
|
||||
Reference in New Issue
Block a user