This commit is contained in:
2025-07-23 18:28:00 +03:00
parent 7c7e0db426
commit ebd3a93cb2
21 changed files with 185 additions and 377 deletions

View File

@@ -1,53 +0,0 @@
import { ElementDefinition } from '@/entitiy/element/model/interface'
import { CheckSquare } from 'lucide-react'
interface CheckboxConfig {
placeholder?: string
required?: boolean
targetCells: any[]
}
export const checkboxDefinition: ElementDefinition<CheckboxConfig, boolean> = {
type: 'checkbox',
label: 'Чекбокс',
icon: <CheckSquare className="h-4 w-4" />,
version: 1,
defaultConfig: {
placeholder: '',
required: false,
targetCells: [],
},
mapToCells: config => config.targetCells || [],
mapToCellValues: (config, value) => {
const cells = config.targetCells || []
return cells.map(target => ({ target, value: value || false }))
},
Editor: () => (
<div className="text-sm text-gray-600">
Дополнительные настройки отсутствуют
</div>
),
Preview: ({ config }) => (
<div className="space-y-3">
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
<div className="rounded-lg border border-gray-200 bg-white p-4">
<div className="flex items-center space-x-2">
<div className="h-4 w-4 rounded border border-gray-300" />
<span className="text-sm">{config.placeholder || 'Отметить'}</span>
</div>
</div>
</div>
),
Render: ({ config, value, onChange }) => (
<div className="flex items-center space-x-2">
<input
type="checkbox"
checked={value || false}
onChange={e => onChange?.(e.target.checked)}
required={config.required}
className="h-4 w-4"
/>
<span className="text-sm">{config.placeholder || 'Отметить'}</span>
</div>
),
}

View File

