feat(all): write new functionalities with cursor AI

add all dashboards + add routing + change styles + prepare listGenerator component which works
correctly + add progress bars + add colors for objects statuses + add mocked data + etc
feat/x_gpu/chat_gpt_new_version
TBS093A 2025-03-04 14:58:26 +01:00
parent 96606b2231
commit e8c234ce51
12 changed files with 2467 additions and 233 deletions

View File

@ -6,6 +6,7 @@ import LoginPage from './pages/auth/login';
import RegisterPage from './pages/auth/register';
import DashboardPage from './pages/dashboard';
import LandingPage from './pages/landing';
import NotFoundPage from './pages/404';
import FormLogin from './pages/FormLogin';
import FormRegister from './pages/FormRegister';
@ -49,7 +50,7 @@ function App() {
<Route path="/models" element={<FormModels />} />
<Route path="/renders" element={<FormRenders />} />
<Route path="/ai-tasks" element={<FormAi />} />
<Route path="*" element={<div>Not Found</div>} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Router>
);

View File

@ -1,159 +1,237 @@
import React, { useState } from 'react';
/**
* Generic List Generator
* Generic List Generator Component
*
* @param {Array} data - Array of items to display.
* @param {ReactElement|Function} create_component - Shown when user clicks "Create +".
* @param {ReactElement|Function} update_component - Shown when user clicks "Update".
* @param {Function} delete_action - Called when user clicks "Delete".
* @param {Array} data - Array of items to display
* @param {string} title - Title of the list
* @param {ReactElement|Function} onCreate - Component shown when user clicks "Create +"
* @param {ReactElement|Function} onUpdate - Component shown when user clicks "Update"
* @param {Function} onDelete - Function called when user clicks "Delete"
* @param {Function} onRefresh - Optional function to refresh the list
*/
export const ListGenerator = ({
data = [],
create_component = null,
update_component = null,
delete_action = null,
title,
onCreate = null,
onUpdate = null,
onDelete = null,
onRefresh = null
}) => {
const columns = data.length > 0 ? Object.keys(data[0]) : [];
var buttons = 0
if(create_component !== null) {
buttons++;
}
if(update_component !== null) {
buttons++;
}
if(delete_action !== null) {
buttons++;
}
const columns_count = 100 / (columns.length + buttons);
// Controls whether the "create" form is visible
const columns = data.length > 0
? Object.keys(data[0]).filter(key => !key.toLowerCase().includes('id'))
: [];
const [createVisible, setCreateVisible] = useState(false);
// Track which item is being updated (store an ID or index, or null if none)
const [itemBeingUpdated, setItemBeingUpdated] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
// Toggle the "create" form
const handleToggleCreate = () => {
setCreateVisible((prev) => !prev);
// If user opens the create form, you might also want to close any update forms
setItemBeingUpdated(null);
setSelectedItem(null);
};
// Toggle update form for a specific item
const handleToggleUpdate = (itemId) => {
setItemBeingUpdated((prev) => (prev === itemId ? null : itemId));
// Also close create if it's open
setCreateVisible(false);
};
// Handle delete
const handleDelete = (item) => {
if (typeof delete_action === 'function') {
delete_action(item);
// Handle delete with confirmation
const handleDelete = async (item) => {
if (window.confirm('Are you sure you want to delete this item?')) {
setIsLoading(true);
try {
if (typeof onDelete === 'function') {
await onDelete(item);
if (onRefresh) {
await onRefresh();
}
}
} catch (error) {
console.error('Error deleting item:', error);
alert('Failed to delete item. Please try again.');
} finally {
setIsLoading(false);
}
}
};
// Helper to render a component if its a function or a React element
// Handle refresh
const handleRefresh = async () => {
if (onRefresh) {
setIsLoading(true);
try {
await onRefresh();
} catch (error) {
console.error('Error refreshing list:', error);
alert('Failed to refresh list. Please try again.');
} finally {
setIsLoading(false);
}
}
};
// Handle item selection
const handleItemClick = (item) => {
setSelectedItem(item);
};
// Helper to render a component if it's a function or a React element
const renderComponent = (component, props = {}) => {
// If it's a function, call it
if (typeof component === 'function') {
return component(props);
}
// If it's a React element, clone it to pass in new props (optional)
return React.cloneElement(component, props);
};
return (
<div className="list-generator-container">
{create_component && (
<div className="create-section">
<button onClick={handleToggleCreate}>
{createVisible ? 'Close' : 'Create +'}
</button>
{createVisible && (
<div className="create-form">
{renderComponent(create_component)}
</div>
<div className="table-header">
{title && <h2>{title}</h2>}
<div className="header-actions">
{onRefresh && (
<button
className="refresh-button"
onClick={handleRefresh}
disabled={isLoading}
>
{isLoading ? 'Refreshing...' : 'Refresh'}
</button>
)}
{onCreate && (
<button
className="create-button"
onClick={handleToggleCreate}
>
{createVisible ? 'Close' : `+ ${title || 'Item'}`}
</button>
)}
</div>
</div>
{createVisible && onCreate && (
<div className="create-form">
{renderComponent(onCreate)}
</div>
)}
<div className="items-columns">
{
columns.map(
(column) => (
<div
className="item-column-row"
style={{
"width": columns_count + "%",
}}
>
{column.toUpperCase()}
</div>
)
)
}
{columns.map((column) => (
<div key={column} className="item-column-row">
{column.toUpperCase()}
</div>
))}
{(onUpdate || onDelete) && (
<div className="item-column-row">Actions</div>
)}
</div>
<div className="items-list">
{data.map((item) => (
<div key={item.id} className="item-row">
{
Object.values(item).map((value) => (
<div
className="item-info"
style={{
"width": columns_count + "%",
}}
>
{value}
</div>
)
)
}
{/* UPDATE BUTTON & FORM */}
{update_component && (
<>
<button
className="update-button"
onClick={() => handleToggleUpdate(item.id)}
>
{itemBeingUpdated === item.id ? 'Close' : 'Update'}
</button>
{itemBeingUpdated === item.id && (
<div className="update-form">
{renderComponent(update_component, { item })}
</div>
)}
</>
)}
{/* DELETE BUTTON */}
{delete_action && (
<button
className="delete-button"
onClick={() => handleDelete(item)}
style={{ marginLeft: '8px' }}
>
Delete
</button>
)}
{data.length === 0 ? (
<div className="no-data">
No items found. {onCreate && `Click '+ ${title || 'Item'}' to add new items.`}
</div>
))}
) : (
data.map((item) => (
<div
key={item.id}
className={`item-row ${selectedItem?.id === item.id ? 'selected' : ''}`}
onClick={() => handleItemClick(item)}
>
{Object.entries(item)
.filter(([key]) => !key.toLowerCase().includes('id'))
.map(([key, value], index) => (
<div key={index} className="item-info">
{key.toLowerCase() === 'progress' ? (
value === 100 ? (
<span className="completed-text">Completed</span>
) : (
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${value}%` }}
/>
<span className="progress-text">{value}%</span>
</div>
)
) : key.toLowerCase() === 'status' ? (
<span className={`status-text ${value.toLowerCase().replace(/\s+/g, '-')}`}>
{value}
</span>
) : (
<span>{value}</span>
)}
</div>
))}
{(onUpdate || onDelete) && (
<div className="action-buttons">
{onUpdate && (
<button
className="update-button"
onClick={(e) => {
e.stopPropagation();
handleToggleUpdate(item.id);
}}
>
{itemBeingUpdated === item.id ? 'Close' : 'Update'}
</button>
)}
{onDelete && (
<button
className="delete-button"
onClick={(e) => {
e.stopPropagation();
handleDelete(item);
}}
disabled={isLoading}
>
Delete
</button>
)}
</div>
)}
{itemBeingUpdated === item.id && onUpdate && (
<div className="update-form">
{renderComponent(onUpdate, { item })}
</div>
)}
</div>
))
)}
</div>
<div className="element-details">
{selectedItem ? (
<div className="details-content">
<h3>Details</h3>
{Object.entries(selectedItem)
.filter(([key]) => !key.toLowerCase().includes('id'))
.map(([key, value]) => (
<div key={key} className="detail-row">
<span className="detail-label">{key}:</span>
<span className="detail-value">
{key.toLowerCase() === 'progress' ? (
value === 100 ? (
<span className="completed-text">Completed</span>
) : (
<span>{value}%</span>
)
) : (
value
)}
</span>
</div>
))}
</div>
) : (
<div className="no-selection">
Select an item to view details
</div>
)}
</div>
</div>
);
};

20
src/pages/404.js 100644
View File

@ -0,0 +1,20 @@
import React from 'react';
import { Link } from 'react-router-dom';
import '../styles/general.scss';
const NotFoundPage = () => {
return (
<div className="landing-container">
<div className="landing">
<h1>404</h1>
<h2>Strona nie została znaleziona</h2>
<p>Przepraszamy, ale strona, której szukasz, nie istnieje.</p>
<Link to="/">
<button>Powrót do strony głównej</button>
</Link>
</div>
</div>
);
};
export default NotFoundPage;

View File

@ -1,67 +1,124 @@
import React from 'react';
import React, { useState } from 'react';
import '../styles/general.scss';
import '@fortawesome/fontawesome-free/css/all.min.css';
import FootComponent from '../components/foot.js';
import NavBarComponent from '../components/navbar.js';
import ModelsDashboard from './dashboards/models.js';
import ThreeDModelsDashboard from './dashboards/3d-models.js';
import AIModelsDashboard from './dashboards/ai.models.js';
import AITasksDashboard from './dashboards/ai.tasks.js';
import RendersDashboard from './dashboards/renders.js';
import ServersDashboard from './dashboards/servers.js';
import UserSettings from './dashboards/user.js';
const DashboardPage = () => {
const icons_size = "fa-2x";
const [activeComponent, setActiveComponent] = useState('3d-models');
const icons_size = "fa-2x"
const handleNavigation = (path) => {
setActiveComponent(path);
};
const isActive = (path) => {
return activeComponent === path;
};
const handleLogout = () => {
// TODO: Implement proper logout logic (clear tokens, session, etc.)
console.log('Logging out...');
window.location.href = '/login';
};
const renderContent = () => {
switch (activeComponent) {
case 'ai-models':
return <AIModelsDashboard />;
case 'ai-tasks':
return <AITasksDashboard />;
case 'renders':
return <RendersDashboard />;
case 'servers':
return <ServersDashboard />;
case 'settings':
return <UserSettings />;
case '3d-models':
default:
return <ThreeDModelsDashboard />;
}
};
return (
<>
<NavBarComponent />
<div className="dashboard-container" style={{"display": "flex"}}>
<main className="dashboard-content">
<ModelsDashboard/>
</main>
<main className="large-menu-content">
<p>Servers</p>
<ol>
<li>
<i className={"fas " + icons_size + " fa-server"}></i> {/* fa-microchip */}
<p>Dashboard</p>
</li>
</ol>
<p>Rendering</p>
<ol>
<li>
<i className={"fas " + icons_size + " fa-cube"}></i>
<p>3D Models</p>
</li>
<li>
<i className={"fas " + icons_size + " fa-paint-brush"}></i>
<p>Rendered Materials</p>
</li>
</ol>
<p>AI Training</p>
<ol>
<li>
<i className={"fas " + icons_size + " fa-robot"}></i>
<p>AI Models</p>
</li>
</ol>
<p>User</p>
<ol>
<li>
<i className={"fas " + icons_size + " fa-user-cog"}></i>
<p>Settings</p>
</li>
<li>
<i className={"fas " + icons_size + " fa-sign-out-alt"}></i>
<p>Log Out</p>
</li>
</ol>
</main>
</div>
<FootComponent />
</>
)
}
<>
<NavBarComponent />
<div className="dashboard-container" style={{"display": "flex"}}>
<main className="dashboard-content">
{renderContent()}
</main>
<main className="large-menu-content">
<p>Servers</p>
<ol>
<li
className={isActive('servers') ? 'active' : ''}
onClick={() => handleNavigation('servers')}
>
<i className={"fas " + icons_size + " fa-server"}></i>
<p>Dashboard</p>
</li>
</ol>
<p>Rendering</p>
<ol>
<li
className={isActive('3d-models') ? 'active' : ''}
onClick={() => handleNavigation('3d-models')}
>
<i className={"fas " + icons_size + " fa-cube"}></i>
<p>3D Models</p>
</li>
<li
className={isActive('renders') ? 'active' : ''}
onClick={() => handleNavigation('renders')}
>
<i className={"fas " + icons_size + " fa-paint-brush"}></i>
<p>Rendered Materials</p>
</li>
</ol>
<p>AI Training</p>
<ol>
<li
className={isActive('ai-models') ? 'active' : ''}
onClick={() => handleNavigation('ai-models')}
>
<i className={"fas " + icons_size + " fa-robot"}></i>
<p>AI Models</p>
</li>
<li
className={isActive('ai-tasks') ? 'active' : ''}
onClick={() => handleNavigation('ai-tasks')}
>
<i className={"fas " + icons_size + " fa-microchip"}></i>
<p>AI Training Tasks</p>
</li>
</ol>
<p>User</p>
<ol>
<li
className={isActive('settings') ? 'active' : ''}
onClick={() => handleNavigation('settings')}
>
<i className={"fas " + icons_size + " fa-user-cog"}></i>
<p>Settings</p>
</li>
<li onClick={handleLogout}>
<i className={"fas " + icons_size + " fa-sign-out-alt"}></i>
<p>Log Out</p>
</li>
</ol>
</main>
</div>
<FootComponent />
</>
);
};
export default DashboardPage
export default DashboardPage;

View File

@ -0,0 +1,226 @@
import React, { useState } from 'react';
import { ListGenerator } from '../../components/forms/listGenerator';
import { FormGenerator } from '../../components/forms/formGenerator';
const ThreeDModelsDashboard = () => {
const [selectedModel, setSelectedModel] = useState(null);
const [models, setModels] = useState([
{
id: 1,
name: 'Model A',
type: '3D',
status: 'Active',
lastModified: '2024-03-20',
size: '2.5MB'
},
{
id: 2,
name: 'Model B',
type: '3D',
status: 'Inactive',
lastModified: '2024-03-19',
size: '1.8MB'
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
const [formMode, setFormMode] = useState('create'); // 'create' lub 'edit'
const [message, setMessage] = useState({ type: '', text: '' });
const handleModelSelect = (model) => {
setSelectedModel(model);
};
const handleModelAction = (action, model) => {
switch (action) {
case 'delete':
handleDeleteModel(model.id);
break;
case 'edit':
handleEditModel(model);
break;
default:
console.log('Unknown action:', action);
}
};
const handleCreateModel = () => {
setFormMode('create');
setIsFormVisible(true);
setSelectedModel(null);
};
const handleEditModel = (model) => {
setFormMode('edit');
setIsFormVisible(true);
setSelectedModel(model);
};
const handleDeleteModel = (modelId) => {
setModels(models.filter(model => model.id !== modelId));
if (selectedModel?.id === modelId) {
setSelectedModel(null);
}
setMessage({ type: 'success', text: 'Model has been deleted' });
};
const handleFormSubmit = (formData) => {
if (formMode === 'create') {
const newModel = {
id: models.length + 1,
...formData,
lastModified: new Date().toISOString().split('T')[0],
status: 'Active'
};
setModels([...models, newModel]);
setMessage({ type: 'success', text: 'Model has been created' });
} else {
setModels(models.map(model =>
model.id === selectedModel.id
? { ...model, ...formData, lastModified: new Date().toISOString().split('T')[0] }
: model
));
setMessage({ type: 'success', text: 'Model has been updated' });
}
setIsFormVisible(false);
};
const handleFormCancel = () => {
setIsFormVisible(false);
setSelectedModel(null);
};
const getModelActions = (model) => {
return [
{
label: 'Edit',
action: 'edit',
className: 'update-button'
},
{
label: 'Delete',
action: 'delete',
className: 'delete-button'
}
];
};
const formFields = [
{
name: 'name',
label: 'Model Name',
type: 'text',
required: true,
value: selectedModel?.name || ''
},
{
name: 'type',
label: 'Model Type',
type: 'select',
required: true,
options: [
{ value: '3D', label: '3D' },
{ value: '2D', label: '2D' }
],
value: selectedModel?.type || '3D'
},
{
name: 'status',
label: 'Status',
type: 'select',
required: true,
options: [
{ value: 'Active', label: 'Active' },
{ value: 'Inactive', label: 'Inactive' }
],
value: selectedModel?.status || 'Active'
}
];
return (
<div className="list-container">
<div className="dashboard-header">
<h2>3D Models</h2>
<button
className="create-button"
onClick={handleCreateModel}
>
<i className="fas fa-plus"></i>
3D Model
</button>
</div>
{message.text && (
<div className={`message ${message.type}`}>
{message.text}
</div>
)}
{isFormVisible && (
<div className="form-overlay">
<div className="form-container">
<FormGenerator
fields={formFields}
onSubmit={handleFormSubmit}
onCancel={handleFormCancel}
title={formMode === 'create' ? 'Create new model' : 'Edit model'}
/>
</div>
</div>
)}
<ListGenerator
data={models}
selectedItem={selectedModel}
onItemSelect={handleModelSelect}
onItemAction={handleModelAction}
getItemActions={getModelActions}
renderItem={(model) => (
<div className="list-row">
<div className="item-info">
<div className="item-name">{model.name}</div>
<div className="item-type">{model.type}</div>
<div className={`item-status ${model.status.toLowerCase()}`}>
{model.status}
</div>
</div>
<div className="item-details">
<div>Last Modified: {model.lastModified}</div>
<div>Size: {model.size}</div>
</div>
</div>
)}
renderDetails={(model) => (
<div className="details-panel">
<h3>Model Details</h3>
<div className="detail-row">
<span className="detail-label">ID:</span>
<span className="detail-value">{model.id}</span>
</div>
<div className="detail-row">
<span className="detail-label">Nazwa:</span>
<span className="detail-value">{model.name}</span>
</div>
<div className="detail-row">
<span className="detail-label">Typ:</span>
<span className="detail-value">{model.type}</span>
</div>
<div className="detail-row">
<span className="detail-label">Status:</span>
<span className="detail-value">{model.status}</span>
</div>
<div className="detail-row">
<span className="detail-label">Last Modified:</span>
<span className="detail-value">{model.lastModified}</span>
</div>
<div className="detail-row">
<span className="detail-label">Size:</span>
<span className="detail-value">{model.size}</span>
</div>
</div>
)}
/>
</div>
);
};
export default ThreeDModelsDashboard;

View File

@ -0,0 +1,234 @@
import React, { useState } from 'react';
import { ListGenerator } from '../../components/forms/listGenerator';
import FormGenerator from '../../components/forms/formGenerator';
const AIModelsDashboard = () => {
const [selectedModel, setSelectedModel] = useState(null);
const [models, setModels] = useState([
{
id: 1,
name: 'stable-diffusion',
type: 'text-to-image',
status: 'Active',
lastModified: '2024-03-20',
version: '2.1'
},
{
id: 2,
name: 'gpt-4',
type: 'text-generation',
status: 'Inactive',
lastModified: '2024-03-19',
version: '1.0'
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
const [formMode, setFormMode] = useState('create');
const [message, setMessage] = useState({ type: '', text: '' });
const handleModelSelect = (model) => {
setSelectedModel(model);
};
const handleModelAction = (action, model) => {
switch (action) {
case 'delete':
handleDeleteModel(model.id);
break;
case 'edit':
handleEditModel(model);
break;
default:
console.log('Unknown action:', action);
}
};
const handleCreateModel = () => {
setFormMode('create');
setIsFormVisible(true);
setSelectedModel(null);
};
const handleEditModel = (model) => {
setFormMode('edit');
setIsFormVisible(true);
setSelectedModel(model);
};
const handleDeleteModel = (modelId) => {
setModels(models.filter(model => model.id !== modelId));
if (selectedModel?.id === modelId) {
setSelectedModel(null);
}
setMessage({ type: 'success', text: 'AI Model has been deleted' });
};
const handleFormSubmit = (formData) => {
if (formMode === 'create') {
const newModel = {
id: models.length + 1,
...formData,
lastModified: new Date().toISOString().split('T')[0],
status: 'Active'
};
setModels([...models, newModel]);
setMessage({ type: 'success', text: 'AI Model has been created' });
} else {
setModels(models.map(model =>
model.id === selectedModel.id
? { ...model, ...formData, lastModified: new Date().toISOString().split('T')[0] }
: model
));
setMessage({ type: 'success', text: 'AI Model has been updated' });
}
setIsFormVisible(false);
};
const handleFormCancel = () => {
setIsFormVisible(false);
setSelectedModel(null);
};
const getModelActions = (model) => {
return [
{
label: 'Edit',
action: 'edit',
className: 'update-button'
},
{
label: 'Delete',
action: 'delete',
className: 'delete-button'
}
];
};
const formFields = [
{
name: 'name',
label: 'Model Name',
type: 'text',
required: true,
value: selectedModel?.name || ''
},
{
name: 'type',
label: 'Model Type',
type: 'select',
required: true,
options: [
{ value: 'text-to-image', label: 'Text to Image' },
{ value: 'image-to-text', label: 'Image to Text' }
],
value: selectedModel?.type || 'text-to-image'
},
{
name: 'version',
label: 'Version',
type: 'text',
required: true,
value: selectedModel?.version || '1.0'
},
{
name: 'status',
label: 'Status',
type: 'select',
required: true,
options: [
{ value: 'Active', label: 'Active' },
{ value: 'Inactive', label: 'Inactive' }
],
value: selectedModel?.status || 'Active'
}
];
return (
<div className="list-container">
<div className="dashboard-header">
<h2>AI Models</h2>
<button
className="create-button"
onClick={handleCreateModel}
>
<i className="fas fa-plus"></i>
AI Model
</button>
</div>
{message.text && (
<div className={`message ${message.type}`}>
{message.text}
</div>
)}
{isFormVisible && (
<div className="form-overlay">
<div className="form-container">
<FormGenerator
fields={formFields}
onSubmit={handleFormSubmit}
onCancel={handleFormCancel}
title={formMode === 'create' ? 'Create new AI Model' : 'Edit AI Model'}
/>
</div>
</div>
)}
<ListGenerator
data={models}
selectedItem={selectedModel}
onItemSelect={handleModelSelect}
onItemAction={handleModelAction}
getItemActions={getModelActions}
renderItem={(model) => (
<div className="list-row">
<div className="item-info">
<div className="item-name">{model.name}</div>
<div className="item-type">{model.type}</div>
<div className={`item-status ${model.status.toLowerCase()}`}>
{model.status}
</div>
</div>
<div className="item-details">
<div>Version: {model.version}</div>
<div>Last Modified: {model.lastModified}</div>
</div>
</div>
)}
renderDetails={(model) => (
<div className="details-panel">
<h3>AI Model Details</h3>
<div className="detail-row">
<span className="detail-label">ID:</span>
<span className="detail-value">{model.id}</span>
</div>
<div className="detail-row">
<span className="detail-label">Nazwa:</span>
<span className="detail-value">{model.name}</span>
</div>
<div className="detail-row">
<span className="detail-label">Typ:</span>
<span className="detail-value">{model.type}</span>
</div>
<div className="detail-row">
<span className="detail-label">Wersja:</span>
<span className="detail-value">{model.version}</span>
</div>
<div className="detail-row">
<span className="detail-label">Status:</span>
<span className="detail-value">{model.status}</span>
</div>
<div className="detail-row">
<span className="detail-label">Ostatnia modyfikacja:</span>
<span className="detail-value">{model.lastModified}</span>
</div>
</div>
)}
/>
</div>
);
};
export default AIModelsDashboard;

View File

@ -0,0 +1,280 @@
import React, { useState } from 'react';
import { ListGenerator } from '../../components/forms/listGenerator';
import { FormGenerator } from '../../components/forms/formGenerator';
const AITasksDashboard = () => {
const [selectedTask, setSelectedTask] = useState(null);
const [tasks, setTasks] = useState([
{
id: 1,
name: 'Task A',
type: 'Training',
status: 'In Progress',
lastModified: '2024-03-20',
progress: 45
},
{
id: 2,
name: 'Task B',
type: 'Inference',
status: 'Completed',
lastModified: '2024-03-19',
progress: 100
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
const [formMode, setFormMode] = useState('create');
const [message, setMessage] = useState({ type: '', text: '' });
const handleTaskSelect = (task) => {
setSelectedTask(task);
};
const handleTaskAction = (action, task) => {
switch (action) {
case 'delete':
handleDeleteTask(task.id);
break;
case 'edit':
handleEditTask(task);
break;
case 'cancel':
handleCancelTask(task.id);
break;
case 'restart':
handleRestartTask(task.id);
break;
default:
console.log('Unknown action:', action);
}
};
const handleCreateTask = () => {
setFormMode('create');
setIsFormVisible(true);
setSelectedTask(null);
};
const handleEditTask = (task) => {
setFormMode('edit');
setIsFormVisible(true);
setSelectedTask(task);
};
const handleDeleteTask = (taskId) => {
setTasks(tasks.filter(task => task.id !== taskId));
if (selectedTask?.id === taskId) {
setSelectedTask(null);
}
setMessage({ type: 'success', text: 'Task has been deleted' });
};
const handleCancelTask = (taskId) => {
setTasks(tasks.map(task =>
task.id === taskId
? { ...task, status: 'Cancelled', progress: 0 }
: task
));
setMessage({ type: 'success', text: 'Task has been cancelled' });
};
const handleRestartTask = (taskId) => {
setTasks(tasks.map(task =>
task.id === taskId
? { ...task, status: 'In Progress', progress: 0 }
: task
));
setMessage({ type: 'success', text: 'Task has been restarted' });
};
const handleFormSubmit = (formData) => {
if (formMode === 'create') {
const newTask = {
id: tasks.length + 1,
...formData,
lastModified: new Date().toISOString().split('T')[0],
status: 'In Progress',
progress: 0
};
setTasks([...tasks, newTask]);
setMessage({ type: 'success', text: 'Task has been created' });
} else {
setTasks(tasks.map(task =>
task.id === selectedTask.id
? { ...task, ...formData, lastModified: new Date().toISOString().split('T')[0] }
: task
));
setMessage({ type: 'success', text: 'Task has been updated' });
}
setIsFormVisible(false);
};
const handleFormCancel = () => {
setIsFormVisible(false);
setSelectedTask(null);
};
const getTaskActions = (task) => {
const actions = [];
if (task.status === 'In Progress') {
actions.push({
label: 'Cancel',
action: 'cancel',
className: 'cancel-button'
});
} else if (task.status === 'Completed' || task.status === 'Cancelled') {
actions.push({
label: 'Restart',
action: 'restart',
className: 'restart-button'
});
}
actions.push(
{
label: 'Edit',
action: 'edit',
className: 'update-button'
},
{
label: 'Delete',
action: 'delete',
className: 'delete-button'
}
);
return actions;
};
const formFields = [
{
name: 'name',
label: 'Task Name',
type: 'text',
required: true,
value: selectedTask?.name || ''
},
{
name: 'type',
label: 'Task Type',
type: 'select',
required: true,
options: [
{ value: 'Training', label: 'Training' },
{ value: 'Inference', label: 'Inference' },
{ value: 'Evaluation', label: 'Evaluation' }
],
value: selectedTask?.type || 'Training'
},
{
name: 'status',
label: 'Status',
type: 'select',
required: true,
options: [
{ value: 'In Progress', label: 'In Progress' },
{ value: 'Completed', label: 'Completed' },
{ value: 'Failed', label: 'Failed' },
{ value: 'Cancelled', label: 'Cancelled' }
],
value: selectedTask?.status || 'In Progress'
}
];
return (
<div className="list-container">
<div className="dashboard-header">
<h2>AI Tasks</h2>
<button
className="create-button"
onClick={handleCreateTask}
>
<i className="fas fa-plus"></i>
Task
</button>
</div>
{message.text && (
<div className={`message ${message.type}`}>
{message.text}
</div>
)}
{isFormVisible && (
<div className="form-overlay">
<div className="form-container">
<FormGenerator
fields={formFields}
onSubmit={handleFormSubmit}
onCancel={handleFormCancel}
title={formMode === 'create' ? 'Create new task' : 'Edit task'}
/>
</div>
</div>
)}
<ListGenerator
data={tasks}
selectedItem={selectedTask}
onItemSelect={handleTaskSelect}
onItemAction={handleTaskAction}
getItemActions={getTaskActions}
renderItem={(task) => (
<div className="list-row">
<div className="item-info">
<div className="item-name">{task.name}</div>
<div className="item-type">{task.type}</div>
<div className={`item-status ${task.status.toLowerCase().replace(' ', '-')}`}>
{task.status}
</div>
<div className="item-progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${task.progress}%` }}
/>
</div>
<span>{task.progress}%</span>
</div>
</div>
<div className="item-details">
<div>Last Modified: {task.lastModified}</div>
</div>
</div>
)}
renderDetails={(task) => (
<div className="details-panel">
<h3>Task Details</h3>
<div className="detail-row">
<span className="detail-label">ID:</span>
<span className="detail-value">{task.id}</span>
</div>
<div className="detail-row">
<span className="detail-label">Name:</span>
<span className="detail-value">{task.name}</span>
</div>
<div className="detail-row">
<span className="detail-label">Type:</span>
<span className="detail-value">{task.type}</span>
</div>
<div className="detail-row">
<span className="detail-label">Status:</span>
<span className="detail-value">{task.status}</span>
</div>
<div className="detail-row">
<span className="detail-label">Progress:</span>
<span className="detail-value">{task.progress}%</span>
</div>
<div className="detail-row">
<span className="detail-label">Last Modified:</span>
<span className="detail-value">{task.lastModified}</span>
</div>
</div>
)}
/>
</div>
);
};
export default AITasksDashboard;

View File

@ -1,26 +0,0 @@
import React, { useState } from 'react';
import { ListGenerator } from '../../components/forms/listGenerator';
const ModelsDashboard = () => {
const [items, setItems] = useState([
{ id: 0, name: 'orc.blend' },
{ id: 1, name: 'palladin.blend' },
]);
return (
<>
<div className="list-container">
<ListGenerator
data={items}
/>
<div className="element-details">
</div>
</div>
</>
)
}
export default ModelsDashboard

View File

@ -0,0 +1,265 @@
import React, { useState } from 'react';
import { ListGenerator } from '../../components/forms/listGenerator';
import FormGenerator from '../../components/forms/formGenerator';
const RendersDashboard = () => {
const [selectedRender, setSelectedRender] = useState(null);
const [renders, setRenders] = useState([
{
id: 1,
name: 'Render A',
type: '3D',
status: 'Completed',
lastModified: '2024-03-20',
resolution: '1920x1080',
progress: 100
},
{
id: 2,
name: 'Render B',
type: '2D',
status: 'In Progress',
lastModified: '2024-03-19',
resolution: '3840x2160',
progress: 45
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
const [formMode, setFormMode] = useState('create');
const [message, setMessage] = useState({ type: '', text: '' });
const handleRenderSelect = (render) => {
setSelectedRender(render);
};
const handleRenderAction = (action, render) => {
switch (action) {
case 'delete':
handleDeleteRender(render.id);
break;
case 'edit':
handleEditRender(render);
break;
default:
console.log('Unknown action:', action);
}
};
const handleCreateRender = () => {
setFormMode('create');
setIsFormVisible(true);
setSelectedRender(null);
};
const handleEditRender = (render) => {
setFormMode('edit');
setIsFormVisible(true);
setSelectedRender(render);
};
const handleDeleteRender = (renderId) => {
setRenders(renders.filter(render => render.id !== renderId));
if (selectedRender?.id === renderId) {
setSelectedRender(null);
}
setMessage({ type: 'success', text: 'Render został usunięty' });
};
const handleFormSubmit = (formData) => {
if (formMode === 'create') {
const newRender = {
id: renders.length + 1,
...formData,
lastModified: new Date().toISOString().split('T')[0],
status: 'In Progress',
progress: 0
};
setRenders([...renders, newRender]);
setMessage({ type: 'success', text: 'Render został utworzony' });
} else {
setRenders(renders.map(render =>
render.id === selectedRender.id
? { ...render, ...formData, lastModified: new Date().toISOString().split('T')[0] }
: render
));
setMessage({ type: 'success', text: 'Render został zaktualizowany' });
}
setIsFormVisible(false);
};
const handleFormCancel = () => {
setIsFormVisible(false);
setSelectedRender(null);
};
const getRenderActions = (render) => {
return [
{
label: 'Edit',
action: 'edit',
className: 'update-button'
},
{
label: 'Delete',
action: 'delete',
className: 'delete-button'
}
];
};
const formFields = [
{
name: 'name',
label: 'Nazwa renderu',
type: 'text',
required: true,
value: selectedRender?.name || ''
},
{
name: 'type',
label: 'Typ renderu',
type: 'select',
required: true,
options: [
{ value: '3D', label: '3D' },
{ value: '2D', label: '2D' }
],
value: selectedRender?.type || '3D'
},
{
name: 'resolution',
label: 'Rozdzielczość',
type: 'select',
required: true,
options: [
{ value: '1920x1080', label: 'Full HD (1920x1080)' },
{ value: '3840x2160', label: '4K (3840x2160)' },
{ value: '7680x4320', label: '8K (7680x4320)' }
],
value: selectedRender?.resolution || '1920x1080'
},
{
name: 'status',
label: 'Status',
type: 'select',
required: true,
options: [
{ value: 'In Progress', label: 'W trakcie' },
{ value: 'Completed', label: 'Zakończony' },
{ value: 'Failed', label: 'Nieudany' }
],
value: selectedRender?.status || 'In Progress'
},
{
name: 'progress',
label: 'Postęp',
type: 'number',
required: true,
min: 0,
max: 100,
value: selectedRender?.progress || 0
}
];
return (
<div className="list-container">
<div className="dashboard-header">
<h2>Rendered Materials</h2>
<button
className="create-button"
onClick={handleCreateRender}
>
<i className="fas fa-plus"></i>
Render
</button>
</div>
{message.text && (
<div className={`message ${message.type}`}>
{message.text}
</div>
)}
{isFormVisible && (
<div className="form-overlay">
<div className="form-container">
<FormGenerator
fields={formFields}
onSubmit={handleFormSubmit}
onCancel={handleFormCancel}
title={formMode === 'create' ? 'Utwórz nowy render' : 'Edytuj render'}
/>
</div>
</div>
)}
<ListGenerator
data={renders}
selectedItem={selectedRender}
onItemSelect={handleRenderSelect}
onItemAction={handleRenderAction}
getItemActions={getRenderActions}
renderItem={(render) => (
<div className="list-row">
<div className="item-info">
<div className="item-name">{render.name}</div>
<div className="item-type">{render.type}</div>
<div className={`item-status ${render.status.toLowerCase().replace(' ', '-')}`}>
{render.status}
</div>
<div className="item-progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${render.progress}%` }}
/>
</div>
<span>{render.progress}%</span>
</div>
</div>
<div className="item-details">
<div>Rozdzielczość: {render.resolution}</div>
<div>Ostatnia modyfikacja: {render.lastModified}</div>
</div>
</div>
)}
renderDetails={(render) => (
<div className="details-panel">
<h3>Szczegóły renderu</h3>
<div className="detail-row">
<span className="detail-label">ID:</span>
<span className="detail-value">{render.id}</span>
</div>
<div className="detail-row">
<span className="detail-label">Nazwa:</span>
<span className="detail-value">{render.name}</span>
</div>
<div className="detail-row">
<span className="detail-label">Typ:</span>
<span className="detail-value">{render.type}</span>
</div>
<div className="detail-row">
<span className="detail-label">Rozdzielczość:</span>
<span className="detail-value">{render.resolution}</span>
</div>
<div className="detail-row">
<span className="detail-label">Status:</span>
<span className="detail-value">{render.status}</span>
</div>
<div className="detail-row">
<span className="detail-label">Postęp:</span>
<span className="detail-value">{render.progress}%</span>
</div>
<div className="detail-row">
<span className="detail-label">Ostatnia modyfikacja:</span>
<span className="detail-value">{render.lastModified}</span>
</div>
</div>
)}
/>
</div>
);
};
export default RendersDashboard;

View File

@ -0,0 +1,236 @@
import React, { useState } from 'react';
import { ListGenerator } from '../../components/forms/listGenerator';
import FormGenerator from '../../components/forms/formGenerator';
const ServersDashboard = () => {
const [selectedServer, setSelectedServer] = useState(null);
const [servers, setServers] = useState([
{
id: 1,
name: 'Server A',
type: 'Render',
status: 'Online',
lastModified: '2024-03-20',
ip: '192.168.1.100'
},
{
id: 2,
name: 'Server B',
type: 'AI',
status: 'Offline',
lastModified: '2024-03-19',
ip: '192.168.1.101'
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
const [formMode, setFormMode] = useState('create');
const [message, setMessage] = useState({ type: '', text: '' });
const handleServerSelect = (server) => {
setSelectedServer(server);
};
const handleServerAction = (action, server) => {
switch (action) {
case 'delete':
handleDeleteServer(server.id);
break;
case 'edit':
handleEditServer(server);
break;
default:
console.log('Unknown action:', action);
}
};
const handleCreateServer = () => {
setFormMode('create');
setIsFormVisible(true);
setSelectedServer(null);
};
const handleEditServer = (server) => {
setFormMode('edit');
setIsFormVisible(true);
setSelectedServer(server);
};
const handleDeleteServer = (serverId) => {
setServers(servers.filter(server => server.id !== serverId));
if (selectedServer?.id === serverId) {
setSelectedServer(null);
}
setMessage({ type: 'success', text: 'Serwer został usunięty' });
};
const handleFormSubmit = (formData) => {
if (formMode === 'create') {
const newServer = {
id: servers.length + 1,
...formData,
lastModified: new Date().toISOString().split('T')[0],
status: 'Offline'
};
setServers([...servers, newServer]);
setMessage({ type: 'success', text: 'Serwer został utworzony' });
} else {
setServers(servers.map(server =>
server.id === selectedServer.id
? { ...server, ...formData, lastModified: new Date().toISOString().split('T')[0] }
: server
));
setMessage({ type: 'success', text: 'Serwer został zaktualizowany' });
}
setIsFormVisible(false);
};
const handleFormCancel = () => {
setIsFormVisible(false);
setSelectedServer(null);
};
const getServerActions = (server) => {
return [
{
label: 'Edit',
action: 'edit',
className: 'update-button'
},
{
label: 'Delete',
action: 'delete',
className: 'delete-button'
}
];
};
const formFields = [
{
name: 'name',
label: 'Nazwa serwera',
type: 'text',
required: true,
value: selectedServer?.name || ''
},
{
name: 'type',
label: 'Typ serwera',
type: 'select',
required: true,
options: [
{ value: 'Render', label: 'Render' },
{ value: 'AI', label: 'AI' },
{ value: 'Storage', label: 'Storage' }
],
value: selectedServer?.type || 'Render'
},
{
name: 'ip',
label: 'Adres IP',
type: 'text',
required: true,
value: selectedServer?.ip || ''
},
{
name: 'status',
label: 'Status',
type: 'select',
required: true,
options: [
{ value: 'Online', label: 'Online' },
{ value: 'Offline', label: 'Offline' },
{ value: 'Maintenance', label: 'Maintenance' }
],
value: selectedServer?.status || 'Offline'
}
];
return (
<div className="list-container">
<div className="dashboard-header">
<h2>Servers</h2>
<button
className="create-button"
onClick={handleCreateServer}
>
<i className="fas fa-plus"></i>
Server
</button>
</div>
{message.text && (
<div className={`message ${message.type}`}>
{message.text}
</div>
)}
{isFormVisible && (
<div className="form-overlay">
<div className="form-container">
<FormGenerator
fields={formFields}
onSubmit={handleFormSubmit}
onCancel={handleFormCancel}
title={formMode === 'create' ? 'Utwórz nowy serwer' : 'Edytuj serwer'}
/>
</div>
</div>
)}
<ListGenerator
data={servers}
selectedItem={selectedServer}
onItemSelect={handleServerSelect}
onItemAction={handleServerAction}
getItemActions={getServerActions}
renderItem={(server) => (
<div className="list-row">
<div className="item-info">
<div className="item-name">{server.name}</div>
<div className="item-type">{server.type}</div>
<div className={`item-status ${server.status.toLowerCase()}`}>
{server.status}
</div>
</div>
<div className="item-details">
<div>IP: {server.ip}</div>
<div>Ostatnia modyfikacja: {server.lastModified}</div>
</div>
</div>
)}
renderDetails={(server) => (
<div className="details-panel">
<h3>Szczegóły serwera</h3>
<div className="detail-row">
<span className="detail-label">ID:</span>
<span className="detail-value">{server.id}</span>
</div>
<div className="detail-row">
<span className="detail-label">Nazwa:</span>
<span className="detail-value">{server.name}</span>
</div>
<div className="detail-row">
<span className="detail-label">Typ:</span>
<span className="detail-value">{server.type}</span>
</div>
<div className="detail-row">
<span className="detail-label">IP:</span>
<span className="detail-value">{server.ip}</span>
</div>
<div className="detail-row">
<span className="detail-label">Status:</span>
<span className="detail-value">{server.status}</span>
</div>
<div className="detail-row">
<span className="detail-label">Ostatnia modyfikacja:</span>
<span className="detail-value">{server.lastModified}</span>
</div>
</div>
)}
/>
</div>
);
};
export default ServersDashboard;

View File

@ -0,0 +1,176 @@
import React, { useState, useRef } from 'react';
import FormGenerator from '../../components/forms/formGenerator';
const UserSettings = () => {
const [isEditing, setIsEditing] = useState(false);
const [message, setMessage] = useState({ type: '', text: '' });
const usernameInput = useRef();
const emailInput = useRef();
const currentPasswordInput = useRef();
const newPasswordInput = useRef();
const confirmPasswordInput = useRef();
const [usernameValidationInfo, setUsernameValidationInfo] = useState("Empty");
const [emailValidationInfo, setEmailValidationInfo] = useState("Empty");
const [currentPasswordValidationInfo, setCurrentPasswordValidationInfo] = useState("Empty");
const [newPasswordValidationInfo, setNewPasswordValidationInfo] = useState("Empty");
const [confirmPasswordValidationInfo, setConfirmPasswordValidationInfo] = useState("Empty");
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
const usernameValidation = (event) => {
if (event.target.value === "") {
setUsernameValidationInfo("Username is required.");
} else {
setUsernameValidationInfo("Success");
}
};
const emailValidation = (event) => {
if (event.target.value === "") {
setEmailValidationInfo("Email is required.");
} else if (!emailRegex.test(event.target.value)) {
setEmailValidationInfo("Please provide correct email");
} else {
setEmailValidationInfo("Success");
}
};
const currentPasswordValidation = (event) => {
if (event.target.value === "") {
setCurrentPasswordValidationInfo("Current password is required.");
} else {
setCurrentPasswordValidationInfo("Success");
}
};
const newPasswordValidation = (event) => {
if (event.target.value === "") {
setNewPasswordValidationInfo("New password is required.");
} else if (!passwordRegex.test(event.target.value)) {
setNewPasswordValidationInfo("Password require:\n - At least 8 characters,\n - At least one uppercase letter,\n - At least one lowercase letter,\n - At least one digit,\n - At least one special character.");
} else {
setNewPasswordValidationInfo("Success");
}
};
const confirmPasswordValidation = (event) => {
if (event.target.value === "") {
setConfirmPasswordValidationInfo("Please confirm your new password.");
} else if (event.target.value !== newPasswordInput.current.value) {
setConfirmPasswordValidationInfo("Passwords do not match.");
} else {
setConfirmPasswordValidationInfo("Success");
}
};
const handleSubmit = async (refs) => {
try {
// TODO: Implement API call to update user data
console.log('Updating user data:', {
username: refs[0].current.value,
email: refs[1].current.value,
currentPassword: refs[2].current.value,
newPassword: refs[3].current.value
});
setMessage({ type: 'success', text: 'Settings updated successfully!' });
setIsEditing(false);
} catch (error) {
setMessage({ type: 'error', text: 'Failed to update settings. Please try again.' });
}
};
const getInputList = () => {
const baseInputs = [
{
type: 'info',
action: 'Update',
endpoint: 'user/settings',
button_value: isEditing ? 'SAVE CHANGES' : ''
},
{
type: 'text',
name: 'USERNAME',
ref: usernameInput,
onChange: usernameValidation,
validationInfo: usernameValidationInfo,
disabled: !isEditing
},
{
type: 'text',
name: 'EMAIL',
ref: emailInput,
onChange: emailValidation,
validationInfo: emailValidationInfo,
disabled: !isEditing
}
];
if (isEditing) {
baseInputs.push(
{
type: 'password',
name: 'CURRENT PASSWORD',
ref: currentPasswordInput,
onChange: currentPasswordValidation,
validationInfo: currentPasswordValidationInfo
},
{
type: 'password',
name: 'NEW PASSWORD',
ref: newPasswordInput,
onChange: newPasswordValidation,
validationInfo: newPasswordValidationInfo
},
{
type: 'password',
name: 'CONFIRM NEW PASSWORD',
ref: confirmPasswordInput,
onChange: confirmPasswordValidation,
validationInfo: confirmPasswordValidationInfo
}
);
}
return baseInputs;
};
return (
<div className="list-container">
<div className="user-settings">
<div className="settings-header">
<h2>User Settings</h2>
<button
className={`edit-button ${isEditing ? 'cancel' : ''}`}
onClick={() => setIsEditing(!isEditing)}
>
{isEditing ? 'Cancel' : 'Edit'}
</button>
</div>
{message.text && (
<div className={`message ${message.type}`}>
{message.text}
</div>
)}
<FormGenerator
inputList={getInputList()}
refList={[
usernameInput,
emailInput,
currentPasswordInput,
newPasswordInput,
confirmPasswordInput
]}
action={handleSubmit}
/>
</div>
</div>
);
};
export default UserSettings;

View File

@ -3,6 +3,10 @@
$first-color: rgba(0, 90, 25, 1);
$secondary-color: white;
$third-color: #242b2f;
$error-color: #dc3545;
$in-progress-color: #ffa500;
$queued-color: #fbff00;
$success-color: $first-color;
$title-color: white;
$subtitle-color: #a6a6a6;
@ -86,9 +90,17 @@ body, html {
width: 100%;
padding-top: 15px;
padding-bottom: 15px;
cursor:pointer;
cursor: pointer;
transition-duration: 0.2s;
&.active {
background-color: $first-color;
i {
color: $form-background;
}
}
p {
height: 13px;
padding-top: 7px;
@ -113,7 +125,7 @@ body, html {
background-color: $first-color;
i {
color: $form-background; // $secondary-color;
color: $form-background;
}
}
}
@ -232,61 +244,421 @@ body, html {
}
.list-container {
width: 95%;
height: 100%;
margin: 2.5%;
background-color: $form-background;
color: $secondary-color;
margin: 20px;
padding: 20px;
background: $form-background;
color: $subtitle-color;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.list-generator-container {
width: 100%;
height: 50%;
.items-columns {
display: flex;
align-items: center;
margin-top: 10px;
margin-bottom: 10px;
width: 100%;
height: 10px;
text-align: center;
display: flex;
flex-direction: column;
gap: 20px;
.item-column-row {
font-weight: bolder;
height: 10px;
margin-top: 5px;
margin-bottom: 5px;
}
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
.items-list {
.item-row {
display: flex;
align-items: center;
margin-top: 10px;
margin-bottom: 10px;
h2 {
margin: 0;
color: $title-color;
}
.item-info {
font-weight: bold;
margin-top: 15px;
margin-bottom: 15px;
}
.header-actions {
display: flex;
gap: 10px;
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
&.refresh-button {
background-color: $secondary-color;
color: white;
&:hover {
background-color: darken($secondary-color, 10%);
}
&:disabled {
background-color: lighten($secondary-color, 20%);
cursor: not-allowed;
}
}
&.create-button {
background-color: $background-color;
color: white;
&:hover {
background-color: darken($background-color, 10%);
}
}
}
}
}
.update-button {
.items-columns {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
padding: 10px;
background-color: rgba($background-color, 0.1);
border-radius: 4px;
.item-column-row {
font-weight: 600;
color: $title-color;
}
}
.delete-button {
.items-list {
display: flex;
flex-direction: column;
gap: 10px;
.no-data {
text-align: center;
padding: 20px;
color: $subtitle-color;
background-color: rgba($subtitle-color, 0.1);
border-radius: 4px;
}
.item-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
padding: 15px;
background-color: $background-color;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
&:hover {
background-color: rgba($form-background, 0.5);
}
&.selected {
background-color: $form-background;
border-left: 8px solid $first-color;
}
.item-info {
display: flex;
align-items: center;
gap: 10px;
.progress-bar {
width: 100%;
height: 8px;
background-color: rgba($subtitle-color, 0.1);
border-radius: 4px;
overflow: visible;
position: relative;
.progress-fill {
height: 100%;
border-radius: 4px;
background-color: $first-color;
transition: width 0.3s ease;
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: $subtitle-color;
font-size: 12px;
font-weight: 500;
text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
}
}
.completed-text {
color: $first-color;
font-weight: 700;
text-align: center;
width: 100%;
}
.status-text {
font-weight: 700;
width: 100%;
&.active {
color: $success-color;
}
&.inactive {
color: $subtitle-color;
}
&.in-progress {
color: $in-progress-color;
}
&.queued {
color: $queued-color;
}
&.completed {
color: $success-color;
}
&.failed {
color: $error-color;
}
&.cancelled {
color: $subtitle-color;
}
}
}
.action-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
button {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
&.update-button {
background-color: $secondary-color;
color: white;
&:hover {
background-color: darken($secondary-color, 10%);
}
}
&.delete-button {
background-color: $error-color;
color: white;
&:hover {
background-color: darken($error-color, 10%);
}
&:disabled {
background-color: lighten($error-color, 20%);
cursor: not-allowed;
}
}
}
}
}
}
}
.element-details {
margin-top: 20px;
padding: 20px;
background-color: $form-background;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
.no-selection {
text-align: center;
color: $subtitle-color;
padding: 20px;
}
.details-content {
h3 {
margin: 0 0 15px 0;
color: $title-color;
}
.detail-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid rgba($subtitle-color, 0.1);
&:last-child {
border-bottom: none;
}
.detail-label {
font-weight: 600;
color: $title-color;
}
.detail-value {
color: $subtitle-color;
}
}
}
}
}
.element-details {
width: 100%;
height: 50%;
.list-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background-color: white;
border-radius: 4px;
margin-bottom: 10px;
cursor: pointer;
transition: all 0.2s;
&:hover {
background-color: rgba($background-color, 0.05);
}
.item-info {
display: flex;
flex-direction: column;
gap: 5px;
.item-name {
font-weight: 600;
color: $title-color;
}
.item-type {
color: $subtitle-color;
font-size: 0.9em;
}
.item-status {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8em;
font-weight: 500;
&.active {
background-color: rgba($success-color, 0.1);
color: $success-color;
}
&.inactive {
background-color: rgba($subtitle-color, 0.1);
color: $subtitle-color;
}
&.in-progress {
background-color: rgba($in-progress-color, 0.1);
color: $in-progress-color;
}
&.queued {
background-color: rgba($queued-color, 0.1);
color: $queued-color;
}
&.completed {
background-color: rgba($success-color, 0.1);
color: $success-color;
}
&.failed {
background-color: rgba($error-color, 0.1);
color: $error-color;
}
&.cancelled {
background-color: rgba($subtitle-color, 0.1);
color: $subtitle-color;
}
}
.item-progress {
display: flex;
align-items: center;
gap: 10px;
.progress-bar {
width: 100%;
height: 8px;
background-color: rgba($subtitle-color, 0.1);
border-radius: 4px;
overflow: visible;
position: relative;
.progress-fill {
height: 100%;
background-color: $first-color;
transition: width 0.3s ease;
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: $subtitle-color;
font-size: 12px;
font-weight: 500;
text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
}
}
.completed-text {
color: $first-color;
font-weight: 500;
}
span {
font-size: 0.9em;
color: $subtitle-color;
}
}
}
.item-details {
color: $subtitle-color;
font-size: 0.9em;
}
}
.details-panel {
margin-top: 20px;
padding: 20px;
background-color: white;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
h3 {
margin: 0 0 15px 0;
color: $title-color;
}
.detail-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid rgba($subtitle-color, 0.1);
&:last-child {
border-bottom: none;
}
.detail-label {
font-weight: 600;
color: $title-color;
}
.detail-value {
color: $subtitle-color;
}
}
}
}
@ -699,3 +1071,318 @@ body, html {
margin-left: 0px;
}
}
.user-settings {
padding: 20px;
color: $secondary-color;
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h2 {
color: $title-color;
margin: 0;
font-size: 24px;
}
.edit-button {
background: $first-color;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
&.cancel {
background: #dc3545;
}
&:hover {
transform: translateY(-2px);
&.cancel {
background: darken(#dc3545, 10%);
}
&:not(.cancel) {
background: darken($first-color, 10%);
}
}
}
}
.message {
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
text-align: center;
font-weight: 500;
&.success {
background: rgba(0, 120, 0, 0.2);
color: #00ff00;
}
&.error {
background: rgba(220, 53, 69, 0.2);
color: #ff4444;
}
}
.settings-form {
max-width: 500px;
margin: 0 auto;
.form-group {
margin-bottom: 20px;
label {
display: block;
margin-bottom: 8px;
color: $title-color;
font-weight: 600;
}
input {
width: 100%;
padding: 10px;
background: rgba(0, 0, 0, 0.2);
border: 1px solid $border-color;
border-radius: 5px;
color: $secondary-color;
transition: all 0.3s ease;
&:disabled {
background: rgba(0, 0, 0, 0.1);
cursor: not-allowed;
}
&:focus {
outline: none;
border-color: $first-color;
box-shadow: 0 0 0 2px rgba($first-color, 0.2);
}
}
}
.save-button {
width: 100%;
padding: 12px;
background: $first-color;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
&:hover {
background: darken($first-color, 10%);
transform: translateY(-2px);
}
}
}
}
.task-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: rgba(0, 0, 0, 0.1);
border-radius: 8px;
margin-bottom: 10px;
transition: all 0.3s ease;
&:hover {
background: rgba(0, 0, 0, 0.2);
transform: translateX(5px);
}
.task-info {
flex: 1;
.task-name {
font-size: 18px;
font-weight: 600;
color: $title-color;
margin-bottom: 5px;
}
.task-status {
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
}
.task-progress {
display: flex;
align-items: center;
gap: 10px;
.progress-bar {
flex: 1;
height: 8px;
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
overflow: hidden;
.progress-fill {
height: 100%;
background: $background-color;
transition: width 0.3s ease;
}
}
span {
min-width: 45px;
text-align: right;
color: $subtitle-color;
}
}
}
.task-details {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
color: $subtitle-color;
font-size: 14px;
margin-left: 20px;
}
.action-buttons {
display: flex;
gap: 8px;
margin-left: 20px;
button {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
&.cancel-button {
background: #ffc107;
color: #000;
&:hover {
background: darken(#ffc107, 10%);
}
}
&.restart-button {
background: #17a2b8;
color: white;
&:hover {
background: darken(#17a2b8, 10%);
}
}
&.delete-button {
background: #dc3545;
color: white;
&:hover {
background: darken(#dc3545, 10%);
}
}
}
}
}
.task-details-panel {
padding: 20px;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
h3 {
color: $title-color;
margin-bottom: 20px;
font-size: 20px;
}
.detail-row {
display: flex;
margin-bottom: 10px;
padding: 8px;
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
.detail-label {
font-weight: 600;
color: $title-color;
margin-right: 10px;
min-width: 120px;
}
.detail-value {
color: $subtitle-color;
}
}
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
h2 {
color: $title-color;
margin: 0;
font-size: 24px;
}
.create-button {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: $first-color;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
i {
font-size: 16px;
}
&:hover {
background: darken($first-color, 10%);
transform: translateY(-2px);
}
}
}
.form-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
.form-container {
background: $form-background;
padding: 30px;
border-radius: 10px;
width: 500px;
max-width: 90%;
max-height: 90vh;
overflow-y: auto;
}
}