Files
tilbudgivern/frontend/src/App.js
T
alexpolo1 9f7ff8456b Refactor Smart Package Management Service and Frontend Components
- Removed `package_type` from the Smart Package creation process in the backend.
- Updated SQL queries and data handling in `smartPackageManagementService.js` to reflect the removal of `package_type`.
- Simplified the Smart Package form component by removing unnecessary navigation hooks and parameters.
- Adjusted the Smart Packages main component to handle editing and creating packages without direct navigation.
- Enhanced the Smart Packages menu to use callback functions for section changes instead of routing.
- Implemented a new script to create Smart Packages from Ordrestyring cases, including detailed logging and error handling.
- Added a test script to explore cases from Ordrestyring, providing insights into available cases and their details.
- Updated last_sync_status.json with new sync data.
2025-10-24 07:45:19 +00:00

352 lines
12 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { SnackbarProvider } from 'notistack';
import './App.css';
import ProjectFlow from './components/ProjectFlow';
import MaterialsList from './MaterialsList';
import PlanningDashboard from './components/PlanningDashboard';
import AnalyticsDashboard from './components/AnalyticsDashboard';
import SmartPackagesRoutes from './routes/SmartPackagesRoutes';
import './components/ProjectReview.css';
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:4031';
function App() {
const [currentView, setCurrentView] = useState('projects'); // 'projects', 'planning', 'materials', 'openai-usage'
// Authentication state
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [authError, setAuthError] = useState('');
// OpenAI usage state
const [openaiUsage, setOpenaiUsage] = useState(null);
// Check authentication on mount
useEffect(() => {
const authStatus = localStorage.getItem('isAuthenticated');
if (authStatus === 'true') {
setIsAuthenticated(true);
// Load AI usage data when user is already authenticated
loadOpenaiUsage();
}
// Set up global AI usage refresh callback
window.refreshAIUsage = loadOpenaiUsage;
// Cleanup on unmount
return () => {
delete window.refreshAIUsage;
};
}, []);
const handleLogin = async (e) => {
e.preventDefault();
setAuthError('');
try {
const response = await axios.post(`${API_BASE_URL}/api/auth/login`, {
username,
password
});
if (response.data.success) {
setIsAuthenticated(true);
localStorage.setItem('isAuthenticated', 'true');
setUsername('');
setPassword('');
// Load AI usage data after successful login
loadOpenaiUsage();
} else {
setAuthError('Forkert brugernavn eller password');
}
} catch (error) {
setAuthError('Forkert brugernavn eller password');
}
};
const handleLogout = () => {
setIsAuthenticated(false);
localStorage.removeItem('isAuthenticated');
};
const loadOpenaiUsage = async () => {
try {
const response = await axios.get(`${API_BASE_URL}/api/quotes/openai/stats`);
// Transform the data to match expected format
const data = response.data;
if (data.tokenUsage) {
setOpenaiUsage({
current_cost: data.tokenUsage.currentCost,
request_count: data.tokenUsage.requestCount,
total_tokens: data.tokenUsage.totalTokens,
last_updated: data.tokenUsage.actualUsage?.lastUpdated || data.timestamp
});
}
} catch (error) {
console.error('Error loading OpenAI usage:', error);
}
};
if (!isAuthenticated) {
return (
<SnackbarProvider
maxSnack={3}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
autoHideDuration={4000}
>
<div className="login-container">
<div className="login-form">
<h1>🏗️ Tilbudgivern</h1>
<h2>Log ind</h2>
<form onSubmit={handleLogin}>
<div className="form-group">
<label htmlFor="username">Brugernavn</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
{authError && <div className="error">{authError}</div>}
<button type="submit" className="btn btn-primary">
Log ind
</button>
</form>
</div>
</div>
</SnackbarProvider>
);
}
const navigateToCompletedQuotes = () => {
setCurrentView('planning');
};
return (
<SnackbarProvider
maxSnack={3}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
autoHideDuration={4000}
>
<div className={`App ${currentView === 'materials' ? 'materials-mode' : ''}`}>
<header className="header">
<h1>🏗️ Tilbudgivern</h1>
{isAuthenticated && (
<div className="nav-buttons">
<button
className={`nav-btn ${currentView === 'projects' ? 'active' : ''}`}
onClick={() => setCurrentView('projects')}
>
🏗️ Projekt Flow
</button>
<button
className={`nav-btn ${currentView === 'planning' ? 'active' : ''}`}
onClick={() => setCurrentView('planning')}
>
📋 Planlægning
</button>
<button
className={`nav-btn ${currentView === 'materials' ? 'active' : ''}`}
onClick={() => {
setCurrentView('materials');
}}
>
🧱 Materialer
</button>
<button
className={`nav-btn ${currentView === 'smart-packages' ? 'active' : ''}`}
onClick={() => {
setCurrentView('smart-packages');
}}
>
📦 Smart Pakker
</button>
<button
className={`nav-btn ${currentView === 'noegletal' ? 'active' : ''}`}
onClick={() => setCurrentView('noegletal')}
>
📊 Nøgletal
</button>
<button
className={`nav-btn ${currentView === 'openai-usage' ? 'active' : ''}`}
onClick={() => {
setCurrentView('openai-usage');
loadOpenaiUsage();
}}
>
🤖 AI Budget
</button>
</div>
)}
{isAuthenticated && (
<button className="logout-btn" onClick={handleLogout}>
Log ud
</button>
)}
</header>
<main className="main-content">
{currentView === 'projects' ? (
// Structured project flow
<ProjectFlow
apiBaseUrl={API_BASE_URL}
onNavigateToCompletedQuotes={navigateToCompletedQuotes}
/>
) : currentView === 'materials' ? (
// Materials Management with new design
<MaterialsList apiBaseUrl={API_BASE_URL} />
) : currentView === 'planning' ? (
// Planning Dashboard with orders and calendar
<PlanningDashboard />
) : currentView === 'noegletal' ? (
// Analytics Dashboard with business intelligence
<AnalyticsDashboard apiBaseUrl={API_BASE_URL} />
) : currentView === 'smart-packages' ? (
// Smart Packages Management
<SmartPackagesRoutes />
) : currentView === 'openai-usage' ? (
// OpenAI Usage section
<div className="openai-usage-view">
<div className="usage-section">
<h2>🤖 OpenAI Budget</h2>
<p>Oversigt over AI forbrug og omkostninger</p>
{openaiUsage ? (
<div className="usage-stats-grid">
<div className="stat-card primary-stat">
<div className="stat-icon">💰</div>
<div className="stat-content">
<h3>Aktuelle omkostninger</h3>
<p className="stat-value large">
${(() => {
const cost = openaiUsage.current_cost || 0;
// Smart formatting: remove trailing zeros
return cost.toFixed(4).replace(/\.?0+$/, '') || '0';
})()}
</p>
<span className="stat-label">USD total</span>
</div>
</div>
<div className="stat-card">
<div className="stat-icon">📊</div>
<div className="stat-content">
<h3>Antal requests</h3>
<p className="stat-value">{openaiUsage.request_count || 0}</p>
<span className="stat-label">API kald</span>
</div>
</div>
<div className="stat-card">
<div className="stat-icon">🔢</div>
<div className="stat-content">
<h3>Total tokens</h3>
<p className="stat-value">{openaiUsage.total_tokens?.toLocaleString() || 0}</p>
<span className="stat-label">tokens brugt</span>
</div>
</div>
<div className="stat-card">
<div className="stat-icon">⏰</div>
<div className="stat-content">
<h3>Sidst opdateret</h3>
<p className="stat-value small">
{openaiUsage.last_updated ?
new Date(openaiUsage.last_updated).toLocaleString('da-DK') :
'Ingen data'
}
</p>
<span className="stat-label">timestamp</span>
</div>
</div>
{/* Additional cost breakdown if available */}
{openaiUsage.tokenUsage && (
<div className="stat-card wide-stat">
<div className="stat-icon">📈</div>
<div className="stat-content">
<h3>Token fordeling</h3>
<div className="token-breakdown">
<div className="token-item">
<span>Input tokens:</span>
<strong>{openaiUsage.tokenUsage.promptTokens?.toLocaleString() || 0}</strong>
</div>
<div className="token-item">
<span>Output tokens:</span>
<strong>{openaiUsage.tokenUsage.completionTokens?.toLocaleString() || 0}</strong>
</div>
<div className="token-item total">
<span>Total:</span>
<strong>{openaiUsage.tokenUsage.totalTokens?.toLocaleString() || 0}</strong>
</div>
</div>
</div>
</div>
)}
{/* Cost in DKK approximation */}
<div className="stat-card secondary-stat">
<div className="stat-icon">🇩🇰</div>
<div className="stat-content">
<h3>Omkostninger (ca.)</h3>
<p className="stat-value medium">
{(() => {
const dkkCost = (openaiUsage.current_cost || 0) * 6.8;
// Smart formatting for DKK: show more decimals for small amounts
if (dkkCost < 0.01) {
return dkkCost.toFixed(4).replace(/\.?0+$/, '') + ' kr';
} else if (dkkCost < 1) {
return dkkCost.toFixed(3).replace(/\.?0+$/, '') + ' kr';
} else {
return dkkCost.toFixed(2) + ' kr';
}
})()}
</p>
<span className="stat-label">Omregnet til DKK</span>
</div>
</div>
</div>
) : (
<div className="loading-state">
<div className="loading-spinner"></div>
<p>Indlæser AI usage data...</p>
</div>
)}
</div>
</div>
) : null}
</main>
</div>
</SnackbarProvider>
);
}
export default App;