Files
protoc-frontend/src/entitiy/element/model/implementations/VerificationConditionsElement/VerificationConditionsRender.tsx

408 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Button } from '@/component/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/component/ui/card'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/component/ui/dialog'
import { Input } from '@/component/ui/input'
import { Label } from '@/component/ui/label'
import { CellsBadge } from '@/entitiy/element/ui/cellsBadge'
import {
AlertCircle,
Building2,
Calendar as CalendarIcon,
CheckCircle,
Plus,
Settings,
} from 'lucide-react'
import { useEffect, useState } from 'react'
import {
useDailyConditions,
useLaboratories,
useLaboratoryConditions,
} from './hooks'
import {
VerificationConditionsConfig,
VerificationConditionsValue,
} from './types'
interface VerificationConditionsRenderProps {
config: VerificationConditionsConfig
value?: VerificationConditionsValue
onChange?: (value: VerificationConditionsValue) => void
onSave?: (value: VerificationConditionsValue) => Promise<void>
}
export function VerificationConditionsRender({
config,
value,
onChange,
onSave,
}: VerificationConditionsRenderProps) {
const [selectedDate, setSelectedDate] = useState(
value?.date || config.selectedDate || new Date().toISOString().split('T')[0]
)
const [editDialogOpen, setEditDialogOpen] = useState(false)
const [editingConditions, setEditingConditions] = useState<
Record<string, string>
>({})
const { data: laboratories = [] } = useLaboratories()
const { data: labData } = useLaboratoryConditions(config.selectedLaboratoryId)
const {
data: dailyCondition,
saveConditions,
isSaving,
} = useDailyConditions(config.selectedLaboratoryId, selectedDate)
const selectedLab = laboratories.find(
lab => lab.id === config.selectedLaboratoryId
)
const hasConditions =
dailyCondition && Object.keys(dailyCondition.conditions).length > 0
// Инициализация данных при загрузке из БД, только если данные еще не заполнены
useEffect(() => {
if (
dailyCondition &&
hasConditions &&
onChange &&
(!value?.conditions || Object.keys(value.conditions).length === 0)
) {
onChange({
date: selectedDate,
conditions: dailyCondition.conditions,
})
}
}, [dailyCondition, hasConditions, onChange, selectedDate, value?.conditions])
// Синхронизация selectedDate с внешним value.date
useEffect(() => {
if (value?.date && value.date !== selectedDate) {
setSelectedDate(value.date)
}
}, [value?.date, selectedDate])
// Инициализация значения если оно не установлено
useEffect(() => {
if (!value && onChange) {
onChange({
date: selectedDate,
conditions: {},
})
}
}, [value, onChange, selectedDate])
const handleDateChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newDate = event.target.value
setSelectedDate(newDate)
// Обновляем значение даты в value
if (onChange) {
onChange({
date: newDate,
conditions: value?.conditions || {},
})
}
}
const handleOpenEditDialog = (isCreating = false) => {
if (isCreating) {
const emptyConditions: Record<string, string> = {}
labData?.conditionTypes.forEach(conditionType => {
emptyConditions[conditionType.id] = ''
})
setEditingConditions(emptyConditions)
} else {
setEditingConditions(dailyCondition?.conditions || {})
}
setEditDialogOpen(true)
}
const handleSaveConditions = async () => {
if (!config.selectedLaboratoryId || !selectedDate || !labData) return
try {
await saveConditions(editingConditions)
if (onChange) {
onChange({
date: selectedDate,
conditions: editingConditions,
})
}
setEditDialogOpen(false)
} catch (error) {
console.error('Ошибка сохранения:', error)
}
}
const handleConditionChange = (conditionId: string, value: string) => {
setEditingConditions(prev => ({
...prev,
[conditionId]: value,
}))
}
if (!config.selectedLaboratoryId) {
return (
<Card className="border-dashed">
<CardContent className="p-3">
<div className="text-center text-muted-foreground">
<AlertCircle className="mx-auto mb-1 h-4 w-4 opacity-50" />
<p className="text-xs">Лаборатория не настроена</p>
</div>
</CardContent>
</Card>
)
}
if (!labData) {
return (
<Card>
<CardContent className="p-3">
<div className="text-center text-muted-foreground">
<div className="mx-auto mb-1 h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<p className="text-xs">Загрузка...</p>
</div>
</CardContent>
</Card>
)
}
return (
<div className="relative">
{/* Бейдж ячеек */}
<CellsBadge targetCells={config.targetCells || []} />
<Card>
<CardHeader className="px-3 pb-1 pt-2">
<CardTitle className="flex items-center gap-1.5 text-sm">
<Building2 className="h-3.5 w-3.5" />
<span className="truncate">
{selectedLab?.name || 'Лаборатория'}
</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2 px-3 pb-3">
{/* Выбор даты */}
<div className="flex items-center gap-2">
<CalendarIcon className="h-3.5 w-3.5 text-muted-foreground" />
<Input
type="date"
value={selectedDate}
onChange={handleDateChange}
lang="ru"
className="h-6 border-none bg-muted/50 px-2 text-xs font-normal hover:bg-muted/70 focus:bg-background"
/>
</div>
{/* Статус и данные */}
<div className="border-t pt-2">
{hasConditions ? (
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5 text-xs text-green-600">
<CheckCircle className="h-3.5 w-3.5" />
<span className="font-medium">Данные внесены</span>
</div>
<Dialog
open={editDialogOpen}
onOpenChange={setEditDialogOpen}
>
<DialogTrigger asChild>
<Button
size="sm"
variant="outline"
className="h-6 px-2 text-xs"
onClick={() => handleOpenEditDialog(false)}
>
<Settings className="mr-1 h-3 w-3" />
Настроить
</Button>
</DialogTrigger>
<DialogContent className="max-w-sm">
<DialogHeader>
<DialogTitle className="text-base">
Условия поверки
</DialogTitle>
</DialogHeader>
<div className="space-y-3">
<div className="space-y-1 text-xs text-muted-foreground">
<div>{selectedLab?.name}</div>
<div>
{new Date(selectedDate).toLocaleDateString('ru-RU')}
</div>
</div>
<div className="space-y-2">
{labData.conditionTypes.map(conditionType => (
<div key={conditionType.id} className="space-y-1">
<Label className="text-xs font-medium">
{conditionType.name}
{conditionType.unit && (
<span className="ml-1 font-normal text-muted-foreground">
({conditionType.unit})
</span>
)}
</Label>
<Input
value={
editingConditions[conditionType.id] || ''
}
onChange={e =>
handleConditionChange(
conditionType.id,
e.target.value
)
}
placeholder="Значение"
className="h-7 text-xs"
/>
</div>
))}
</div>
<div className="flex gap-2">
<Button
onClick={handleSaveConditions}
disabled={isSaving}
size="sm"
className="h-7 flex-1 text-xs"
>
{isSaving ? 'Сохранение...' : 'Сохранить'}
</Button>
<Button
variant="outline"
onClick={() => setEditDialogOpen(false)}
size="sm"
className="h-7 flex-1 text-xs"
>
Отмена
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
<div className="space-y-1">
{labData.conditionTypes.map(conditionType => {
const conditionValue =
dailyCondition!.conditions[conditionType.id]
return (
<div
key={conditionType.id}
className="flex items-center justify-between rounded bg-muted/30 px-2 py-1 text-xs"
>
<span className="truncate font-medium">
{conditionType.name}
</span>
<span className="ml-2 font-mono text-muted-foreground">
{conditionValue || '—'}
{conditionValue && conditionType.unit && (
<span className="ml-1">{conditionType.unit}</span>
)}
</span>
</div>
)
})}
</div>
</div>
) : (
<div className="space-y-2">
<div className="flex items-center gap-1.5 text-xs text-orange-600">
<AlertCircle className="h-3.5 w-3.5" />
<span className="font-medium">Данные не внесены</span>
</div>
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
<DialogTrigger asChild>
<Button
onClick={() => handleOpenEditDialog(true)}
disabled={isSaving}
size="sm"
className="h-6 w-full text-xs"
variant="outline"
>
<Plus className="mr-1.5 h-3 w-3" />
Внести данные
</Button>
</DialogTrigger>
<DialogContent className="max-w-sm">
<DialogHeader>
<DialogTitle className="text-base">
Условия поверки
</DialogTitle>
</DialogHeader>
<div className="space-y-3">
<div className="space-y-1 text-xs text-muted-foreground">
<div>{selectedLab?.name}</div>
<div>
{new Date(selectedDate).toLocaleDateString('ru-RU')}
</div>
</div>
<div className="space-y-2">
{labData.conditionTypes.map(conditionType => (
<div key={conditionType.id} className="space-y-1">
<Label className="text-xs font-medium">
{conditionType.name}
{conditionType.unit && (
<span className="ml-1 font-normal text-muted-foreground">
({conditionType.unit})
</span>
)}
</Label>
<Input
value={editingConditions[conditionType.id] || ''}
onChange={e =>
handleConditionChange(
conditionType.id,
e.target.value
)
}
placeholder="Значение"
className="h-7 text-xs"
/>
</div>
))}
</div>
<div className="flex gap-2">
<Button
onClick={handleSaveConditions}
disabled={isSaving}
size="sm"
className="h-7 flex-1 text-xs"
>
{isSaving ? 'Сохранение...' : 'Сохранить'}
</Button>
<Button
variant="outline"
onClick={() => setEditDialogOpen(false)}
size="sm"
className="h-7 flex-1 text-xs"
>
Отмена
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
)}
</div>
</CardContent>
</Card>
</div>
)
}