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