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
This commit is contained in:
Repo Bot
2025-10-19 12:38:19 +02:00
parent 59c4e3857a
commit 6ff041d2a8
35 changed files with 6277 additions and 0 deletions

0
Frontend/.dockerignore Normal file
View File

0
Frontend/Dockerfile Normal file
View File

15
Frontend/next.config.js Normal file
View File

@@ -0,0 +1,15 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: 'standalone',
async rewrites() {
return [
{
source: '/api/v1/:path*',
destination: process.env.NEXT_PUBLIC_API_URL || 'http://backend:8001/api/v1/:path*',
},
];
},
}
module.exports = nextConfig

3213
Frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
Frontend/package.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "diagram-ph-frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@types/node": "^24.8.1",
"@types/react": "^19.2.2",
"axios": "^1.12.2",
"lucide-react": "^0.546.0",
"next": "^15.5.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.65.0",
"recharts": "^3.3.0",
"typescript": "^5.9.3",
"zod": "^4.1.12"
},
"devDependencies": {
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.18"
}
}

View File

@@ -0,0 +1,11 @@
Pressure (bar),Enthalpy (kJ/kg)
3.2,580
3.5,595
11.8,650
12.1,652
12.0,380
11.9,375
3.3,378
8.5,500
5.0,450
7.2,520
1 Pressure (bar) Enthalpy (kJ/kg)
2 3.2 580
3 3.5 595
4 11.8 650
5 12.1 652
6 12.0 380
7 11.9 375
8 3.3 378
9 8.5 500
10 5.0 450
11 7.2 520

View File

@@ -0,0 +1,16 @@
{
"description": "Sample test points for R290 refrigeration cycle",
"refrigerant": "R290",
"points": [
{ "pressure": 3.2, "enthalpy": 580 },
{ "pressure": 3.5, "enthalpy": 595 },
{ "pressure": 11.8, "enthalpy": 650 },
{ "pressure": 12.1, "enthalpy": 652 },
{ "pressure": 12.0, "enthalpy": 380 },
{ "pressure": 11.9, "enthalpy": 375 },
{ "pressure": 3.3, "enthalpy": 378 },
{ "pressure": 8.5, "enthalpy": 500 },
{ "pressure": 5.0, "enthalpy": 450 },
{ "pressure": 7.2, "enthalpy": 520 }
]
}

View File

@@ -0,0 +1,30 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: #0a0e1a;
min-height: 100vh;
color: #f8fafc;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #0a0e1a;
}
::-webkit-scrollbar-thumb {
background: #2d3548;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #3d4558;
}

View File

@@ -0,0 +1,19 @@
import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'Diagram PH - Refrigeration Cycle Calculator',
description: 'Calculate and visualize refrigeration cycles',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}

67
Frontend/src/app/page.tsx Normal file
View File

