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 + etcfeat/x_gpu/chat_gpt_new_version
parent
96606b2231
commit
e8c234ce51
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 it’s 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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue