fix: restore original flex layout in FieldValueRow
- Revert NotionFieldRow grid layout back to original flex layout - Keep StickyActionBar at bottom (not top) - Add pencil icon on hover in left column - Keep all original overlay logic (wide overlay for contract-number, date+prolongation) - Preserve inline horizontal layout for extra elements (checkbox, calendar icon) Fixes: vertical stacking of extra elements, misaligned fields
This commit is contained in:
@@ -52,7 +52,6 @@ import ContactField from '@/components/ContactField';
|
||||
import { UserRoleRenderer, UserRoleEditor } from '@/components/fields';
|
||||
import { InlineField } from '@/components/fields/InlineField';
|
||||
import { TaskFieldSelect } from '@/components/task-form-fields';
|
||||
import { NotionFieldRow } from '@/components/task-detail/NotionFieldRow';
|
||||
import { StickyActionBar } from '@/components/task-detail/StickyActionBar';
|
||||
import { useDirtyFields } from '@/components/task-detail/useDirtyFields';
|
||||
import { TaskDocumentButton } from '@/components/tasks/TaskDocumentButton';
|
||||
@@ -1256,79 +1255,102 @@ function FieldValueRow({
|
||||
const fieldUrl = `/api/tasks/${taskId}/field-values/${field.id}`;
|
||||
const isFieldPending = isPending ? isPending(fieldUrl) : false;
|
||||
|
||||
// Build status indicator for label
|
||||
const statusIndicator = isFieldPending ? (
|
||||
<span title="Изменение ожидает синхронизации">
|
||||
<Clock className="w-3 h-3 text-amber-500 dark:text-amber-400 animate-pulse" />
|
||||
</span>
|
||||
) : fieldState.immutable ? (
|
||||
<span title="Редактирование запрещено">
|
||||
<PencilOff className="w-3 h-3 text-red-400" />
|
||||
</span>
|
||||
) : fieldState.required ? (
|
||||
<span className="text-destructive text-[10px] font-bold">*</span>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<NotionFieldRow
|
||||
label={field.name}
|
||||
required={fieldState.required}
|
||||
editable={isEditable && !readOnly && !fieldState.immutable}
|
||||
isEditing={isGlobalEditMode && isEditable && !fieldState.immutable && !readOnly}
|
||||
onEnterEdit={handleEnterEdit}
|
||||
className={isFieldPending && !isGlobalEditMode ? 'opacity-80' : undefined}
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="group/field flex items-start gap-1 py-0.5 -mx-1 px-1 rounded transition-colors hover:bg-muted/20"
|
||||
onDoubleClick={handleEnterEdit}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
>
|
||||
{/* Read view */}
|
||||
<div className="flex items-center gap-1.5">
|
||||
{statusIndicator}
|
||||
{(() => {
|
||||
const readViewNode = renderReadValue();
|
||||
const editorNode = renderEditor();
|
||||
const canShowInlineEditor = isGlobalEditMode && isEditable && !fieldState.immutable && !readOnly && editorNode != null;
|
||||
return canShowInlineEditor ? (
|
||||
<InlineField
|
||||
readView={readViewNode}
|
||||
editor={editorNode}
|
||||
isEditing={true}
|
||||
editable={isEditable && !readOnly && !fieldState.immutable}
|
||||
onEnterEdit={handleEnterEdit}
|
||||
fitOverlay={field.type !== 'textarea' && field.type !== 'rich-text'}
|
||||
overlayClassName={
|
||||
field.type === 'textarea' || field.type === 'rich-text'
|
||||
? 'flex flex-col overflow-hidden'
|
||||
: ''
|
||||
}
|
||||
overlayMaxHeight={field.type === 'textarea' || field.type === 'rich-text' ? 'min(85vh, 700px)' : undefined}
|
||||
overlayMinWidth={
|
||||
field.type === 'textarea' || field.type === 'rich-text'
|
||||
? '100%'
|
||||
: 200
|
||||
}
|
||||
overlayWidth={field.type === 'textarea' || field.type === 'rich-text' ? '100%' : undefined}
|
||||
className={field.type === 'textarea' || field.type === 'rich-text' ? 'items-start min-h-[24px] w-full' : 'items-center min-h-[28px]'}
|
||||
/>
|
||||
) : readViewNode;
|
||||
})()}
|
||||
{/* Левая колонка: индикаторы обязательности / неизменяемости / карандаш */}
|
||||
<div className="w-6 h-5 shrink-0 flex items-center justify-center">
|
||||
{isFieldPending ? (
|
||||
<span className="inline-flex items-center justify-center w-5 h-5" title="Изменение ожидает синхронизации">
|
||||
<Clock className="w-3.5 h-3.5 text-amber-500 dark:text-amber-400 animate-pulse" />
|
||||
</span>
|
||||
) : fieldState.immutable ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleEnterEdit}
|
||||
className="inline-flex items-center justify-center w-5 h-5 rounded text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/50 cursor-not-allowed"
|
||||
title="Редактирование запрещено в текущем статусе"
|
||||
>
|
||||
<PencilOff className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
) : fieldState.required ? (
|
||||
<span
|
||||
className="inline-flex items-center justify-center w-5 h-5 text-red-500 dark:text-red-400 font-bold"
|
||||
title="Обязательное поле"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
) : (
|
||||
/* Карандаш при наведении на editable поле */
|
||||
isEditable && !readOnly && !isGlobalEditMode && (
|
||||
<Pencil className="w-3 h-3 text-muted-foreground/0 group-hover/field:text-muted-foreground/50 transition-all" />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pending indicator below value */}
|
||||
{isFieldPending && !isGlobalEditMode && (
|
||||
<span
|
||||
className="inline-flex items-center gap-0.5 text-[10px] text-amber-600 dark:text-amber-400 font-medium mt-0.5"
|
||||
title="Изменение будет отправлено при восстановлении соединения"
|
||||
>
|
||||
<Clock className="w-2.5 h-2.5" />
|
||||
ожидает
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Validation error */}
|
||||
{errorMessage && (
|
||||
<span className="text-[10px] text-destructive mt-0.5 block">{errorMessage}</span>
|
||||
)}
|
||||
</NotionFieldRow>
|
||||
|
||||
{/* Контент поля */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start gap-1 flex-wrap">
|
||||
<strong className={cn('shrink-0', errorMessage && 'text-destructive')}>
|
||||
{field.name}:
|
||||
</strong>
|
||||
{(() => {
|
||||
const readViewNode = renderReadValue();
|
||||
const editorNode = renderEditor();
|
||||
const canShowInlineEditor = isGlobalEditMode && isEditable && !fieldState.immutable && !readOnly && editorNode != null;
|
||||
const isContractNumberField = field.type === 'contract-number';
|
||||
const isDateWithProlongation = (field.type === 'date' || field.type === 'datetime') && field.autoProlongation;
|
||||
const needsWideOverlay = isContractNumberField || isDateWithProlongation;
|
||||
return canShowInlineEditor ? (
|
||||
<InlineField
|
||||
readView={readViewNode}
|
||||
editor={editorNode}
|
||||
isEditing={true}
|
||||
editable={isEditable && !readOnly && !fieldState.immutable}
|
||||
onEnterEdit={handleEnterEdit}
|
||||
fitOverlay={!needsWideOverlay && field.type !== 'textarea' && field.type !== 'rich-text'}
|
||||
overlayClassName={
|
||||
needsWideOverlay
|
||||
? 'overflow-visible'
|
||||
: field.type === 'textarea' || field.type === 'rich-text'
|
||||
? 'flex flex-col overflow-hidden'
|
||||
: ''
|
||||
}
|
||||
overlayMaxHeight={field.type === 'textarea' || field.type === 'rich-text' ? 'min(85vh, 700px)' : undefined}
|
||||
overlayMinWidth={
|
||||
isContractNumberField
|
||||
? 320
|
||||
: isDateWithProlongation
|
||||
? 280
|
||||
: field.type === 'textarea' || field.type === 'rich-text'
|
||||
? '100%'
|
||||
: 180
|
||||
}
|
||||
overlayWidth={field.type === 'textarea' || field.type === 'rich-text' ? '100%' : undefined}
|
||||
className={field.type === 'textarea' || field.type === 'rich-text' ? 'items-start min-h-[24px] w-full' : 'items-center min-h-[28px]'}
|
||||
/>
|
||||
) : readViewNode;
|
||||
})()}
|
||||
</div>
|
||||
{isFieldPending && !isGlobalEditMode && (
|
||||
<span
|
||||
className="ml-1.5 inline-flex items-center gap-0.5 text-[10px] text-amber-600 dark:text-amber-400 font-medium"
|
||||
title="Изменение будет отправлено при восстановлении соединения"
|
||||
>
|
||||
<Clock className="w-2.5 h-2.5" />
|
||||
ожидает
|
||||
</span>
|
||||
)}
|
||||
{errorMessage && (
|
||||
<span className="ml-1.5 text-[10px] text-destructive">{errorMessage}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<GeoNavigationPicker
|
||||
geo={navGeo}
|
||||
open={!!navGeo}
|
||||
|
||||
Reference in New Issue
Block a user