@@ -1,139 +0,0 @@
import { Button } from '@/component/ui/button'
import { Input } from '@/component/ui/input'
import { ElementDefinition } from '@/entitiy/element/model/interface'
import { ElementOption } from '@/type/template'
import { CheckSquare, Plus, Trash2 } from 'lucide-react'
interface RadioConfig {
placeholder?: string
required?: boolean
options: ElementOption[]
targetCells: any[]
}
export const radioDefinition: ElementDefinition<RadioConfig, string> = {
type: 'radio',
label: 'Радиокнопки',
icon: <CheckSquare className="h-4 w-4" />,
version: 1,
defaultConfig: {
placeholder: '',
required: false,
options: [],
targetCells: [],
},
mapToCells: config => config.targetCells || [],
mapToCellValues: (config, value) => {
const cells = config.targetCells || []
return cells.map(target => ({ target, value: value || '' }))
},
Editor: ({ config, onChange }) => {
const handleAddOption = () => {
onChange({
...config,
options: [...config.options, { value: '', label: '' }],
})
}
const handleUpdateOption = (
index: number,
field: 'value' | 'label',
value: string
) => {
onChange({
...config,
options: config.options.map((option, i) =>
i === index ? { ...option, [field]: value } : option
),
})
}
const handleRemoveOption = (index: number) => {
onChange({
...config,
options: config.options.filter((_, i) => i !== index),
})
}
return (
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-sm font-medium">Варианты</label>
<Button variant="outline" size="sm" onClick={handleAddOption}>
<Plus className="mr-1 h-4 w-4" />
Добавить
</Button>
</div>
<div className="max-h-32 space-y-2 overflow-y-auto">
{config.options.map((option, index) => (
<div key={index} className="flex gap-2">
<Input
placeholder="Значение"
value={option.value}
onChange={e =>
handleUpdateOption(index, 'value', e.target.value)
}
/>
<Input
placeholder="Подпись"
value={option.label}
onChange={e =>
handleUpdateOption(index, 'label', e.target.value)
}
/>
<Button
variant="outline"
size="icon"
onClick={() => handleRemoveOption(index)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
</div>
</div>
</div>
)
},
Preview: ({ config }) => (
<div className="space-y-3">
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
<div className="rounded-lg border border-gray-200 bg-white p-4">
<div className="space-y-2">
{config.options.length > 0 ? (
config.options.map((option, index) => (
<div key={index} className="flex items-center space-x-2">
<div className="h-4 w-4 rounded-full border border-gray-300" />
<span className="text-sm">{option.label}</span>
</div>
))
) : (
<div className="flex items-center space-x-2">
<div className="h-4 w-4 rounded-full border border-gray-300" />
<span className="text-sm text-gray-500">Вариант 1</span>
</div>
)}
</div>
</div>
</div>
),
Render: ({ config, value, onChange }) => (
<div className="space-y-2">
{config.options.map((option, index) => (
<div key={index} className="flex items-center space-x-2">
<input
type="radio"
name="radio-group"
value={option.value}
checked={value === option.value}
onChange={e => onChange?.(e.target.value)}
required={config.required}
className="h-4 w-4"
/>
<span className="text-sm">{option.label}</span>
</div>
))}
</div>
),
}

View File

@@ -1,54 +0,0 @@
import { Textarea } from '@/component/ui/textarea'
import { ElementDefinition } from '@/entitiy/element/model/interface'
import { FileText } from 'lucide-react'
interface TextareaConfig {
placeholder?: string
required?: boolean
targetCells: any[]
}
export const textareaDefinition: ElementDefinition<TextareaConfig, string> = {
type: 'textarea',
label: 'Многострочный текст',
icon: <FileText className="h-4 w-4" />,
version: 1,
defaultConfig: {
placeholder: '',
required: false,
targetCells: [],
},
mapToCells: config => config.targetCells || [],
mapToCellValues: (config, value) => {
const cells = config.targetCells || []
return cells.map(target => ({ target, value: value || '' }))
},
Editor: () => (
<div className="text-sm text-gray-600">
Дополнительные настройки отсутствуют
</div>
),
Preview: ({ config }) => (
<div className="space-y-3">
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
<div className="rounded-lg border border-gray-200 bg-white p-4">
<Textarea
placeholder={config.placeholder || 'Введите текст'}
disabled
rows={3}
className="w-full resize-none"
/>
</div>
</div>
),
Render: ({ config, value, onChange }) => (
<Textarea
placeholder={config.placeholder || 'Введите текст'}
value={value || ''}
onChange={e => onChange?.(e.target.value)}
required={config.required}
rows={3}
className="w-full resize-none"
/>
),
}

View File

@@ -1,9 +1,6 @@
export { textDefinition } from '@/entitiy/element/model/implementations/TextElement' export { textDefinition } from '@/entitiy/element/model/implementations/TextElement'
export { buttonGroupDefinition } from '../../entitiy/element/model/implementations/ButtonGroup' export { buttonGroupDefinition } from '../../entitiy/element/model/implementations/ButtonGroup'
export { dateDefinition } from '../../entitiy/element/model/implementations/DateElement'
export { calibrationConditionsDefinition } from './CalibrationConditionsElement' export { calibrationConditionsDefinition } from './CalibrationConditionsElement'
export { checkboxDefinition } from './CheckboxElement'
export { dateDefinition } from './DateElement'
export { numberDefinition } from './NumberElement' export { numberDefinition } from './NumberElement'
export { radioDefinition } from './RadioElement'
export { selectDefinition } from './SelectElement' export { selectDefinition } from './SelectElement'
export { textareaDefinition } from './TextareaElement'

View File

@@ -30,7 +30,7 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({ template, onBack }) => {
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
variant="outline" variant="link"
size="sm" size="sm"
onClick={() => navigate(`/templates/${template.id}/elements`)} onClick={() => navigate(`/templates/${template.id}/elements`)}
className="flex items-center gap-1 hover:bg-muted" className="flex items-center gap-1 hover:bg-muted"
@@ -39,7 +39,7 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({ template, onBack }) => {
<span className="text-sm">Редактор интерфейса</span> <span className="text-sm">Редактор интерфейса</span>
</Button> </Button>
<Button <Button
variant="outline" variant="link"
size="sm" size="sm"
onClick={() => navigate(`/templates/${template.id}/protocols`)} onClick={() => navigate(`/templates/${template.id}/protocols`)}
className="flex items-center gap-1 hover:bg-muted" className="flex items-center gap-1 hover:bg-muted"

View File

@@ -20,7 +20,7 @@ const badgeVariants = cva(
defaultVariants: { defaultVariants: {
variant: 'default', variant: 'default',
}, },
}, }
) )
export interface BadgeProps export interface BadgeProps

View File

@@ -1,9 +1,15 @@
import * as React from "react" import { cn } from '@/lib/utils'
import { cn } from "../../lib/utils" import * as React from 'react'
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> { extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' variant?:
| 'default'
| 'destructive'
| 'outline'
| 'secondary'
| 'ghost'
| 'link'
size?: 'default' | 'sm' | 'lg' | 'icon' size?: 'default' | 'sm' | 'lg' | 'icon'
} }
@@ -12,14 +18,19 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
return ( return (
<button <button
className={cn( className={cn(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{ {
'bg-primary text-primary-foreground hover:bg-primary/90': variant === 'default', 'bg-primary text-primary-foreground hover:bg-primary/90':
'bg-destructive text-destructive-foreground hover:bg-destructive/90': variant === 'destructive', variant === 'default',
'border border-input bg-background hover:bg-accent hover:text-accent-foreground': variant === 'outline', 'bg-destructive text-destructive-foreground hover:bg-destructive/90':
'bg-secondary text-secondary-foreground hover:bg-secondary/80': variant === 'secondary', variant === 'destructive',
'border border-input bg-background hover:bg-accent hover:text-accent-foreground':
variant === 'outline',
'bg-secondary text-secondary-foreground hover:bg-secondary/80':
variant === 'secondary',
'hover:bg-accent hover:text-accent-foreground': variant === 'ghost', 'hover:bg-accent hover:text-accent-foreground': variant === 'ghost',
'text-primary underline-offset-4 hover:underline': variant === 'link', 'text-primary underline-offset-4 hover:underline':
variant === 'link',
}, },
{ {
'h-10 px-4 py-2': size === 'default', 'h-10 px-4 py-2': size === 'default',
@@ -35,6 +46,6 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
) )
} }
) )
Button.displayName = "Button" Button.displayName = 'Button'
export { Button } export { Button }

View File

@@ -1,6 +1,6 @@
import * as React from "react" import * as React from 'react'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const Card = React.forwardRef< const Card = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@@ -9,13 +9,13 @@ const Card = React.forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm", 'rounded-lg border bg-card text-card-foreground shadow-sm',
className className
)} )}
{...props} {...props}
/> />
)) ))
Card.displayName = "Card" Card.displayName = 'Card'
const CardHeader = React.forwardRef< const CardHeader = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@@ -23,11 +23,11 @@ const CardHeader = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)} className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props} {...props}
/> />
)) ))
CardHeader.displayName = "CardHeader" CardHeader.displayName = 'CardHeader'
const CardTitle = React.forwardRef< const CardTitle = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@@ -36,13 +36,13 @@ const CardTitle = React.forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"text-2xl font-semibold leading-none tracking-tight", 'text-2xl font-semibold leading-none tracking-tight',
className className
)} )}
{...props} {...props}
/> />
)) ))
CardTitle.displayName = "CardTitle" CardTitle.displayName = 'CardTitle'
const CardDescription = React.forwardRef< const CardDescription = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@@ -50,19 +50,19 @@ const CardDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("text-sm text-muted-foreground", className)} className={cn('text-sm text-muted-foreground', className)}
{...props} {...props}
/> />
)) ))
CardDescription.displayName = "CardDescription" CardDescription.displayName = 'CardDescription'
const CardContent = React.forwardRef< const CardContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
)) ))
CardContent.displayName = "CardContent" CardContent.displayName = 'CardContent'
const CardFooter = React.forwardRef< const CardFooter = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@@ -70,11 +70,10 @@ const CardFooter = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("flex items-center p-6 pt-0", className)} className={cn('flex items-center p-6 pt-0', className)}
{...props} {...props}
/> />
)) ))
CardFooter.displayName = "CardFooter" CardFooter.displayName = 'CardFooter'
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }

