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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -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]'}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user