408 lines
14 KiB
TypeScript
408 lines
14 KiB
TypeScript
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>
|
||
)
|
||
}
|