View File

@@ -1,7 +1,7 @@
import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import { Check } from "lucide-react" import { Check } from 'lucide-react'
import * as React from "react" import * as React from 'react'
import { cn } from "../../lib/utils" import { cn } from '../../lib/utils'
const Checkbox = React.forwardRef< const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>, React.ElementRef<typeof CheckboxPrimitive.Root>,
@@ -10,13 +10,13 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", 'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className className
)} )}
{...props} {...props}
> >
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")} className={cn('flex items-center justify-center text-current')}
> >
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>

View File

@@ -1,7 +1,7 @@
import * as DialogPrimitive from "@radix-ui/react-dialog" import * as DialogPrimitive from '@radix-ui/react-dialog'
import { X } from "lucide-react" import { X } from 'lucide-react'
import * as React from "react" import * as React from 'react'
import { cn } from "../../lib/utils" import { cn } from '../../lib/utils'
const Dialog = DialogPrimitive.Root const Dialog = DialogPrimitive.Root
@@ -18,7 +18,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
ref={ref} ref={ref}
className={cn( className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-background/80 backdrop-blur-sm',
className className
)} )}
{...props} {...props}
@@ -35,7 +35,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-lg',
className className
)} )}
{...props} {...props}
@@ -56,13 +56,13 @@ const DialogHeader = ({
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left", 'flex flex-col space-y-1.5 text-center sm:text-left',
className className
)} )}
{...props} {...props}
/> />
) )
DialogHeader.displayName = "DialogHeader" DialogHeader.displayName = 'DialogHeader'
const DialogFooter = ({ const DialogFooter = ({
className, className,
@@ -70,13 +70,13 @@ const DialogFooter = ({
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className className
)} )}
{...props} {...props}
/> />
) )
DialogFooter.displayName = "DialogFooter" DialogFooter.displayName = 'DialogFooter'
const DialogTitle = React.forwardRef< const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>, React.ElementRef<typeof DialogPrimitive.Title>,
@@ -85,7 +85,7 @@ const DialogTitle = React.forwardRef<
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref} ref={ref}
className={cn( className={cn(
"text-lg font-semibold leading-none tracking-tight", 'text-lg font-semibold leading-none tracking-tight',
className className
)} )}
{...props} {...props}
@@ -99,12 +99,21 @@ const DialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Description <DialogPrimitive.Description
ref={ref} ref={ref}
className={cn("text-sm text-muted-foreground", className)} className={cn('text-sm text-muted-foreground', className)}
{...props} {...props}
/> />
)) ))
DialogDescription.displayName = DialogPrimitive.Description.displayName DialogDescription.displayName = DialogPrimitive.Description.displayName
export { export {
Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
} }

