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:
agent
2026-07-16 01:38:29 +08:00
parent 2331dc00a5
commit f59409fba1

View File

@@ -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}