11import { TableCellValue , TableFormView } from 'mobx-lark' ;
2- import { formatDate } from 'web-utility' ;
2+ import { Day , formatDate } from 'web-utility' ;
33
44import type { HackathonScheduleTone } from './Schedule' ;
55import { i18n , I18nKey } from '../../../models/Translation' ;
@@ -33,14 +33,124 @@ export const buildAgendaTypeLabelMap = ({
3333export const isPublicForm = ( { shared_limit } : TableFormView ) =>
3434 [ 'anyone_editable' ] . includes ( shared_limit as string ) ;
3535
36+ type NamedLike = { name ?: string | null } ;
37+ type TextLike = TableCellValue | NamedLike | null | undefined ;
38+ type TextListLike = TextLike | TextLike [ ] ;
39+
40+ const textOf = ( value : TextLike ) => {
41+ if ( value === null || value === undefined ) return '' ;
42+ if ( typeof value === 'boolean' ) return '' ;
43+
44+ if ( typeof value === 'object' && ! Array . isArray ( value ) ) {
45+ const {
46+ name,
47+ text,
48+ value : primitiveValue ,
49+ displayName,
50+ display_name,
51+ title,
52+ content,
53+ plainText,
54+ plain_text,
55+ user,
56+ } = value as NamedLike & {
57+ text ?: string | null ;
58+ value ?: string | number | null ;
59+ displayName ?: string | null ;
60+ display_name ?: string | null ;
61+ title ?: string | null ;
62+ content ?: string | null ;
63+ plainText ?: string | null ;
64+ plain_text ?: string | null ;
65+ user ?: {
66+ name ?: string | null ;
67+ displayName ?: string | null ;
68+ display_name ?: string | null ;
69+ } | null ;
70+ } ;
71+ const candidate = [
72+ name ,
73+ text ,
74+ primitiveValue ,
75+ displayName ,
76+ display_name ,
77+ title ,
78+ content ,
79+ plainText ,
80+ plain_text ,
81+ user ?. displayName ,
82+ user ?. display_name ,
83+ user ?. name ,
84+ ] . find ( item => item !== null && item !== undefined && `${ item } ` . trim ( ) ) ;
85+
86+ return candidate === null || candidate === undefined ? '' : `${ candidate } ` . trim ( ) ;
87+ }
88+
89+ const text = value . toString ( ) . trim ( ) ;
90+
91+ return text === '[object Object]' ? '' : text ;
92+ } ;
93+
94+ export const firstTextOf = ( value : TextListLike ) =>
95+ ( Array . isArray ( value ) ? value . map ( textOf ) . find ( Boolean ) : textOf ( value ) ) || '' ;
96+
3697export const formatMoment = ( value ?: TableCellValue ) => ( value ? formatDate ( value as string ) : '' ) ;
3798
3899export const formatPeriod = ( startedAt ?: TableCellValue , endedAt ?: TableCellValue ) =>
39100 [ formatMoment ( startedAt ) , formatMoment ( endedAt ) ] . filter ( Boolean ) . join ( ' - ' ) ;
40101
102+ export const timeOf = ( value ?: TableCellValue ) => {
103+ if ( value instanceof Date ) return value . getTime ( ) ;
104+
105+ if ( typeof value === 'number' ) return Number . isFinite ( value ) ? value : NaN ;
106+
107+ const text = firstTextOf ( value as TextListLike ) ;
108+
109+ if ( ! text ) return NaN ;
110+
111+ const time = Date . parse ( text ) ;
112+
113+ return Number . isFinite ( time ) ? time : NaN ;
114+ } ;
115+
116+ export interface CountdownWindow {
117+ startedAt ?: TableCellValue ;
118+ endedAt ?: TableCellValue ;
119+ }
120+
121+ const countdownTextOf = ( value ?: TableCellValue ) => {
122+ const time = timeOf ( value ) ;
123+
124+ return Number . isFinite ( time ) ? new Date ( time ) . toISOString ( ) : undefined ;
125+ } ;
126+
127+ export const resolveCountdownState = < T extends CountdownWindow > (
128+ items : T [ ] ,
129+ referenceTime : number ,
130+ startTime ?: TableCellValue ,
131+ endTime ?: TableCellValue ,
132+ ) => {
133+ const nextItem = items . find ( ( { startedAt, endedAt } ) => {
134+ const started = timeOf ( startedAt ) ;
135+ const ended = timeOf ( endedAt ) ;
136+
137+ return Number . isFinite ( started ) && Number . isFinite ( ended ) && referenceTime <= ended ;
138+ } ) ;
139+ const nextStartedAt = timeOf ( nextItem ?. startedAt ) ;
140+ const nextCountdownTarget =
141+ Number . isFinite ( nextStartedAt ) && nextStartedAt > referenceTime
142+ ? nextItem ?. startedAt
143+ : nextItem ?. endedAt ;
144+ const fallbackCountdownTarget = timeOf ( startTime ) > referenceTime ? startTime : endTime ;
145+ const countdownTo =
146+ countdownTextOf ( nextCountdownTarget ) || countdownTextOf ( fallbackCountdownTarget ) ;
147+
148+ return { nextItem, countdownTo } ;
149+ } ;
150+
41151export const previewText = ( items : TableCellValue [ ] , fallback : string ) =>
42152 items
43- . map ( item => item ?. toString ( ) )
153+ . map ( item => textOf ( item ) )
44154 . filter ( Boolean )
45155 . slice ( 0 , 2 )
46156 . join ( ' · ' ) || fallback ;
@@ -75,10 +185,10 @@ export const compactSummaryOf = (
75185) => {
76186 const source = Array . isArray ( text )
77187 ? text
78- . map ( item => item ?. toString ( ) )
188+ . map ( item => textOf ( item ) )
79189 . filter ( Boolean )
80190 . join ( ' · ' )
81- : text ?. toString ( ) || '' ;
191+ : textOf ( text ) ;
82192 const normalized = source . replace ( / \s + / g, ' ' ) . trim ( ) ;
83193
84194 if ( ! normalized ) return fallback ;
@@ -95,12 +205,12 @@ export const dateKeyOf = (value?: TableCellValue) => {
95205export const compactDateKeyOf = ( value ?: TableCellValue ) => dateKeyOf ( value ) . replace ( '-' , '.' ) ;
96206
97207export const daysBetween = ( startedAt ?: TableCellValue , endedAt ?: TableCellValue ) => {
98- const start = new Date ( ( startedAt as string ) || '' ) . getTime ( ) ;
99- const end = new Date ( ( endedAt as string ) || '' ) . getTime ( ) ;
208+ const start = timeOf ( startedAt ) ;
209+ const end = timeOf ( endedAt ) ;
100210
101211 if ( ! Number . isFinite ( start ) || ! Number . isFinite ( end ) || end < start ) return 0 ;
102212
103- return Math . max ( 1 , Math . ceil ( ( end - start ) / ( 24 * 60 * 60 * 1000 ) ) ) ;
213+ return Math . max ( 1 , Math . ceil ( ( end - start ) / Day ) ) ;
104214} ;
105215
106216export const normalizeAgendaType = ( value ?: TableCellValue ) =>
0 commit comments