@@ -0,0 +1,67 @@
"use client";
import { useState } from "react";
import PHDiagramModern from "@/components/PHDiagramModern";
import CycleCalculatorModern from "@/components/CycleCalculatorModern";
export default function Home() {
const [activeView, setActiveView] = useState<'calculator' | 'diagram'>('diagram');
return (
<div style={{ position: 'relative' }}>
<div style={{
position: 'fixed',
top: '2rem',
right: '2rem',
zIndex: 1000,
display: 'flex',
gap: '0.75rem',
background: 'rgba(30, 58, 95, 0.95)',
backdropFilter: 'blur(10px)',
padding: '0.5rem',
borderRadius: '16px',
boxShadow: '0 8px 32px rgba(0,0,0,0.3)',
border: '1px solid rgba(255,255,255,0.1)'
}}>
<button
onClick={() => setActiveView('diagram')}
style={{
padding: '0.75rem 1.5rem',
background: activeView === 'diagram' ? 'linear-gradient(135deg, #38b2ac 0%, #2c7a7b 100%)' : 'transparent',
border: 'none',
borderRadius: '12px',
color: '#ffffff',
fontSize: '0.9rem',
fontWeight: '700',
cursor: 'pointer',
transition: 'all 0.3s',
boxShadow: activeView === 'diagram' ? '0 4px 12px rgba(56, 178, 172, 0.4)' : 'none',
letterSpacing: '0.3px'
}}
>
📊 P-h Diagram
</button>
<button
onClick={() => setActiveView('calculator')}
style={{
padding: '0.75rem 1.5rem',
background: activeView === 'calculator' ? 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)' : 'transparent',
border: 'none',
borderRadius: '12px',
color: '#ffffff',
fontSize: '0.9rem',
fontWeight: '700',
cursor: 'pointer',
transition: 'all 0.3s',
boxShadow: activeView === 'calculator' ? '0 4px 12px rgba(59, 130, 246, 0.4)' : 'none',
letterSpacing: '0.3px'
}}
>
🧮 Cycle Calculator
</button>
</div>
{activeView === 'diagram' && <PHDiagramModern />}
{activeView === 'calculator' && <CycleCalculatorModern />}
</div>
);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
import {
CycleCalculationRequest,
CycleCalculationResponse,
DiagramRequest,
DiagramResponse,
RefrigerantInfo,
RefrigerantsListResponse,
} from '@/types/api';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8001/api/v1';
export class ApiClient {
private baseUrl: string;
constructor(baseUrl: string = API_BASE_URL) {
this.baseUrl = baseUrl;
}
async getRefrigerants(): Promise<RefrigerantInfo[]> {
const response = await fetch(`${this.baseUrl}/refrigerants/`);
if (!response.ok) {
throw new Error('Failed to fetch refrigerants');
}
const data: RefrigerantsListResponse = await response.json();
return data.refrigerants;
}
async calculateCycle(
request: CycleCalculationRequest
): Promise<CycleCalculationResponse> {
const response = await fetch(`${this.baseUrl}/cycles/simple`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to calculate cycle');
}
return response.json();
}
async generateDiagram(request: DiagramRequest): Promise<DiagramResponse> {
const response = await fetch(`${this.baseUrl}/diagrams/ph`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to generate diagram');
}
return response.json();
}
}
export const apiClient = new ApiClient();

84
Frontend/src/types/api.ts Normal file
View File

@@ -0,0 +1,84 @@
export interface CycleCalculationRequest {
refrigerant: string;
evap_temperature?: number;
cond_temperature?: number;
evap_pressure?: number;
cond_pressure?: number;
compressor_efficiency?: number;
superheat?: number;
subcool?: number;
mass_flow?: number;
}
export interface CyclePoint {
point_id: string;
pressure: number;
temperature?: number;
enthalpy?: number;
entropy?: number;
quality?: number;
description?: string;
}
export interface CyclePerformance {
cop: number;
cooling_capacity: number;
heating_capacity: number;
compressor_power: number;
compressor_efficiency: number;
mass_flow: number;
volumetric_flow?: number;
compression_ratio: number;
discharge_temperature: number;
}
export interface CycleCalculationResponse {
success: boolean;
refrigerant: string;
points: CyclePoint[];
performance: CyclePerformance;
diagram_data?: any;
message?: string;
}
export interface DiagramRequest {
refrigerant: string;
pressure_range: {
min: number;
max: number;
};
enthalpy_range?: {
min: number;
max: number;
};
include_isotherms?: boolean;
isotherm_values?: number[];
cycle_points?: Array<{ enthalpy: number; pressure: number }>;
title?: string;
format?: string;
width?: number;
height?: number;
dpi?: number;
}
export interface DiagramResponse {
success: boolean;
image?: string;
data?: any;
metadata: any;
message?: string;
}
export interface RefrigerantInfo {
name: string;
formula?: string;
available: boolean;
loaded?: boolean;
error?: string;
}
export interface RefrigerantsListResponse {
refrigerants: RefrigerantInfo[];
total: number;
available_count: number;
}

24
Frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}