fix(ui): плоский inline-оверлей и компактный редактор номера договора

- FieldOverlay: border-input, shadow-sm, убран overflow-hidden по умолчанию

- TaskDetail: input-редакторы h-7 leading-6, для contract-number fitOverlay=false

- ContractNumberField: добавлен compact-режим, подполя не обрезаются

- Создание задачи оставлено с вертикальным layout
This commit is contained in:
2026-07-15 15:38:22 +03:00
parent 2b682d5ad3
commit 2b3a9f768b
3 changed files with 53 additions and 34 deletions

View File

@@ -11,6 +11,7 @@ import {
} from "@/components/ui/select";
import { apiRequest } from "@/lib/queryClient";
import { useQuery } from "@tanstack/react-query";
import { cn } from "@/lib/utils";
import { FileSignature, Loader2 } from "lucide-react";
export interface ContractNumberValue {
@@ -33,6 +34,7 @@ interface ContractNumberFieldProps {
config?: ContractNumberConfig;
formId?: number;
fieldId?: number;
compact?: boolean;
}
function parseValue(val: ContractNumberValue | string | undefined): ContractNumberValue {
@@ -54,6 +56,7 @@ export default function ContractNumberField({
config,
formId,
fieldId,
compact = false,
}: ContractNumberFieldProps) {
const parsed = parseValue(value);
const [mode, setMode] = useState<'mine' | 'customer'>(parsed.mode);
@@ -127,35 +130,39 @@ export default function ContractNumberField({
const isGenerated = !!(parsed.mode === 'mine' && parsed.value && parsed.value !== '');
return (
<div className="space-y-2">
<div className="flex items-center space-x-3">
<Switch
id={`contract-number-mode-${fieldId}`}
checked={mode === 'mine'}
onCheckedChange={(checked: boolean) => handleModeChange(checked ? 'mine' : 'customer')}
disabled={disabled || isGenerated}
/>
<Label htmlFor={`contract-number-mode-${fieldId}`} className="cursor-pointer">
{mode === 'mine' ? 'Мой номер' : 'Номер заказчика'}
</Label>
</div>
<div className={cn(compact ? 'flex flex-col gap-2' : 'space-y-2')}>
<div className={cn('flex items-center', compact ? 'gap-3 flex-wrap' : 'space-x-3')}>
<div className="flex items-center space-x-3 shrink-0">
<Switch
id={`contract-number-mode-${fieldId}`}
checked={mode === 'mine'}
onCheckedChange={(checked: boolean) => handleModeChange(checked ? 'mine' : 'customer')}
disabled={disabled || isGenerated}
/>
<Label htmlFor={`contract-number-mode-${fieldId}`} className="cursor-pointer">
{mode === 'mine' ? 'Мой номер' : 'Номер заказчика'}
</Label>
</div>
<Select
value={legalEntityTaskId?.toString() || ''}
onValueChange={handleLegalEntityChange}
disabled={disabled || isGenerated}
>
<SelectTrigger>
<SelectValue placeholder="Выберите юрлицо" />
</SelectTrigger>
<SelectContent>
{tasksData?.tasks?.map((task) => (
<SelectItem key={task.id} value={task.id.toString()}>
#{task.id} {task.title}
</SelectItem>
))}
</SelectContent>
</Select>
<div className={cn('flex-1 min-w-[180px]', compact && 'w-full')}>
<Select
value={legalEntityTaskId?.toString() || ''}
onValueChange={handleLegalEntityChange}
disabled={disabled || isGenerated}
>
<SelectTrigger>
<SelectValue placeholder="Выберите юрлицо" />
</SelectTrigger>
<SelectContent>
{tasksData?.tasks?.map((task) => (
<SelectItem key={task.id} value={task.id.toString()}>
#{task.id} {task.title}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{mode === 'customer' ? (
<Input
@@ -164,10 +171,14 @@ export default function ContractNumberField({
onChange={(e) => handleCustomerChange(e.target.value)}
placeholder="Введите шифр заказчика"
disabled={disabled}
className={cn(compact && 'h-7 leading-6 px-2')}
/>
) : (
<div className="relative">
<div className="flex items-center space-x-2 rounded-md border border-input bg-muted/30 px-3 py-2">
<div className={cn(
'flex items-center space-x-2 rounded-md border border-input bg-muted/30',
compact ? 'px-2 h-7' : 'px-3 py-2'
)}>
<FileSignature className="h-4 w-4 text-muted-foreground shrink-0" />
{isGenerated ? (
<span className="text-sm font-medium">{parsed.value}</span>

View File

@@ -42,7 +42,7 @@ export function FieldOverlay({
return createPortal(
<div
className={cn(
'absolute z-50 rounded-md border-2 border-primary/50 bg-background shadow-lg overflow-hidden',
'absolute z-50 rounded-md border border-input bg-background shadow-sm',
fitContainer ? 'inset-0' : 'left-0 top-0 min-w-full',
className
)}

View File

@@ -950,7 +950,7 @@ function FieldValueRow({
// Рендер редактора по типу поля
const renderEditor = () => {
const inputClass = cn(
'border border-input rounded px-1.5 py-0.5 text-sm bg-background focus:outline-none focus:ring-1 focus:ring-ring w-full max-w-[280px]',
'border border-input rounded px-1.5 text-sm bg-background focus:outline-none focus:ring-1 focus:ring-ring w-full max-w-[280px] h-7 leading-6',
errorMessage && 'border-destructive ring-destructive'
);
const textareaClass = cn(
@@ -1160,6 +1160,7 @@ function FieldValueRow({
config={cnConfig}
formId={formId}
fieldId={field.id}
compact
/>
</div>
);
@@ -1295,6 +1296,7 @@ function FieldValueRow({
const readViewNode = renderReadValue();
const editorNode = renderEditor();
const canShowInlineEditor = isGlobalEditMode && isEditable && !fieldState.immutable && !readOnly && editorNode != null;
const isContractNumberField = field.type === 'contract-number';
return canShowInlineEditor ? (
<InlineField
readView={readViewNode}
@@ -1302,10 +1304,16 @@ function FieldValueRow({
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' : ''}
fitOverlay={!isContractNumberField && field.type !== 'textarea' && field.type !== 'rich-text'}
overlayClassName={
isContractNumberField
? '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={field.type === 'textarea' || field.type === 'rich-text' ? '100%' : 180}
overlayMinWidth={isContractNumberField ? 320 : 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]'}
/>