View File

@@ -1,5 +1,5 @@
import * as React from "react" import * as React from 'react'
import { cn } from "../../lib/utils" import { cn } from '../../lib/utils'
export interface InputProps export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {} extends React.InputHTMLAttributes<HTMLInputElement> {}
@@ -10,7 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input <input
type={type} type={type}
className={cn( className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className className
)} )}
ref={ref} ref={ref}
@@ -19,6 +19,6 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
) )
} }
) )
Input.displayName = "Input" Input.displayName = 'Input'
export { Input } export { Input }

View File

@@ -1,11 +1,11 @@
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from '@radix-ui/react-label'
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from 'class-variance-authority'
import * as React from "react" import * as React from 'react'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const labelVariants = cva( const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
) )
const Label = React.forwardRef< const Label = React.forwardRef<

View File

@@ -1,7 +1,7 @@
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
import { Circle } from "lucide-react" import { Circle } from 'lucide-react'
import * as React from "react" import * as React from 'react'
import { cn } from "../../lib/utils" import { cn } from '../../lib/utils'
const RadioGroup = React.forwardRef< const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>, React.ElementRef<typeof RadioGroupPrimitive.Root>,
@@ -9,7 +9,7 @@ const RadioGroup = React.forwardRef<
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
return ( return (
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root
className={cn("grid gap-2", className)} className={cn('grid gap-2', className)}
{...props} {...props}
ref={ref} ref={ref}
/> />
@@ -25,7 +25,7 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", 'aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className className
)} )}
{...props} {...props}

View File

@@ -1,7 +1,7 @@
import * as SelectPrimitive from "@radix-ui/react-select" import * as SelectPrimitive from '@radix-ui/react-select'
import { Check, ChevronDown, ChevronUp } from "lucide-react" import { Check, ChevronDown, ChevronUp } from 'lucide-react'
import * as React from "react" import * as React from 'react'
import { cn } from "../../lib/utils" import { cn } from '../../lib/utils'
const Select = SelectPrimitive.Root const Select = SelectPrimitive.Root
@@ -16,7 +16,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className className
)} )}
{...props} {...props}
@@ -36,7 +36,7 @@ const SelectScrollUpButton = React.forwardRef<
<SelectPrimitive.ScrollUpButton <SelectPrimitive.ScrollUpButton
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", 'flex cursor-default items-center justify-center py-1',
className className
)} )}
{...props} {...props}
@@ -53,7 +53,7 @@ const SelectScrollDownButton = React.forwardRef<
<SelectPrimitive.ScrollDownButton <SelectPrimitive.ScrollDownButton
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", 'flex cursor-default items-center justify-center py-1',
className className
)} )}
{...props} {...props}
@@ -67,14 +67,14 @@ SelectScrollDownButton.displayName =
const SelectContent = React.forwardRef< const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>, React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => ( >(({ className, children, position = 'popper', ...props }, ref) => (
<SelectPrimitive.Portal> <SelectPrimitive.Portal>
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md',
position === "popper" && position === 'popper' &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className className
)} )}
position={position} position={position}
@@ -83,9 +83,9 @@ const SelectContent = React.forwardRef<
<SelectScrollUpButton /> <SelectScrollUpButton />
<SelectPrimitive.Viewport <SelectPrimitive.Viewport
className={cn( className={cn(
"p-1", 'p-1',
position === "popper" && position === 'popper' &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
)} )}
> >
{children} {children}
@@ -102,7 +102,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Label <SelectPrimitive.Label
ref={ref} ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
{...props} {...props}
/> />
)) ))
@@ -115,7 +115,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item <SelectPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className className
)} )}
{...props} {...props}
@@ -137,12 +137,21 @@ const SelectSeparator = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Separator <SelectPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props} {...props}
/> />
)) ))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export { export {
Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
} }

