11'use client'
22
3- import { useEffect , useId , useRef , useState } from 'react'
3+ import { useId , useRef , useState } from 'react'
44import {
55 Checkbox ,
6+ Chip ,
67 ChipConfirmModal ,
78 ChipDatePicker ,
89 ChipModal ,
@@ -22,7 +23,7 @@ import type { ColumnDefinition, TableInfo, TableRow } from '@/lib/table'
2223import { columnTypeOf } from '@/lib/table/column-types'
2324import { resolveCurrencyCode } from '@/lib/table/currency'
2425import { getTimezoneEditBlockedMessage } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/timezone-editing'
25- import { useTimezoneState } from '@/hooks/queries/general-settings'
26+ import { type TimezoneState , useTimezoneState } from '@/hooks/queries/general-settings'
2627import { useDeleteTableRow , useDeleteTableRows , useUpdateTableRow } from '@/hooks/queries/tables'
2728import {
2829 cleanCellValue ,
@@ -48,12 +49,16 @@ export interface RowModalProps {
4849function cleanRowData (
4950 columns : ColumnDefinition [ ] ,
5051 rowData : Record < string , unknown > ,
51- timeZone : string
52+ timeZone : string ,
53+ dateEditorsReady : boolean
5254) : Record < string , unknown > {
5355 const cleanData : Record < string , unknown > = { }
5456
5557 columns . forEach ( ( col ) => {
5658 const value = rowData [ col . name ]
59+ if ( columnTypeOf ( col ) . editor === 'date' && ! dateEditorsReady ) {
60+ return
61+ }
5762 try {
5863 cleanData [ col . name ] = cleanCellValue ( value , col , timeZone )
5964 } catch {
@@ -85,9 +90,7 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
8590 if ( timezoneState . status === 'ready' && editTimeZoneRef . current === null ) {
8691 editTimeZoneRef . current = timezoneState . timezone
8792 }
88- const hasDateEditorColumn =
89- mode === 'edit' && columns . some ( ( column ) => columnTypeOf ( column ) . editor === 'date' )
90- const timezoneUnavailable = hasDateEditorColumn && editTimeZoneRef . current === null
93+ const dateEditorsReady = editTimeZoneRef . current !== null
9194 const timeZone = editTimeZoneRef . current ?? timezoneState . timezone
9295 const [ rowData , setRowData ] = useState < Record < string , unknown > > ( ( ) =>
9396 mode === 'edit' && row ? row . data : { }
@@ -100,25 +103,17 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
100103 updateRowMutation . isPending || deleteRowMutation . isPending || deleteRowsMutation . isPending
101104
102105 const timezoneBlockedMessage = getTimezoneEditBlockedMessage ( timezoneState )
103- useEffect ( ( ) => {
104- if (
105- ! isOpen ||
106- ! timezoneUnavailable ||
107- ( timezoneState . status !== 'invalid' && timezoneState . status !== 'error' ) ||
108- ! timezoneBlockedMessage
109- ) {
110- return
111- }
112- toast . error ( timezoneBlockedMessage )
113- } , [ isOpen , timezoneBlockedMessage , timezoneState . status , timezoneUnavailable ] )
106+ const hasEditableColumn = columns . some (
107+ ( column ) => columnTypeOf ( column ) . editor !== 'date' || dateEditorsReady
108+ )
114109
115110 const handleFormSubmit = async ( e ?: React . FormEvent ) => {
116111 e ?. preventDefault ( )
117112 setError ( null )
118- if ( timezoneUnavailable ) return
113+ if ( ! hasEditableColumn ) return
119114
120115 try {
121- const cleanData = cleanRowData ( columns , rowData , timeZone )
116+ const cleanData = cleanRowData ( columns , rowData , timeZone , dateEditorsReady )
122117
123118 if ( row ) {
124119 await updateRowMutation . mutateAsync ( { rowId : row . id , data : cleanData } )
@@ -193,25 +188,27 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
193188 Update values for { table ?. name ?? 'table' }
194189 </ p >
195190 < form onSubmit = { handleFormSubmit } className = 'contents' >
196- < button type = 'submit' hidden disabled = { isSubmitting || timezoneUnavailable } />
197- { timezoneUnavailable ? (
198- < p role = 'status' className = 'px-2 text-[var(--text-muted)] text-small' >
199- { timezoneState . status === 'loading'
200- ? 'Loading timezone…'
201- : timezoneState . status === 'invalid'
202- ? 'Invalid timezone'
203- : 'Timezone unavailable' }
204- </ p >
205- ) : (
206- columns . map ( ( column ) => (
191+ < button type = 'submit' hidden disabled = { isSubmitting || ! hasEditableColumn } />
192+ { columns . map ( ( column ) =>
193+ columnTypeOf ( column ) . editor === 'date' && ! dateEditorsReady ? (
194+ < TimezoneBlockedColumnField
195+ key = { column . name }
196+ column = { column }
197+ value = { rowData [ column . name ] }
198+ status = { timezoneState . status }
199+ onAttemptEdit = { ( ) => {
200+ if ( timezoneBlockedMessage ) toast . error ( timezoneBlockedMessage )
201+ } }
202+ />
203+ ) : (
207204 < ColumnField
208205 key = { column . name }
209206 column = { column }
210207 value = { rowData [ column . name ] }
211208 timeZone = { timeZone }
212209 onChange = { ( value ) => setRowData ( ( prev ) => ( { ...prev , [ column . name ] : value } ) ) }
213210 />
214- ) )
211+ )
215212 ) }
216213 </ form >
217214 < ChipModalError > { error } </ ChipModalError >
@@ -222,7 +219,7 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
222219 primaryAction = { {
223220 label : isSubmitting ? 'Updating...' : 'Update Row' ,
224221 onClick : ( ) => handleFormSubmit ( ) ,
225- disabled : isSubmitting || timezoneUnavailable ,
222+ disabled : isSubmitting || ! hasEditableColumn ,
226223 } }
227224 />
228225 </ ChipModal >
@@ -236,23 +233,72 @@ interface ColumnFieldProps {
236233 onChange : ( value : unknown ) => void
237234}
238235
239- function ColumnField ( { column, value, timeZone, onChange } : ColumnFieldProps ) {
240- const checkboxId = useId ( )
241- const title = (
236+ function ColumnTitle ( { column } : { column : ColumnDefinition } ) {
237+ return (
242238 < >
243239 { column . name }
244240 { column . unique && (
245241 < span className = 'ml-1.5 font-normal text-[var(--text-tertiary)] text-xs' > (unique)</ span >
246242 ) }
247243 </ >
248244 )
249- // Currency names its code — the modal edits the bare amount, so without it
250- // there is nothing on screen saying which currency the number is in.
245+ }
246+
247+ function columnFieldHint ( column : ColumnDefinition ) : string {
251248 const typeLabel =
252249 column . type === 'currency'
253250 ? `currency (${ resolveCurrencyCode ( column . currencyCode ) } )`
254251 : column . type
255- const hint = `Type: ${ typeLabel } ${ column . required ? '' : ' (optional)' } `
252+ return `Type: ${ typeLabel } ${ column . required ? '' : ' (optional)' } `
253+ }
254+
255+ interface TimezoneBlockedColumnFieldProps {
256+ column : ColumnDefinition
257+ value : unknown
258+ status : TimezoneState [ 'status' ]
259+ onAttemptEdit : ( ) => void
260+ }
261+
262+ function TimezoneBlockedColumnField ( {
263+ column,
264+ value,
265+ status,
266+ onAttemptEdit,
267+ } : TimezoneBlockedColumnFieldProps ) {
268+ const rawValue =
269+ typeof value === 'string'
270+ ? value
271+ : value === null || value === undefined
272+ ? ''
273+ : JSON . stringify ( value )
274+ const displayValue = status === 'loading' ? 'Loading timezone…' : rawValue || 'No value'
275+
276+ return (
277+ < ChipModalField
278+ type = 'custom'
279+ title = { < ColumnTitle column = { column } /> }
280+ required = { column . required }
281+ hint = { columnFieldHint ( column ) }
282+ >
283+ { ( aria ) => (
284+ < Chip
285+ { ...aria }
286+ variant = 'border'
287+ fullWidth
288+ onClick = { onAttemptEdit }
289+ aria-label = { `Edit ${ column . name } ` }
290+ >
291+ { displayValue }
292+ </ Chip >
293+ ) }
294+ </ ChipModalField >
295+ )
296+ }
297+
298+ function ColumnField ( { column, value, timeZone, onChange } : ColumnFieldProps ) {
299+ const checkboxId = useId ( )
300+ const title = < ColumnTitle column = { column } />
301+ const hint = columnFieldHint ( column )
256302 const definition = columnTypeOf ( column )
257303
258304 if ( definition . editor === 'toggle' ) {
0 commit comments