переименования папок

This commit is contained in:
2025-07-21 15:02:10 +03:00
parent d0c79bd334
commit 84f0c9c6aa
72 changed files with 245 additions and 249 deletions

View File

@@ -0,0 +1,228 @@
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>
);
}