chore: synchronize local fixes to gitea
This commit is contained in:
197
src/client/components/AdhesionGradeSelect.tsx
Normal file
197
src/client/components/AdhesionGradeSelect.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import React, { useState } from 'react';
|
||||
import { HelpCircle, X } from 'lucide-react';
|
||||
import gradeImage from '../assets/grade.jpg';
|
||||
|
||||
interface AdhesionGrade {
|
||||
value: string;
|
||||
label: string;
|
||||
areaRemoved: string;
|
||||
status: 'approved' | 'warning' | 'rejected' | 'critical';
|
||||
statusLabel: string;
|
||||
description: string;
|
||||
spriteY: number;
|
||||
}
|
||||
|
||||
const adhesionGrades: AdhesionGrade[] = [
|
||||
{
|
||||
value: '5B',
|
||||
label: '5B / 5Y',
|
||||
areaRemoved: '0%',
|
||||
status: 'approved',
|
||||
statusLabel: 'Aprovado',
|
||||
description: 'As bordas dos cortes estão completamente lisas; nenhum quadradinho da grade se soltou. A grade parece intacta, apenas riscos finos.',
|
||||
spriteY: 14
|
||||
},
|
||||
{
|
||||
value: '4B',
|
||||
label: '4B / 4Y',
|
||||
areaRemoved: '< 5%',
|
||||
status: 'approved',
|
||||
statusLabel: 'Geralmente Aprovado',
|
||||
description: 'Pequenas lascas de tinta se soltaram nas interseções dos cortes. A área afetada é inferior a 5% da área total da grade.',
|
||||
spriteY: 29
|
||||
},
|
||||
{
|
||||
value: '3B',
|
||||
label: '3B / 3Y',
|
||||
areaRemoved: '5 - 15%',
|
||||
status: 'warning',
|
||||
statusLabel: 'Limite Aceitável',
|
||||
description: 'Pequenas lascas se soltaram ao longo das bordas e nas interseções. As linhas de corte parecem irregulares e alguns cantinhos dos quadrados sumiram.',
|
||||
spriteY: 44
|
||||
},
|
||||
{
|
||||
value: '2B',
|
||||
label: '2B / 2Y',
|
||||
areaRemoved: '15 - 35%',
|
||||
status: 'rejected',
|
||||
statusLabel: 'Geralmente Reprovado',
|
||||
description: 'A tinta descascou ao longo das bordas e em partes dos quadrados. É visível que a tinta está falhando; faixas inteiras ao lado dos cortes podem ter saído.',
|
||||
spriteY: 59
|
||||
},
|
||||
{
|
||||
value: '1B',
|
||||
label: '1B / 1Y',
|
||||
areaRemoved: '35 - 65%',
|
||||
status: 'rejected',
|
||||
statusLabel: 'Reprovado',
|
||||
description: 'A tinta descascou em fitas largas ou quadrados inteiros se soltaram. A grade está muito danificada, com grandes buracos.',
|
||||
spriteY: 74
|
||||
},
|
||||
{
|
||||
value: '0B',
|
||||
label: '0B / 0Y',
|
||||
areaRemoved: '> 65%',
|
||||
status: 'critical',
|
||||
statusLabel: 'Reprovado Crítico',
|
||||
description: 'A descamação e remoção é pior que o grau 1B (mais de 65% da área). A maior parte da tinta na área do teste foi arrancada pela fita.',
|
||||
spriteY: 89
|
||||
}
|
||||
];
|
||||
|
||||
interface AdhesionGradeSelectProps {
|
||||
name: string;
|
||||
label?: string;
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
}
|
||||
|
||||
export const AdhesionGradeSelect: React.FC<AdhesionGradeSelectProps> = ({ name, label, value, onChange }) => {
|
||||
const [showGuide, setShowGuide] = useState(false);
|
||||
|
||||
const getStatusColor = (status: AdhesionGrade['status']) => {
|
||||
switch (status) {
|
||||
case 'approved': return 'text-green-400 bg-green-500/20 border-green-500/30';
|
||||
case 'warning': return 'text-amber-400 bg-amber-500/20 border-amber-500/30';
|
||||
case 'rejected': return 'text-red-400 bg-red-500/20 border-red-500/30';
|
||||
case 'critical': return 'text-red-500 bg-red-600/30 border-red-600/40';
|
||||
default: return 'text-gray-400 bg-gray-500/20 border-gray-500/30';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col gap-1 w-full">
|
||||
{/* Label with help button */}
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
{label && <label className="text-[10px] font-bold text-primary dark:text-primary-light uppercase tracking-[0.15em] ml-1">{label}</label>}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowGuide(true)}
|
||||
className="text-primary hover:text-primary/80 transition-colors"
|
||||
title="Ver guia de classificação ASTM D3359"
|
||||
>
|
||||
<HelpCircle size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<select
|
||||
name={name}
|
||||
title={label || 'Teste de Aderência'}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="flex h-12 w-full rounded-xl border px-4 py-2 text-sm transition-all font-medium shadow-inner cursor-pointer appearance-none outline-none bg-no-repeat bg-[length:1.5em_1.5em] bg-[right_0.75rem_center] bg-[url('data:image/svg+xml,%3Csvg_xmlns=%27http://www.w3.org/2000/svg%27_fill=%27none%27_viewBox=%270_0_20_20%27%3E%3Cpath_stroke=%27%23fb923c%27_stroke-linecap=%27round%27_stroke-linejoin=%27round%27_stroke-width=%271.5%27_d=%27m6_8_4_4_4-4%27/%3E%3C/svg%3E')] bg-[var(--input-bg)] border-[var(--input-border)] text-[var(--input-text)] focus:ring-2 focus:ring-primary/20 focus:border-primary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="">-- Selecione --</option>
|
||||
{adhesionGrades.map((grade) => (
|
||||
<option key={grade.value} value={grade.value}>
|
||||
{grade.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{/* Guide Modal */}
|
||||
{showGuide && (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50"
|
||||
onClick={() => setShowGuide(false)}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="fixed inset-4 sm:inset-8 md:inset-12 lg:inset-20 z-50 flex items-center justify-center">
|
||||
<div className="bg-surface border border-border rounded-2xl shadow-2xl w-full max-w-4xl max-h-full overflow-hidden flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-border bg-surface-soft">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-text-main">Guia de Classificação ASTM D3359</h2>
|
||||
<p className="text-xs text-text-muted">Método B - Teste de Aderência de Corte em Grade</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowGuide(false)}
|
||||
className="p-2 rounded-lg hover:bg-surface-hover text-text-muted hover:text-text-main transition-all"
|
||||
title="Fechar guia"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{/* Grade Image */}
|
||||
<div className="mb-6 rounded-xl overflow-hidden border border-border bg-white">
|
||||
<img
|
||||
src={gradeImage}
|
||||
alt="Guia Visual ASTM D3359"
|
||||
className="w-full object-contain max-h-64 sm:max-h-80"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Grade Cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{adhesionGrades.map((grade) => (
|
||||
<div
|
||||
key={grade.value}
|
||||
className={`p-3 rounded-xl border ${getStatusColor(grade.status)}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-base font-bold">{grade.value}</span>
|
||||
<span className="text-[10px] font-bold uppercase opacity-80">
|
||||
{grade.areaRemoved}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs font-semibold mb-1">{grade.statusLabel}</p>
|
||||
<p className="text-[10px] opacity-80 leading-relaxed">
|
||||
{grade.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Info Note */}
|
||||
<div className="mt-4 p-3 rounded-xl bg-primary/10 border border-primary/20">
|
||||
<p className="text-xs text-text-secondary">
|
||||
<strong className="text-primary">Nota:</strong> A escala ASTM funciona como uma "nota escolar":
|
||||
<strong className="text-green-400"> 5B é a nota máxima (perfeito)</strong> e
|
||||
<strong className="text-red-400"> 0B é a nota mínima (reprovado)</strong>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user