View File

@@ -1,14 +1,14 @@
import * as SeparatorPrimitive from "@radix-ui/react-separator" import * as SeparatorPrimitive from '@radix-ui/react-separator'
import * as React from "react" import * as React from 'react'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const Separator = React.forwardRef< const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>, React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>( >(
( (
{ className, orientation = "horizontal", decorative = true, ...props }, { className, orientation = 'horizontal', decorative = true, ...props },
ref ref
) => ( ) => (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
@@ -16,8 +16,8 @@ const Separator = React.forwardRef<
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"shrink-0 bg-border", 'shrink-0 bg-border',
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className className
)} )}
{...props} {...props}

View File

@@ -1,5 +1,5 @@
import * as React from "react" import * as React from 'react'
import { cn } from "../../lib/utils" import { cn } from '../../lib/utils'
export interface TextareaProps export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
@@ -9,7 +9,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
return ( return (
<textarea <textarea
className={cn( className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className className
)} )}
ref={ref} ref={ref}
@@ -18,6 +18,6 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
) )
} }
) )
Textarea.displayName = "Textarea" Textarea.displayName = 'Textarea'
export { Textarea } export { Textarea }

View File

@@ -0,0 +1,57 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -1,5 +1,6 @@
import { Input } from '@/component/ui/input' import { Input } from '@/component/ui/input'
import { ElementDefinition } from '@/entitiy/element/model/interface' import { ElementDefinition } from '@/entitiy/element/model/interface'
import { ExtraSettingsBadge } from '@/entitiy/element/ui/extraSettingsBadge'
import { Calendar } from 'lucide-react' import { Calendar } from 'lucide-react'
interface DateConfig { interface DateConfig {
@@ -21,27 +22,10 @@ export const dateDefinition: ElementDefinition<DateConfig, string> = {
const cells = config.targetCells || [] const cells = config.targetCells || []
return cells.map(target => ({ target, value: value || '' })) return cells.map(target => ({ target, value: value || '' }))
}, },
Editor: ({ config, onChange }) => ( Editor: () => <ExtraSettingsBadge />,
<div className="space-y-4">
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="required"
checked={config.required}
onChange={e => onChange({ ...config, required: e.target.checked })}
/>
<label htmlFor="required" className="text-sm font-medium">
Обязательное поле
</label>
</div>
</div>
),
Preview: () => ( Preview: () => (
<div className="space-y-3"> <div className="space-y-3">
<div className="text-sm font-medium text-gray-700">Предпросмотр</div> <Input type="date" className="w-full" />
<div className="rounded-lg border border-gray-200 bg-white p-4">
<Input type="date" disabled className="w-full" />
</div>
</div> </div>
), ),
Render: ({ config, value, onChange }) => ( Render: ({ config, value, onChange }) => (

View File

@@ -1,13 +1,10 @@
import { textDefinition } from '@/component/BasicElements' import { textDefinition } from '@/component/BasicElements'
import { calibrationConditionsDefinition } from '@/component/BasicElements/CalibrationConditionsElement' import { calibrationConditionsDefinition } from '@/component/BasicElements/CalibrationConditionsElement'
import { checkboxDefinition } from '@/component/BasicElements/CheckboxElement'
import { dateDefinition } from '@/component/BasicElements/DateElement'
import { numberDefinition } from '@/component/BasicElements/NumberElement' import { numberDefinition } from '@/component/BasicElements/NumberElement'
import { radioDefinition } from '@/component/BasicElements/RadioElement'
import { selectDefinition } from '@/component/BasicElements/SelectElement' import { selectDefinition } from '@/component/BasicElements/SelectElement'
import { textareaDefinition } from '@/component/BasicElements/TextareaElement'
import { standardsDefinition } from '@/component/StandardsElement/definition' import { standardsDefinition } from '@/component/StandardsElement/definition'
import { buttonGroupDefinition } from '@/entitiy/element/model/implementations/ButtonGroup' import { buttonGroupDefinition } from '@/entitiy/element/model/implementations/ButtonGroup'
import { dateDefinition } from '@/entitiy/element/model/implementations/DateElement'
import { CellTarget } from '@/type/template' import { CellTarget } from '@/type/template'
import { ReactNode } from 'react' import { ReactNode } from 'react'
@@ -66,10 +63,7 @@ export function getElementDefinition(
export const elementDefinitions = { export const elementDefinitions = {
text: textDefinition, text: textDefinition,
select: selectDefinition, select: selectDefinition,
radio: radioDefinition,
checkbox: checkboxDefinition,
number: numberDefinition, number: numberDefinition,
textarea: textareaDefinition,
date: dateDefinition, date: dateDefinition,
standards: standardsDefinition, standards: standardsDefinition,
calibration_conditions: calibrationConditionsDefinition, calibration_conditions: calibrationConditionsDefinition,
@@ -79,17 +73,11 @@ export const elementDefinitions = {
export type ElementType = keyof typeof elementDefinitions export type ElementType = keyof typeof elementDefinitions
export function initializeElementRegistry() { export function initializeElementRegistry() {
// Базовые элементы
registerElement('text', textDefinition) registerElement('text', textDefinition)
registerElement('select', selectDefinition) registerElement('select', selectDefinition)
registerElement('number', numberDefinition) registerElement('number', numberDefinition)
registerElement('date', dateDefinition) registerElement('date', dateDefinition)
registerElement('textarea', textareaDefinition)
registerElement('checkbox', checkboxDefinition)
registerElement('radio', radioDefinition)
registerElement('button_group', buttonGroupDefinition) registerElement('button_group', buttonGroupDefinition)
registerElement('calibration_conditions', calibrationConditionsDefinition) registerElement('calibration_conditions', calibrationConditionsDefinition)
// Специальные элементы
registerElement('standards', standardsDefinition) registerElement('standards', standardsDefinition)
} }

View File

@@ -172,7 +172,7 @@ export const ElementsCreation: FC = () => {
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
variant="outline" variant="link"
size="sm" size="sm"
onClick={() => navigate(`/templates/${selectedTemplate.id}/edit`)} onClick={() => navigate(`/templates/${selectedTemplate.id}/edit`)}
className="flex items-center gap-2 hover:bg-muted" className="flex items-center gap-2 hover:bg-muted"
@@ -181,7 +181,7 @@ export const ElementsCreation: FC = () => {
Настройки шаблона Настройки шаблона
</Button> </Button>
<Button <Button
variant="outline" variant="link"
size="sm" size="sm"
onClick={() => onClick={() =>
navigate(`/templates/${selectedTemplate.id}/protocols`) navigate(`/templates/${selectedTemplate.id}/protocols`)

View File

@@ -283,7 +283,7 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
{/* Кнопки справа */} {/* Кнопки справа */}
<div className="absolute right-0 flex gap-2"> <div className="absolute right-0 flex gap-2">
<Button <Button
variant="outline" variant="link"
size="sm" size="sm"
onClick={() => navigate(`/templates/${template.id}/edit`)} onClick={() => navigate(`/templates/${template.id}/edit`)}
className="flex items-center gap-1 hover:bg-muted" className="flex items-center gap-1 hover:bg-muted"
@@ -292,7 +292,7 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
<span className="text-sm">Редактор шаблона</span> <span className="text-sm">Редактор шаблона</span>
</Button> </Button>
<Button <Button
variant="outline" variant="link"
size="sm" size="sm"
onClick={() => navigate(`/templates/${template.id}/elements`)} onClick={() => navigate(`/templates/${template.id}/elements`)}
className="flex items-center gap-1 hover:bg-muted" className="flex items-center gap-1 hover:bg-muted"
@@ -314,7 +314,7 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
<Grid className="h-4 w-4" /> <Grid className="h-4 w-4" />
</Button> </Button>
<Button variant="outline" onClick={handleSave} disabled={!canSave}> <Button variant="outline" onClick={handleSave} disabled={!canSave}>
<Save className="mr-2 h-4 w-4" /> <Save className="h-4 w-4" />
Сохранить протокол Сохранить протокол
</Button> </Button>
</div> </div>