diagram_ph/Frontend/src/components/CycleCalculatorModern.tsx
Repo Bot 6ff041d2a8 feat: Add Docker Compose setup for fullstack deployment
- Add Frontend Dockerfile with Next.js standalone build
- Add docker-compose.yml for production deployment
- Add docker-compose.dev.yml for development with hot-reload
- Configure Frontend next.config.js with standalone output
- Add .dockerignore files for both backend and frontend
- Add comprehensive README-DOCKER.md documentation
- Update .gitignore to exclude node_modules and build artifacts
- Remove obsolete component files (CycleCalculator.tsx, PHDiagram.tsx)
- Backend and Frontend communicate via Docker network
- Healthchecks configured for both services
- Environment variables configured for API URL
2025-10-19 12:38:19 +02:00

1068 lines
39 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect, useCallback } from 'react';
import { apiClient } from '@/lib/api-client';
import type { CycleCalculationRequest, CycleCalculationResponse, RefrigerantInfo } from '@/types/api';
export default function CycleCalculatorModern() {
const [refrigerants, setRefrigerants] = useState<RefrigerantInfo[]>([]);
const [selectedRefrigerant, setSelectedRefrigerant] = useState('R290');
const [cycleData, setCycleData] = useState<CycleCalculationResponse | null>(null);
const [inputType, setInputType] = useState<'temperature' | 'pressure'>('pressure');
const [evaporatingValue, setEvaporatingValue] = useState('3');
const [condensingValue, setCondensingValue] = useState('12');
const [superheating, setSuperheating] = useState('0');
const [subcooling, setSubcooling] = useState('2');
const [massFlowRate, setMassFlowRate] = useState('1.0');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
apiClient.getRefrigerants()
.then(setRefrigerants)
.catch(err => {
console.error('Failed to load refrigerants:', err);
setError('Cannot connect to API on port 8001');
});
}, []);
const handleCalculate = useCallback(async () => {
if (!selectedRefrigerant) return;
setLoading(true);
setError(null);
try {
const req: CycleCalculationRequest = {
refrigerant: selectedRefrigerant,
superheat: parseFloat(superheating) || 0,
subcool: parseFloat(subcooling) || 0,
compressor_efficiency: 0.7,
mass_flow: parseFloat(massFlowRate) || 1.0,
} as CycleCalculationRequest;
if (inputType === 'temperature') {
req.evap_temperature = parseFloat(evaporatingValue) || 0;
req.cond_temperature = parseFloat(condensingValue) || 0;
} else {
req.evap_pressure = parseFloat(evaporatingValue) || 1;
req.cond_pressure = parseFloat(condensingValue) || 1;
}
const response = await apiClient.calculateCycle(req);
setCycleData(response);
// dispatch overlay event
try {
if (response && response.points && response.points.length > 0) {
const cyclePoints = response.points.map(p => ({ enthalpy: p.enthalpy || 0, pressure: p.pressure }));
const ev = new CustomEvent('cycle:calculated', { detail: { refrigerant: selectedRefrigerant, points: cyclePoints } });
window.dispatchEvent(ev);
}
} catch (_) {}
} catch (err: any) {
setError(err?.message || String(err));
} finally {
setLoading(false);
}
}, [selectedRefrigerant, inputType, evaporatingValue, condensingValue, massFlowRate, superheating, subcooling]);
// debounce 200ms
useEffect(() => {
let mounted = true;
const id = setTimeout(() => { if (mounted) handleCalculate(); }, 200);
return () => { mounted = false; clearTimeout(id); };
}, [selectedRefrigerant, inputType, evaporatingValue, condensingValue, massFlowRate, superheating, subcooling, handleCalculate]);
// Auto-calculate on initial load
useEffect(() => {
if (refrigerants.length > 0 && selectedRefrigerant && !cycleData) {
handleCalculate();
}
}, [refrigerants.length, selectedRefrigerant]);
const coolingCapacity = cycleData?.performance?.cooling_capacity ?? 0;
const compressorPower = cycleData?.performance?.compressor_power ?? 0;
return (
<>
<style jsx>{`
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
`}</style>
<div style={{ minHeight: '100vh', background: 'linear-gradient(135deg, #3a5a77 0%, #4a2c5e 100%)', display: 'flex', fontFamily: '"Inter", system-ui, -apple-system, sans-serif' }}>
{/* SIDEBAR - exact copy from PHDiagramModern */}
<aside style={{
width: '320px',
background: 'rgba(42, 74, 95, 0.95)',
backdropFilter: 'blur(10px)',
padding: '2rem 1.5rem',
boxShadow: '4px 0 24px rgba(0,0,0,0.4)',
display: 'flex',
flexDirection: 'column',
gap: '1.5rem',
overflowY: 'auto',
borderRight: '1px solid rgba(255,255,255,0.1)'
}}>
{/* HEADER PILL - exact copy */}
<div style={{
background: 'linear-gradient(135deg, rgba(37, 99, 235, 0.18) 0%, rgba(59, 130, 246, 0.08) 100%)',
padding: '1rem 1.25rem',
borderRadius: '12px',
border: '1px solid rgba(59, 130, 246, 0.26)',
cursor: 'pointer',
transition: 'all 0.3s'
}}>
<h2 style={{
color: '#ffffff',
fontSize: '1.3rem',
fontWeight: '700',
margin: '0',
letterSpacing: '0.3px'
}}>Cycle Calculator</h2>
<div style={{
marginTop: '0.25rem',
fontSize: '0.75rem',
color: 'rgba(255,255,255,0.7)',
fontWeight: '400'
}}>
{cycleData && `COP = ${cycleData.performance?.cop?.toFixed(2) || '-'}`}
</div>
</div>
{error && (
<div style={{
padding: '0.75rem 1rem',
background: 'rgba(239, 68, 68, 0.2)',
border: '1px solid rgba(239, 68, 68, 0.4)',
borderRadius: '10px',
color: '#fca5a5',
fontSize: '0.85rem',
lineHeight: '1.4'
}}>
{error}
</div>
)}
<div>
<label style={{ display: 'block', color: '#ffffff', fontSize: '0.9rem', fontWeight: '600', marginBottom: '0.6rem', letterSpacing: '0.2px' }}>
Refrigerant
</label>
<select
value={selectedRefrigerant}
onChange={(e) => setSelectedRefrigerant(e.target.value)}
style={{
width: '100%',
padding: '0.8rem 1rem',
background: 'rgba(15, 23, 42, 0.9)',
border: '1.5px solid rgba(148, 163, 184, 0.4)',
borderRadius: '10px',
color: '#ffffff',
fontSize: '0.95rem',
fontWeight: '600',
cursor: 'pointer',
outline: 'none',
marginTop: 8
}}
>
{refrigerants.filter(r => r.available).map(r => (
<option key={r.name} value={r.name} style={{ background: '#0f172a' }}>{r.name}{r.formula ? ` - ${r.formula}` : ''}</option>
))}
</select>
</div>
<div>
<label style={{ display: 'block', color: '#ffffff', fontSize: '0.9rem', fontWeight: '600', marginBottom: '0.6rem', letterSpacing: '0.2px' }}>
Define State Cycle
</label>
<select
value={inputType}
onChange={(e) => setInputType(e.target.value as 'temperature' | 'pressure')}
style={{
width: '100%',
padding: '0.8rem 1rem',
background: 'rgba(15, 23, 42, 0.9)',
border: '1.5px solid rgba(148, 163, 184, 0.4)',
borderRadius: '10px',
color: '#ffffff',
fontSize: '0.95rem',
fontWeight: '600',
cursor: 'pointer',
outline: 'none'
}}
>
<option value="pressure" style={{ background: '#0f172a' }}>Pressure levels (Evap./Cond.)</option>
<option value="temperature" style={{ background: '#0f172a' }}>Temperature levels (Evap./Cond.)</option>
</select>
</div>
{/* Condensing Pressure/Temperature - EXACT STYLE from PHDiagramModern */}
<div>
<label style={{
display: 'block',
color: '#ffffff',
fontSize: '0.9rem',
fontWeight: '600',
marginBottom: '0.8rem',
letterSpacing: '0.2px'
}}>
{inputType === 'pressure' ? 'Condensing Pressure' : 'Condensing Temperature'}
</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<input
type="range"
min={inputType === 'pressure' ? '5' : '20'}
max={inputType === 'pressure' ? '30' : '70'}
step={inputType === 'pressure' ? '1' : '5'}
value={condensingValue}
onChange={(e) => setCondensingValue(e.target.value)}
style={{
flex: 1,
height: '5px',
background: 'linear-gradient(90deg, rgba(236, 72, 153, 0.4) 0%, rgba(236, 72, 153, 0.9) 100%)',
borderRadius: '5px',
outline: 'none',
cursor: 'pointer',
appearance: 'none'
}}
/>
<div style={{
minWidth: '90px',
padding: '0.6rem 0.9rem',
background: 'rgba(30, 58, 80, 0.95)',
border: '1.5px solid rgba(148, 163, 184, 0.35)',
borderRadius: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '0.3rem'
}}>
<input
type="number"
value={condensingValue}
onChange={(e) => setCondensingValue(e.target.value)}
step="1"
style={{
width: '50px',
background: 'transparent',
border: 'none',
color: '#ffffff',
fontSize: '1.05rem',
fontWeight: '700',
textAlign: 'right',
outline: 'none',
padding: 0,
margin: 0,
WebkitAppearance: 'none',
MozAppearance: 'textfield'
}}
/>
<span style={{ fontSize: '0.75rem', color: '#94a3b8', fontWeight: '500' }}>
{inputType === 'pressure' ? 'bar' : '°C'}
</span>
</div>
</div>
</div>
{/* Evaporating Pressure/Temperature - EXACT STYLE from PHDiagramModern */}
<div>
<label style={{
display: 'block',
color: '#ffffff',
fontSize: '0.9rem',
fontWeight: '600',
marginBottom: '0.8rem',
letterSpacing: '0.2px'
}}>
{inputType === 'pressure' ? 'Evaporating Pressure' : 'Evaporating Temperature'}
</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<input
type="range"
min={inputType === 'pressure' ? '1' : '-40'}
max={inputType === 'pressure' ? '15' : '40'}
step={inputType === 'pressure' ? '1' : '5'}
value={evaporatingValue}
onChange={(e) => setEvaporatingValue(e.target.value)}
style={{
flex: 1,
height: '5px',
background: 'linear-gradient(90deg, rgba(59, 130, 246, 0.4) 0%, rgba(59, 130, 246, 0.9) 100%)',
borderRadius: '5px',
outline: 'none',
cursor: 'pointer',
appearance: 'none'
}}
/>
<div style={{
minWidth: '90px',
padding: '0.6rem 0.9rem',
background: 'rgba(30, 58, 80, 0.95)',
border: '1.5px solid rgba(148, 163, 184, 0.35)',
borderRadius: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '0.3rem'
}}>
<input
type="number"
value={evaporatingValue}
onChange={(e) => setEvaporatingValue(e.target.value)}
step="1"
style={{
width: '50px',
background: 'transparent',
border: 'none',
color: '#ffffff',
fontSize: '1.05rem',
fontWeight: '700',
textAlign: 'right',
outline: 'none',
padding: 0,
margin: 0,
WebkitAppearance: 'none',
MozAppearance: 'textfield'
}}
/>
<span style={{ fontSize: '0.75rem', color: '#94a3b8', fontWeight: '500' }}>
{inputType === 'pressure' ? 'bar' : '°C'}
</span>
</div>
</div>
</div>
{/* Mass Flow Rate - EXACT STYLE from PHDiagramModern */}
<div>
<label style={{
display: 'block',
color: '#ffffff',
fontSize: '0.9rem',
fontWeight: '600',
marginBottom: '0.8rem',
letterSpacing: '0.2px'
}}>
Mass Flow Rate
</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<input
type="range"
min="0.1"
max="5"
step="0.1"
value={massFlowRate}
onChange={(e) => setMassFlowRate(e.target.value)}
style={{
flex: 1,
height: '5px',
background: 'linear-gradient(90deg, rgba(168, 85, 247, 0.4) 0%, rgba(168, 85, 247, 0.9) 100%)',
borderRadius: '5px',
outline: 'none',
cursor: 'pointer',
appearance: 'none'
}}
/>
<div style={{
minWidth: '90px',
padding: '0.6rem 0.9rem',
background: 'rgba(30, 58, 80, 0.95)',
border: '1.5px solid rgba(148, 163, 184, 0.35)',
borderRadius: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '0.3rem'
}}>
<input
type="number"
value={massFlowRate}
onChange={(e) => setMassFlowRate(e.target.value)}
step="0.1"
style={{
width: '50px',
background: 'transparent',
border: 'none',
color: '#ffffff',
fontSize: '1.05rem',
fontWeight: '700',
textAlign: 'right',
outline: 'none',
padding: 0,
margin: 0,
WebkitAppearance: 'none',
MozAppearance: 'textfield'
}}
/>
<span style={{ fontSize: '0.75rem', color: '#94a3b8', fontWeight: '500' }}>
kg/s
</span>
</div>
</div>
</div>
{/* Superheating - EXACT STYLE from PHDiagramModern */}
<div>
<label style={{
display: 'block',
color: '#ffffff',
fontSize: '0.9rem',
fontWeight: '600',
marginBottom: '0.8rem',
letterSpacing: '0.2px'
}}>
Superheating
</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<input
type="range"
min="0"
max="20"
step="1"
value={superheating}
onChange={(e) => setSuperheating(e.target.value)}
style={{
flex: 1,
height: '5px',
background: 'linear-gradient(90deg, rgba(34, 197, 94, 0.4) 0%, rgba(34, 197, 94, 0.9) 100%)',
borderRadius: '5px',
outline: 'none',
cursor: 'pointer',
appearance: 'none'
}}
/>
<div style={{
minWidth: '90px',
padding: '0.6rem 0.9rem',
background: 'rgba(30, 58, 80, 0.95)',
border: '1.5px solid rgba(148, 163, 184, 0.35)',
borderRadius: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '0.3rem'
}}>
<input
type="number"
value={superheating}
onChange={(e) => setSuperheating(e.target.value)}
step="1"
style={{
width: '50px',
background: 'transparent',
border: 'none',
color: '#ffffff',
fontSize: '1.05rem',
fontWeight: '700',
textAlign: 'right',
outline: 'none',
padding: 0,
margin: 0,
WebkitAppearance: 'none',
MozAppearance: 'textfield'
}}
/>
<span style={{ fontSize: '0.75rem', color: '#94a3b8', fontWeight: '500' }}>
°C
</span>
</div>
</div>
</div>
{/* Subcooling - EXACT STYLE from PHDiagramModern */}
<div>
<label style={{
display: 'block',
color: '#ffffff',
fontSize: '0.9rem',
fontWeight: '600',
marginBottom: '0.8rem',
letterSpacing: '0.2px'
}}>
Subcooling
</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<input
type="range"
min="0"
max="20"
step="1"
value={subcooling}
onChange={(e) => setSubcooling(e.target.value)}
style={{
flex: 1,
height: '5px',
background: 'linear-gradient(90deg, rgba(249, 115, 22, 0.4) 0%, rgba(249, 115, 22, 0.9) 100%)',
borderRadius: '5px',
outline: 'none',
cursor: 'pointer',
appearance: 'none'
}}
/>
<div style={{
minWidth: '90px',
padding: '0.6rem 0.9rem',
background: 'rgba(30, 58, 80, 0.95)',
border: '1.5px solid rgba(148, 163, 184, 0.35)',
borderRadius: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '0.3rem'
}}>
<input
type="number"
value={subcooling}
onChange={(e) => setSubcooling(e.target.value)}
step="1"
style={{
width: '50px',
background: 'transparent',
border: 'none',
color: '#ffffff',
fontSize: '1.05rem',
fontWeight: '700',
textAlign: 'right',
outline: 'none',
padding: 0,
margin: 0,
WebkitAppearance: 'none',
MozAppearance: 'textfield'
}}
/>
<span style={{ fontSize: '0.75rem', color: '#94a3b8', fontWeight: '500' }}>
°C
</span>
</div>
</div>
</div>
<div style={{ marginTop: 'auto' }}>
<div style={{ fontSize: 12, color: 'rgba(255,255,255,0.6)' }}>Auto-update enabled (debounced)</div>
</div>
</aside>
{/* MAIN AREA - EXACT COPY from PHDiagramModern structure */}
<main style={{
flex: 1,
padding: '2rem',
overflowY: 'auto',
background: '#f8fafc'
}}>
{cycleData && (
<div style={{ maxWidth: '1400px', margin: '0 auto' }}>
{/* Header section - EXACT COPY from PHDiagramModern */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1.5rem'
}}>
<h3 style={{
color: '#1e293b',
fontSize: '1.5rem',
fontWeight: '700',
margin: 0
}}>
Thermodynamic Cycle {cycleData.refrigerant}
</h3>
</div>
{/* COP Info - EXACT COPY from PHDiagramModern */}
{cycleData.performance && (
<div style={{
fontSize: '0.85rem',
color: '#64748b',
marginBottom: '1.5rem',
fontWeight: '500'
}}>
COP (Heat Pump) = {cycleData.performance.cop.toFixed(2)} / COP (Refrigerator) = {(cycleData.performance.cop - 1).toFixed(2)}
</div>
)}
{/* Performance Cards - ELEGANT WHITE DESIGN */}
<div style={{ marginBottom: '2.5rem' }}>
<h3 style={{
color: '#1e293b',
fontSize: '1.5rem',
fontWeight: '700',
marginBottom: '1.5rem',
marginTop: 0,
letterSpacing: '-0.025em',
display: 'flex',
alignItems: 'center',
gap: '0.5rem'
}}>
<span style={{ fontSize: '1.75rem' }}></span>
Performance Metrics
</h3>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
gap: '1.25rem'
}}>
{/* COP Card */}
<div style={{
background: '#ffffff',
padding: '1.5rem',
borderRadius: '16px',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
border: '1px solid rgba(0, 0, 0, 0.05)',
transition: 'all 0.3s ease',
cursor: 'default'
}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
marginBottom: '1rem'
}}>
<div style={{
width: '48px',
height: '48px',
borderRadius: '12px',
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '1.5rem',
boxShadow: '0 4px 12px rgba(16, 185, 129, 0.3)'
}}>
🎯
</div>
<div>
<div style={{
color: '#64748b',
fontSize: '0.75rem',
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: '0.05em'
}}>
COP
</div>
<div style={{
fontSize: '0.875rem',
color: '#94a3b8',
fontWeight: '500'
}}>
Coefficient
</div>
</div>
</div>
<div style={{
fontSize: '2.25rem',
fontWeight: '800',
color: '#10b981',
lineHeight: 1,
letterSpacing: '-0.025em'
}}>
{cycleData.performance.cop.toFixed(2)}
</div>
</div>
{/* Cooling Capacity Card */}
<div style={{
background: '#ffffff',
padding: '1.5rem',
borderRadius: '16px',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
border: '1px solid rgba(0, 0, 0, 0.05)',
transition: 'all 0.3s ease'
}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
marginBottom: '1rem'
}}>
<div style={{
width: '48px',
height: '48px',
borderRadius: '12px',
background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '1.5rem',
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.3)'
}}>
</div>
<div>
<div style={{
color: '#64748b',
fontSize: '0.75rem',
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: '0.05em'
}}>
Cooling
</div>
<div style={{
fontSize: '0.875rem',
color: '#94a3b8',
fontWeight: '500'
}}>
Capacity
</div>
</div>
</div>
<div style={{
fontSize: '2.25rem',
fontWeight: '800',
color: '#3b82f6',
lineHeight: 1,
letterSpacing: '-0.025em'
}}>
{coolingCapacity.toFixed(1)}
<span style={{
fontSize: '1rem',
color: '#94a3b8',
fontWeight: '600',
marginLeft: '0.5rem'
}}>
kW
</span>
</div>
</div>
{/* Compressor Power Card */}
<div style={{
background: '#ffffff',
padding: '1.5rem',
borderRadius: '16px',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
border: '1px solid rgba(0, 0, 0, 0.05)',
transition: 'all 0.3s ease'
}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
marginBottom: '1rem'
}}>
<div style={{
width: '48px',
height: '48px',
borderRadius: '12px',
background: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '1.5rem',
boxShadow: '0 4px 12px rgba(239, 68, 68, 0.3)'
}}>
</div>
<div>
<div style={{
color: '#64748b',
fontSize: '0.75rem',
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: '0.05em'
}}>
Power
</div>
<div style={{
fontSize: '0.875rem',
color: '#94a3b8',
fontWeight: '500'
}}>
Compressor
</div>
</div>
</div>
<div style={{
fontSize: '2.25rem',
fontWeight: '800',
color: '#ef4444',
lineHeight: 1,
letterSpacing: '-0.025em'
}}>
{compressorPower.toFixed(1)}
<span style={{
fontSize: '1rem',
color: '#94a3b8',
fontWeight: '600',
marginLeft: '0.5rem'
}}>
kW
</span>
</div>
</div>
{/* Compression Ratio Card */}
<div style={{
background: '#ffffff',
padding: '1.5rem',
borderRadius: '16px',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
border: '1px solid rgba(0, 0, 0, 0.05)',
transition: 'all 0.3s ease'
}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
marginBottom: '1rem'
}}>
<div style={{
width: '48px',
height: '48px',
borderRadius: '12px',
background: 'linear-gradient(135deg, #06b6d4 0%, #0891b2 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '1.5rem',
boxShadow: '0 4px 12px rgba(6, 182, 212, 0.3)'
}}>
📊
</div>
<div>
<div style={{
color: '#64748b',
fontSize: '0.75rem',
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: '0.05em'
}}>
Ratio
</div>
<div style={{
fontSize: '0.875rem',
color: '#94a3b8',
fontWeight: '500'
}}>
Compression
</div>
</div>
</div>
<div style={{
fontSize: '2.25rem',
fontWeight: '800',
color: '#06b6d4',
lineHeight: 1,
letterSpacing: '-0.025em'
}}>
{cycleData.performance.compression_ratio.toFixed(2)}
</div>
</div>
</div>
</div>
{/* Cycle State Points - ELEGANT WHITE CARDS */}
<div style={{ marginBottom: '2rem' }}>
<h3 style={{
color: '#1e293b',
fontSize: '1.5rem',
fontWeight: '700',
marginBottom: '1.5rem',
marginTop: 0,
letterSpacing: '-0.025em',
display: 'flex',
alignItems: 'center',
gap: '0.5rem'
}}>
<span style={{ fontSize: '1.75rem' }}>🔄</span>
Cycle State Points
</h3>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
gap: '1.25rem'
}}>
{cycleData.points.map((p, index) => {
// Define icons and colors for each point
const pointStyles = [
{ icon: '🌡️', color: '#3b82f6', bg: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', label: 'Point 1' },
{ icon: '🔥', color: '#ef4444', bg: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)', label: 'Point 2' },
{ icon: '💧', color: '#10b981', bg: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', label: 'Point 3' },
{ icon: '❄️', color: '#06b6d4', bg: 'linear-gradient(135deg, #06b6d4 0%, #0891b2 100%)', label: 'Point 4' }
];
const style = pointStyles[index] || pointStyles[0];
return (
<div
key={p.point_id}
style={{
background: '#ffffff',
padding: '1.5rem',
borderRadius: '16px',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
border: '1px solid rgba(0, 0, 0, 0.05)',
transition: 'all 0.3s ease',
position: 'relative',
overflow: 'hidden'
}}
>
{/* Decorative gradient bar */}
<div style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '4px',
background: style.bg
}} />
{/* Header with icon */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
marginBottom: '1rem',
paddingTop: '0.25rem'
}}>
<div style={{
width: '40px',
height: '40px',
borderRadius: '10px',
background: style.bg,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '1.25rem',
boxShadow: `0 4px 12px ${style.color}40`
}}>
{style.icon}
</div>
<div style={{ flex: 1 }}>
<div style={{
fontSize: '0.875rem',
fontWeight: '700',
color: '#1e293b',
marginBottom: '0.125rem'
}}>
{p.description}
</div>
<div style={{
fontSize: '0.75rem',
color: '#94a3b8',
fontWeight: '500'
}}>
{style.label}
</div>
</div>
</div>
{/* Properties Grid */}
<div style={{
display: 'grid',
gap: '0.75rem'
}}>
{/* Pressure */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.75rem',
background: '#f8fafc',
borderRadius: '10px',
border: '1px solid #e2e8f0'
}}>
<span style={{
fontSize: '0.875rem',
fontWeight: '600',
color: '#64748b'
}}>
Pressure
</span>
<span style={{
fontSize: '1rem',
fontWeight: '700',
color: '#1e293b'
}}>
{p.pressure.toFixed(2)} <span style={{ fontSize: '0.875rem', color: '#94a3b8', fontWeight: '600' }}>bar</span>
</span>
</div>
{/* Temperature */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.75rem',
background: '#f8fafc',
borderRadius: '10px',
border: '1px solid #e2e8f0'
}}>
<span style={{
fontSize: '0.875rem',
fontWeight: '600',
color: '#64748b'
}}>
Temperature
</span>
<span style={{
fontSize: '1rem',
fontWeight: '700',
color: '#1e293b'
}}>
{(p.temperature || 0).toFixed(1)} <span style={{ fontSize: '0.875rem', color: '#94a3b8', fontWeight: '600' }}>°C</span>
</span>
</div>
{/* Enthalpy */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.75rem',
background: '#f8fafc',
borderRadius: '10px',
border: '1px solid #e2e8f0'
}}>
<span style={{
fontSize: '0.875rem',
fontWeight: '600',
color: '#64748b'
}}>
Enthalpy
</span>
<span style={{
fontSize: '1rem',
fontWeight: '700',
color: '#1e293b'
}}>
{(p.enthalpy || 0).toFixed(1)} <span style={{ fontSize: '0.875rem', color: '#94a3b8', fontWeight: '600' }}>kJ/kg</span>
</span>
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
)}
{!cycleData && (
<div style={{
maxWidth: '1400px',
margin: '0 auto',
background: '#fff',
padding: '3rem',
borderRadius: 16,
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
textAlign: 'center'
}}>
<div style={{ fontSize: 14, color: '#94a3b8' }}>
{loading ? 'Calculating cycle...' : 'Adjust sliders — results will appear here (auto-calculated)'}
</div>
</div>
)}
</main>
</div>
</>
);
}