From e8c234ce5150c46f9a0dea597daf6cbf4705a47c Mon Sep 17 00:00:00 2001 From: TBS093A Date: Tue, 4 Mar 2025 14:58:26 +0100 Subject: [PATCH] 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 --- src/App.js | 3 +- src/components/forms/listGenerator.js | 298 ++++++---- src/pages/404.js | 20 + src/pages/dashboard.js | 167 ++++-- src/pages/dashboards/3d-models.js | 226 ++++++++ src/pages/dashboards/ai.models.js | 234 ++++++++ src/pages/dashboards/ai.tasks.js | 280 ++++++++++ src/pages/dashboards/models.js | 26 - src/pages/dashboards/renders.js | 265 +++++++++ src/pages/dashboards/servers.js | 236 ++++++++ src/pages/dashboards/user.js | 176 ++++++ src/styles/general.scss | 769 ++++++++++++++++++++++++-- 12 files changed, 2467 insertions(+), 233 deletions(-) create mode 100644 src/pages/404.js create mode 100644 src/pages/dashboards/3d-models.js create mode 100644 src/pages/dashboards/ai.tasks.js delete mode 100644 src/pages/dashboards/models.js diff --git a/src/App.js b/src/App.js index 45f37c8..cc2ce40 100644 --- a/src/App.js +++ b/src/App.js @@ -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() { } /> } /> } /> - Not Found} /> + } /> ); diff --git a/src/components/forms/listGenerator.js b/src/components/forms/listGenerator.js index efec749..a19f22b 100644 --- a/src/components/forms/listGenerator.js +++ b/src/components/forms/listGenerator.js @@ -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 (
- - {create_component && ( -
- - {createVisible && ( -
- {renderComponent(create_component)} -
+
+ {title &&

{title}

} +
+ {onRefresh && ( + )} + {onCreate && ( + + )} +
+
+ + {createVisible && onCreate && ( +
+ {renderComponent(onCreate)}
)}
- { - columns.map( - (column) => ( - -
- {column.toUpperCase()} -
- - ) - ) - } + {columns.map((column) => ( +
+ {column.toUpperCase()} +
+ ))} + {(onUpdate || onDelete) && ( +
Actions
+ )}
- - {data.map((item) => ( - -
- - { - Object.values(item).map((value) => ( -
- {value} -
- ) - ) - } - - {/* UPDATE BUTTON & FORM */} - {update_component && ( - <> - - {itemBeingUpdated === item.id && ( -
- {renderComponent(update_component, { item })} -
- )} - - )} - - {/* DELETE BUTTON */} - {delete_action && ( - - )} + {data.length === 0 ? ( +
+ No items found. {onCreate && `Click '+ ${title || 'Item'}' to add new items.`}
- ))} + ) : ( + data.map((item) => ( +
handleItemClick(item)} + > + {Object.entries(item) + .filter(([key]) => !key.toLowerCase().includes('id')) + .map(([key, value], index) => ( +
+ {key.toLowerCase() === 'progress' ? ( + value === 100 ? ( + Completed + ) : ( +
+
+ {value}% +
+ ) + ) : key.toLowerCase() === 'status' ? ( + + {value} + + ) : ( + {value} + )} +
+ ))} + {(onUpdate || onDelete) && ( +
+ {onUpdate && ( + + )} + {onDelete && ( + + )} +
+ )} + {itemBeingUpdated === item.id && onUpdate && ( +
+ {renderComponent(onUpdate, { item })} +
+ )} +
+ )) + )}
+
+ {selectedItem ? ( +
+

Details

+ {Object.entries(selectedItem) + .filter(([key]) => !key.toLowerCase().includes('id')) + .map(([key, value]) => ( +
+ {key}: + + {key.toLowerCase() === 'progress' ? ( + value === 100 ? ( + Completed + ) : ( + {value}% + ) + ) : ( + value + )} + +
+ ))} +
+ ) : ( +
+ Select an item to view details +
+ )} +
); }; diff --git a/src/pages/404.js b/src/pages/404.js new file mode 100644 index 0000000..9c03863 --- /dev/null +++ b/src/pages/404.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import '../styles/general.scss'; + +const NotFoundPage = () => { + return ( +
+
+

404

+

Strona nie została znaleziona

+

Przepraszamy, ale strona, której szukasz, nie istnieje.

+ + + +
+
+ ); +}; + +export default NotFoundPage; \ No newline at end of file diff --git a/src/pages/dashboard.js b/src/pages/dashboard.js index 117d6c5..6fb5665 100644 --- a/src/pages/dashboard.js +++ b/src/pages/dashboard.js @@ -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 ; + case 'ai-tasks': + return ; + case 'renders': + return ; + case 'servers': + return ; + case 'settings': + return ; + case '3d-models': + default: + return ; + } + }; return ( - <> - -
-
- -
-
-

Servers

-
    -
  1. - {/* fa-microchip */} -

    Dashboard

    -
  2. -
-

Rendering

-
    -
  1. - -

    3D Models

    -
  2. -
  3. - -

    Rendered Materials

    -
  4. -
-

AI Training

-
    -
  1. - -

    AI Models

    -
  2. -
-

User

-
    -
  1. - -

    Settings

    -
  2. -
  3. - -

    Log Out

    -
  4. -
-
-
- - - ) -} + <> + +
+
+ {renderContent()} +
+
+

