Files
protoc-frontend/src/components/StandardsElement/StandardsSelector.tsx

228 lines
7.8 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 { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Calendar, Check, FileText, Settings, X } from "lucide-react";
import { useState } from "react";
interface MeasurementStandard {
id: string;
name: string;
shortName: string;
type: string;
registryNumber: string;
range: string;
accuracy: string;
certificateNumber: string;
validUntil: string;
}
interface StandardsSelectorProps {
isOpen: boolean;
onClose: () => void;
value: string[];
onChange: (value: string[]) => void;
registryNumber?: string;
}
// Моковые данные для эталонов
const mockStandards: MeasurementStandard[] = [
{
id: "1",
name: "Эталон массы 1 кг",
shortName: "ЭМ-1кг",
type: "Масса",
registryNumber: "ГРСИ 12345-01",
range: "0.5-2 кг",
accuracy: "±0.001 г",
certificateNumber: "СИ-2024-001",
validUntil: "2025-12-31",
},
{
id: "2",
name: "Эталон длины 1 м",
shortName: "ЭД-1м",
type: "Длина",
registryNumber: "ГРСИ 12345-02",
range: "0.5-2 м",
accuracy: "±0.001 мм",
certificateNumber: "СИ-2024-002",
validUntil: "2025-06-30",
},
{
id: "3",
name: "Эталон температуры 20°C",
shortName: "ЭТ-20°C",
type: "Температура",
registryNumber: "ГРСИ 12345-03",
range: "15-25°C",
accuracy: "±0.01°C",
certificateNumber: "СИ-2024-003",
validUntil: "2025-03-15",
},
{
id: "4",
name: "Эталон давления 1 Па",
shortName: "ЭД-1Па",
type: "Давление",
registryNumber: "ГРСИ 12345-04",
range: "0.5-2 Па",
accuracy: "±0.001 Па",
certificateNumber: "СИ-2024-004",
validUntil: "2025-09-20",
},
{
id: "5",
name: "Эталон времени 1 с",
shortName: "ЭВ-1с",
type: "Время",
registryNumber: "ГРСИ 12345-05",
range: "0.5-2 с",
accuracy: "±0.000001 с",
certificateNumber: "СИ-2024-005",
validUntil: "2025-12-01",
},
];
export function StandardsSelector({
isOpen,
onClose,
value = [],
onChange,
registryNumber = "ГРСИ 12345"
}: StandardsSelectorProps) {
const [selectedStandards, setSelectedStandards] = useState<string[]>(value);
const toggleStandard = (standardId: string) => {
setSelectedStandards(prev => {
if (prev.includes(standardId)) {
return prev.filter(id => id !== standardId);
} else if (prev.length < 7) {
return [...prev, standardId];
}
return prev;
});
};
const handleSave = () => {
onChange(selectedStandards);
onClose();
};
const handleClose = () => {
setSelectedStandards(value); // Сбрасываем к исходному значению
onClose();
};
const isExpiringSoon = (validUntil: string) => {
const expiryDate = new Date(validUntil);
const now = new Date();
const monthsUntilExpiry = (expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24 * 30);
return monthsUntilExpiry <= 3;
};
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden">
<DialogHeader>
<div className="flex items-center justify-between">
<div>
<DialogTitle className="text-lg flex items-center gap-2">
<Settings className="w-5 h-5" />
Измерительные эталоны
</DialogTitle>
<p className="text-sm text-muted-foreground mt-1">
ГРСИ: {registryNumber} Выбрано: {selectedStandards.length}/7
</p>
</div>
<Button variant="ghost" size="sm" onClick={handleClose}>
<X className="w-4 h-4" />
</Button>
</div>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-96 overflow-y-auto">
{mockStandards.map((standard) => {
const isSelected = selectedStandards.includes(standard.id);
const isExpiring = isExpiringSoon(standard.validUntil);
return (
<div
key={standard.id}
onClick={() => toggleStandard(standard.id)}
className={`p-3 rounded-lg border cursor-pointer transition-all duration-200 ${
isSelected
? "border-primary bg-primary/10 shadow-sm"
: "border-border hover:border-primary/50 hover:bg-primary/5"
}`}
>
<div className="flex items-start gap-3">
<div className={`mt-1 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-colors ${
isSelected
? "border-primary bg-primary"
: "border-border"
}`}>
{isSelected && <Check className="w-3 h-3 text-primary-foreground" />}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
<div className="font-medium text-sm text-foreground leading-tight">
{standard.shortName}
</div>
<div className="text-xs text-muted-foreground mt-1 line-clamp-2">
{standard.name}
</div>
</div>
<div className="flex flex-col gap-1 shrink-0">
<Badge variant="secondary" className="text-xs">
{standard.accuracy}
</Badge>
{isExpiring && (
<Badge variant="destructive" className="text-xs">
<Calendar className="w-3 h-3 mr-1" />
Истекает
</Badge>
)}
</div>
</div>
<div className="mt-2 space-y-1">
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<FileText className="w-3 h-3" />
{standard.registryNumber}
</div>
<div className="text-xs text-muted-foreground">
Диапазон: {standard.range}
</div>
<div className="text-xs text-muted-foreground">
Действует до: {new Date(standard.validUntil).toLocaleDateString('ru-RU')}
</div>
</div>
</div>
</div>
</div>
);
})}
</div>
<div className="flex items-center justify-between pt-4 border-t">
<div className="text-sm text-muted-foreground">
Максимум 7 эталонов. Выбрано: {selectedStandards.length}
</div>
<div className="flex gap-2">
<Button variant="outline" onClick={handleClose}>
Отмена
</Button>
<Button onClick={handleSave}>
Сохранить
</Button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
}