Servers

+
    +
  1. handleNavigation('servers')} + > + +

    Dashboard

    +
  2. +
+

Rendering

+
    +
  1. handleNavigation('3d-models')} + > + +

    3D Models

    +
  2. +
  3. handleNavigation('renders')} + > + +

    Rendered Materials

    +
  4. +
+

AI Training

+
    +
  1. handleNavigation('ai-models')} + > + +

    AI Models

    +
  2. +
  3. handleNavigation('ai-tasks')} + > + +

    AI Training Tasks

    +
  4. +
+

User

+
    +
  1. handleNavigation('settings')} + > + +

    Settings

    +
  2. +
  3. + +

    Log Out

    +
  4. +
+
+
+ + + ); +}; - -export default DashboardPage \ No newline at end of file +export default DashboardPage; \ No newline at end of file diff --git a/src/pages/dashboards/3d-models.js b/src/pages/dashboards/3d-models.js new file mode 100644 index 0000000..b6d5e57 --- /dev/null +++ b/src/pages/dashboards/3d-models.js @@ -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 ( +
+
+

3D Models

+ +
+ + {message.text && ( +
+ {message.text} +
+ )} + + {isFormVisible && ( +
+
+ +
+
+ )} + + ( +
+
+
{model.name}
+
{model.type}
+
+ {model.status} +
+
+
+
Last Modified: {model.lastModified}
+
Size: {model.size}
+
+
+ )} + renderDetails={(model) => ( +
+

Model Details

+
+ ID: + {model.id} +
+
+ Nazwa: + {model.name} +
+
+ Typ: + {model.type} +
+
+ Status: + {model.status} +
+
+ Last Modified: + {model.lastModified} +
+
+ Size: + {model.size} +
+
+ )} + /> +
+ ); +}; + +export default ThreeDModelsDashboard; \ No newline at end of file diff --git a/src/pages/dashboards/ai.models.js b/src/pages/dashboards/ai.models.js index e69de29..d16caa6 100644 --- a/src/pages/dashboards/ai.models.js +++ b/src/pages/dashboards/ai.models.js @@ -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 ( +
+
+

AI Models

+ +
+ + {message.text && ( +
+ {message.text} +
+ )} + + {isFormVisible && ( +
+
+ +
+
+ )} + + ( +
+
+
{model.name}
+
{model.type}
+
+ {model.status} +
+
+
+
Version: {model.version}
+
Last Modified: {model.lastModified}
+
+
+ )} + renderDetails={(model) => ( +
+

AI Model Details

+
+ ID: + {model.id} +
+
+ Nazwa: + {model.name} +
+
+ Typ: + {model.type} +
+
+ Wersja: + {model.version} +
+
+ Status: + {model.status} +
+
+ Ostatnia modyfikacja: + {model.lastModified} +
+
+ )} + /> +
+ ); +}; + +export default AIModelsDashboard; + diff --git a/src/pages/dashboards/ai.tasks.js b/src/pages/dashboards/ai.tasks.js new file mode 100644 index 0000000..3f0fb88 --- /dev/null +++ b/src/pages/dashboards/ai.tasks.js @@ -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 ( +
+
+

AI Tasks

+ +
+ + {message.text && ( +
+ {message.text} +
+ )} + + {isFormVisible && ( +
+
+ +
+
+ )} + + ( +
+
+
{task.name}
+
{task.type}
+
+ {task.status} +
+
+
+
+
+ {task.progress}% +
+
+
+
Last Modified: {task.lastModified}
+
+
+ )} + renderDetails={(task) => ( +
+

Task Details

+
+ ID: + {task.id} +
+
+ Name: + {task.name} +
+
+ Type: + {task.type} +
+
+ Status: + {task.status} +
+
+ Progress: + {task.progress}% +
+
+ Last Modified: + {task.lastModified} +
+
+ )} + /> +
+ ); +}; + +export default AITasksDashboard; \ No newline at end of file diff --git a/src/pages/dashboards/models.js b/src/pages/dashboards/models.js deleted file mode 100644 index 24a9ffe..0000000 --- a/src/pages/dashboards/models.js +++ /dev/null @@ -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 ( - <> -
- -
- -
-
- - ) -} - -export default ModelsDashboard \ No newline at end of file diff --git a/src/pages/dashboards/renders.js b/src/pages/dashboards/renders.js index e69de29..4677f23 100644 --- a/src/pages/dashboards/renders.js +++ b/src/pages/dashboards/renders.js @@ -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 ( +
+
+

Rendered Materials

+ +
+ + {message.text && ( +
+ {message.text} +
+ )} + + {isFormVisible && ( +
+
+ +
+
+ )} + + ( +
+
+
{render.name}
+
{render.type}
+
+ {render.status} +
+
+
+
+
+ {render.progress}% +
+
+
+
Rozdzielczość: {render.resolution}
+
Ostatnia modyfikacja: {render.lastModified}
+
+
+ )} + renderDetails={(render) => ( +
+

Szczegóły renderu

+
+ ID: + {render.id} +
+
+ Nazwa: + {render.name} +
+
+ Typ: + {render.type} +
+
+ Rozdzielczość: + {render.resolution} +
+
+ Status: + {render.status} +
+
+ Postęp: + {render.progress}% +
+
+ Ostatnia modyfikacja: + {render.lastModified} +
+
+ )} + /> +
+ ); +}; + +export default RendersDashboard; + diff --git a/src/pages/dashboards/servers.js b/src/pages/dashboards/servers.js index e69de29..e182c3e 100644 --- a/src/pages/dashboards/servers.js +++ b/src/pages/dashboards/servers.js @@ -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 ( +
+
+

Servers

+ +
+ + {message.text && ( +
+ {message.text} +
+ )} + + {isFormVisible && ( +
+
+ +
+
+ )} + + ( +
+
+
{server.name}
+
{server.type}
+
+ {server.status} +
+
+
+
IP: {server.ip}
+
Ostatnia modyfikacja: {server.lastModified}
+
+
+ )} + renderDetails={(server) => ( +
+

Szczegóły serwera

+
+ ID: + {server.id} +
+
+ Nazwa: + {server.name} +
+
+ Typ: + {server.type} +
+
+ IP: + {server.ip} +
+
+ Status: + {server.status} +
+
+ Ostatnia modyfikacja: + {server.lastModified} +
+
+ )} + /> +
+ ); +}; + +export default ServersDashboard; + diff --git a/src/pages/dashboards/user.js b/src/pages/dashboards/user.js index e69de29..ba07e78 100644 --- a/src/pages/dashboards/user.js +++ b/src/pages/dashboards/user.js @@ -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 ( +
+
+
+

User Settings

+ +
+ + {message.text && ( +
+ {message.text} +
+ )} + + +
+
+ ); +}; + +export default UserSettings; diff --git a/src/styles/general.scss b/src/styles/general.scss index b82ca81..c7668a1 100644 --- a/src/styles/general.scss +++ b/src/styles/general.scss @@ -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; + } +}