onLoadFileDrop(event)} >
-
- {input.dropInfo}
-
+
onLoadFileDrop(event)}
+ className='upload_input_container'
+ >
+
+ {
+ typeof input.dropInfo === 'string' ?
+ input.dropInfo
+ :
+ input.dropInfo.name
+ }
+
+
+ {
+ typeof input.dropInfo === 'string' ?
+ ""
+ :
+ input.dropInfo.size
+ }
+
{
+ const startIndex = (currentPage - 1) * itemsPerPage;
+ return data.slice(startIndex, startIndex + itemsPerPage);
+ }, [data, currentPage, itemsPerPage]);
+
+ // Obsługa zmiany strony
+ const handlePageChange = (newPage) => {
+ setCurrentPage(newPage);
+ setSelectedItem(null);
+ };
+
+ // Obsługa zmiany liczby elementów na stronie
+ const handleItemsPerPageChange = (event) => {
+ const newItemsPerPage = parseInt(event.target.value);
+ setItemsPerPage(newItemsPerPage);
+ setCurrentPage(1);
+ setSelectedItem(null);
+ };
// Toggle the "create" form
const handleToggleCreate = () => {
@@ -135,7 +160,7 @@ export const ListGenerator = ({
No items found. {onCreate && `Click '+ ${title || 'Item'}' to add new items.`}
) : (
- data.map((item) => (
+ currentItems.map((item) => (
+ {data.length > 0 && (
+
+
+ Items per page:
+
+
+
+
+
+
+ Page {currentPage} of {totalPages}
+
+
+
+
+
+ )}
+
{selectedItem ? (
diff --git a/src/pages/dashboard.js b/src/pages/dashboard.js
index 6fb5665..da19c62 100644
--- a/src/pages/dashboard.js
+++ b/src/pages/dashboard.js
@@ -63,10 +63,10 @@ const DashboardPage = () => {
onClick={() => handleNavigation('servers')}
>
-
Dashboard
+
GPU Instances
-
Rendering
+
3D Stuff
- {
onClick={() => handleNavigation('renders')}
>
-
Rendered Materials
+ 3D Rendering
-
AI Training
+
AI Stuff
- {
onClick={() => handleNavigation('ai-tasks')}
>
-
AI Training Tasks
+ AI Training
User
diff --git a/src/pages/dashboards/3d-models.js b/src/pages/dashboards/3d-models.js
index b6d5e57..16e5b34 100644
--- a/src/pages/dashboards/3d-models.js
+++ b/src/pages/dashboards/3d-models.js
@@ -1,31 +1,244 @@
-import React, { useState } from 'react';
+import React, { useState, useMemo } from 'react';
import { ListGenerator } from '../../components/forms/listGenerator';
-import { FormGenerator } from '../../components/forms/formGenerator';
+import ModelUploadForm from '../../components/forms/3d_model_crud/threeDModelUpload';
const ThreeDModelsDashboard = () => {
const [selectedModel, setSelectedModel] = useState(null);
+ const [searchQuery, setSearchQuery] = useState('');
const [models, setModels] = useState([
{
id: 1,
- name: 'Model A',
- type: '3D',
+ name: 'Dragon Model',
+ type: 'Blender',
status: 'Active',
lastModified: '2024-03-20',
- size: '2.5MB'
+ version: '2.1'
},
{
id: 2,
- name: 'Model B',
- type: '3D',
+ name: 'Medieval Castle',
+ type: 'Maya',
status: 'Inactive',
lastModified: '2024-03-19',
- size: '1.8MB'
+ version: '1.5'
+ },
+ {
+ id: 3,
+ name: 'Sci-fi Weapon',
+ type: '3ds Max',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '1.0'
+ },
+ {
+ id: 4,
+ name: 'Forest Scene',
+ type: 'Blender',
+ status: 'Active',
+ lastModified: '2024-03-18',
+ version: '3.2'
+ },
+ {
+ id: 5,
+ name: 'Robot Character',
+ type: 'Maya',
+ status: 'Inactive',
+ lastModified: '2024-03-17',
+ version: '2.0'
+ },
+ {
+ id: 6,
+ name: 'Space Ship',
+ type: '3ds Max',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '1.8'
+ },
+ {
+ id: 7,
+ name: 'Ancient Temple',
+ type: 'Blender',
+ status: 'Active',
+ lastModified: '2024-03-19',
+ version: '2.4'
+ },
+ {
+ id: 8,
+ name: 'Fantasy Sword',
+ type: 'Maya',
+ status: 'Inactive',
+ lastModified: '2024-03-16',
+ version: '1.2'
+ },
+ {
+ id: 9,
+ name: 'City Block',
+ type: '3ds Max',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '2.7'
+ },
+ {
+ id: 10,
+ name: 'Warrior Character',
+ type: 'Blender',
+ status: 'Active',
+ lastModified: '2024-03-18',
+ version: '1.9'
+ },
+ {
+ id: 11,
+ name: 'Futuristic Car',
+ type: 'Maya',
+ status: 'Inactive',
+ lastModified: '2024-03-15',
+ version: '1.3'
+ },
+ {
+ id: 12,
+ name: 'Mountain Range',
+ type: '3ds Max',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '2.2'
+ },
+ {
+ id: 13,
+ name: 'Alien Creature',
+ type: 'Blender',
+ status: 'Active',
+ lastModified: '2024-03-19',
+ version: '1.6'
+ },
+ {
+ id: 14,
+ name: 'Magic Staff',
+ type: 'Maya',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '1.4'
+ },
+ {
+ id: 15,
+ name: 'Underground Cave',
+ type: '3ds Max',
+ status: 'Inactive',
+ lastModified: '2024-03-17',
+ version: '2.3'
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
- const [formMode, setFormMode] = useState('create'); // 'create' lub 'edit'
+ const [formMode, setFormMode] = useState('create');
const [message, setMessage] = useState({ type: '', text: '' });
+ const mockModels = [
+ {
+ id: 1,
+ name: "Dragon Model",
+ type: "Character",
+ status: "Completed",
+ progress: 100
+ },
+ {
+ id: 2,
+ name: "Medieval Castle",
+ type: "Environment",
+ status: "In Progress",
+ progress: 65
+ },
+ {
+ id: 3,
+ name: "Sci-fi Weapon",
+ type: "Prop",
+ status: "Queued",
+ progress: 0
+ },
+ {
+ id: 4,
+ name: "Forest Scene",
+ type: "Environment",
+ status: "Completed",
+ progress: 100
+ },
+ {
+ id: 5,
+ name: "Robot Character",
+ type: "Character",
+ status: "In Progress",
+ progress: 45
+ },
+ {
+ id: 6,
+ name: "Space Ship",
+ type: "Vehicle",
+ status: "Completed",
+ progress: 100
+ },
+ {
+ id: 7,
+ name: "Ancient Temple",
+ type: "Environment",
+ status: "In Progress",
+ progress: 78
+ },
+ {
+ id: 8,
+ name: "Fantasy Sword",
+ type: "Prop",
+ status: "Queued",
+ progress: 0
+ },
+ {
+ id: 9,
+ name: "City Block",
+ type: "Environment",
+ status: "Completed",
+ progress: 100
+ },
+ {
+ id: 10,
+ name: "Warrior Character",
+ type: "Character",
+ status: "In Progress",
+ progress: 89
+ },
+ {
+ id: 11,
+ name: "Futuristic Car",
+ type: "Vehicle",
+ status: "Queued",
+ progress: 0
+ },
+ {
+ id: 12,
+ name: "Mountain Range",
+ type: "Environment",
+ status: "Completed",
+ progress: 100
+ },
+ {
+ id: 13,
+ name: "Alien Creature",
+ type: "Character",
+ status: "In Progress",
+ progress: 34
+ },
+ {
+ id: 14,
+ name: "Magic Staff",
+ type: "Prop",
+ status: "Completed",
+ progress: 100
+ },
+ {
+ id: 15,
+ name: "Underground Cave",
+ type: "Environment",
+ status: "In Progress",
+ progress: 56
+ }
+ ];
+
const handleModelSelect = (model) => {
setSelectedModel(model);
};
@@ -60,28 +273,7 @@ const ThreeDModelsDashboard = () => {
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);
+ setMessage({ type: 'success', text: '3D Model has been deleted' });
};
const handleFormCancel = () => {
@@ -104,49 +296,44 @@ const ThreeDModelsDashboard = () => {
];
};
- 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'
- }
- ];
+ const filteredModels = useMemo(() => {
+ return models.filter(model =>
+ model.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ model.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ model.status.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }, [models, searchQuery]);
return (
3D Models
-
+
+
+ setSearchQuery(e.target.value)}
+ className="search-input"
+ />
+ {searchQuery && (
+
+ )}
+
+
+
{message.text && (
@@ -158,18 +345,13 @@ const ThreeDModelsDashboard = () => {
{isFormVisible && (
)}
{
+
Version: {model.version}
Last Modified: {model.lastModified}
-
Size: {model.size}
)}
renderDetails={(model) => (
-
Model Details
+
3D Model Details
ID:
{model.id}
- Nazwa:
+ Name:
{model.name}
- Typ:
+ Type:
{model.type}
+
+ Version:
+ {model.version}
+
Status:
{model.status}
@@ -212,10 +398,6 @@ const ThreeDModelsDashboard = () => {
Last Modified:
{model.lastModified}
-
- Size:
- {model.size}
-
)}
/>
diff --git a/src/pages/dashboards/ai.models.js b/src/pages/dashboards/ai.models.js
index d16caa6..b164294 100644
--- a/src/pages/dashboards/ai.models.js
+++ b/src/pages/dashboards/ai.models.js
@@ -1,9 +1,11 @@
-import React, { useState } from 'react';
+import React, { useState, useRef, useMemo } from 'react';
import { ListGenerator } from '../../components/forms/listGenerator';
import FormGenerator from '../../components/forms/formGenerator';
+import AIModelUploadForm from '../../components/forms/ai_model_crud/AIModelUpload';
const AIModelsDashboard = () => {
const [selectedModel, setSelectedModel] = useState(null);
+ const [searchQuery, setSearchQuery] = useState('');
const [models, setModels] = useState([
{
id: 1,
@@ -20,12 +22,178 @@ const AIModelsDashboard = () => {
status: 'Inactive',
lastModified: '2024-03-19',
version: '1.0'
+ },
+ {
+ id: 3,
+ name: 'yolo-v8',
+ type: 'object-detection',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '1.2'
+ },
+ {
+ id: 4,
+ name: 'wav2vec',
+ type: 'speech-recognition',
+ status: 'Active',
+ lastModified: '2024-03-18',
+ version: '3.0'
+ },
+ {
+ id: 5,
+ name: 'bert-base',
+ type: 'text-classification',
+ status: 'Inactive',
+ lastModified: '2024-03-17',
+ version: '2.0'
+ },
+ {
+ id: 6,
+ name: 'resnet-50',
+ type: 'image-classification',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '1.8'
+ },
+ {
+ id: 7,
+ name: 'detr',
+ type: 'object-detection',
+ status: 'Active',
+ lastModified: '2024-03-19',
+ version: '2.4'
+ },
+ {
+ id: 8,
+ name: 'whisper',
+ type: 'speech-recognition',
+ status: 'Inactive',
+ lastModified: '2024-03-16',
+ version: '1.2'
+ },
+ {
+ id: 9,
+ name: 'dalle-3',
+ type: 'text-to-image',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '2.7'
+ },
+ {
+ id: 10,
+ name: 'llama-2',
+ type: 'text-generation',
+ status: 'Active',
+ lastModified: '2024-03-18',
+ version: '1.9'
+ },
+ {
+ id: 11,
+ name: 'mask-rcnn',
+ type: 'image-segmentation',
+ status: 'Inactive',
+ lastModified: '2024-03-15',
+ version: '1.3'
+ },
+ {
+ id: 12,
+ name: 'roberta',
+ type: 'text-classification',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '2.2'
+ },
+ {
+ id: 13,
+ name: 'dino',
+ type: 'image-classification',
+ status: 'Active',
+ lastModified: '2024-03-19',
+ version: '1.6'
+ },
+ {
+ id: 14,
+ name: 'sam',
+ type: 'image-segmentation',
+ status: 'Active',
+ lastModified: '2024-03-20',
+ version: '1.4'
+ },
+ {
+ id: 15,
+ name: 'clip',
+ type: 'image-text',
+ status: 'Inactive',
+ lastModified: '2024-03-17',
+ version: '2.3'
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
const [formMode, setFormMode] = useState('create');
const [message, setMessage] = useState({ type: '', text: '' });
+ const nameInput = React.createRef();
+ const typeInput = React.createRef();
+ const versionInput = React.createRef();
+ const statusInput = React.createRef();
+
+ const formRefs = [
+ nameInput,
+ typeInput,
+ versionInput,
+ statusInput
+ ];
+
+ const inputList = [
+ {
+ type: 'info',
+ action: formMode === 'create' ? 'Create' : 'Update',
+ endpoint: 'ai/models',
+ button_value: formMode === 'create' ? '+ AI MODEL' : 'UPDATE',
+ allowButtonAction: false
+ },
+ {
+ type: 'text',
+ name: 'NAME',
+ ref: nameInput,
+ value: selectedModel?.name || '',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'select',
+ name: 'TYPE',
+ ref: typeInput,
+ options: [
+ { value: 'text-to-image', label: 'Text to Image' },
+ { value: 'image-to-text', label: 'Image to Text' }
+ ],
+ value: selectedModel?.type || 'text-to-image',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'text',
+ name: 'VERSION',
+ ref: versionInput,
+ value: selectedModel?.version || '1.0',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'select',
+ name: 'STATUS',
+ ref: statusInput,
+ options: [
+ { value: 'Active', label: 'Active' },
+ { value: 'Inactive', label: 'Inactive' }
+ ],
+ value: selectedModel?.status || 'Active',
+ onChange: null,
+ validationInfo: null
+ }
+ ];
+
const handleModelSelect = (model) => {
setSelectedModel(model);
};
@@ -104,56 +272,44 @@ const AIModelsDashboard = () => {
];
};
- 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'
- }
- ];
+ const filteredModels = useMemo(() => {
+ return models.filter(model =>
+ model.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ model.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ model.status.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }, [models, searchQuery]);
return (
AI Models
-
+
+
+ setSearchQuery(e.target.value)}
+ className="search-input"
+ />
+ {searchQuery && (
+
+ )}
+
+
+
{message.text && (
@@ -165,18 +321,13 @@ const AIModelsDashboard = () => {
{isFormVisible && (
)}
{
{model.id}
- Nazwa:
+ Name:
{model.name}
- Typ:
+ Type:
{model.type}
- Wersja:
+ Version:
{model.version}
@@ -220,7 +371,7 @@ const AIModelsDashboard = () => {
{model.status}
- Ostatnia modyfikacja:
+ Last Modified:
{model.lastModified}
diff --git a/src/pages/dashboards/ai.tasks.js b/src/pages/dashboards/ai.tasks.js
index 3f0fb88..611f8fa 100644
--- a/src/pages/dashboards/ai.tasks.js
+++ b/src/pages/dashboards/ai.tasks.js
@@ -1,31 +1,232 @@
-import React, { useState } from 'react';
+import React, { useState, useRef, useMemo } from 'react';
import { ListGenerator } from '../../components/forms/listGenerator';
-import { FormGenerator } from '../../components/forms/formGenerator';
+import FormGenerator from '../../components/forms/formGenerator';
const AITasksDashboard = () => {
const [selectedTask, setSelectedTask] = useState(null);
+ const [searchQuery, setSearchQuery] = useState('');
const [tasks, setTasks] = useState([
{
id: 1,
- name: 'Task A',
- type: 'Training',
- status: 'In Progress',
- lastModified: '2024-03-20',
- progress: 45
+ name: "Model Training - CNN",
+ type: "Training",
+ status: "Completed",
+ progress: 100,
+ model: "Object Detection v2",
+ startTime: "2024-03-20 09:00:00",
+ endTime: "2024-03-20 14:30:00"
},
{
id: 2,
- name: 'Task B',
- type: 'Inference',
- status: 'Completed',
- lastModified: '2024-03-19',
- progress: 100
+ name: "BERT Fine-tuning",
+ type: "Fine-tuning",
+ status: "In Progress",
+ progress: 75,
+ model: "Text Generator",
+ startTime: "2024-03-20 10:15:00",
+ endTime: null
+ },
+ {
+ id: 3,
+ name: "Model Evaluation",
+ type: "Evaluation",
+ status: "Queued",
+ progress: 0,
+ model: "Style Transfer v1",
+ startTime: null,
+ endTime: null
+ },
+ {
+ id: 4,
+ name: "Performance Testing",
+ type: "Testing",
+ status: "Completed",
+ progress: 100,
+ model: "Face Recognition",
+ startTime: "2024-03-19 15:00:00",
+ endTime: "2024-03-19 17:30:00"
+ },
+ {
+ id: 5,
+ name: "Model Training - RNN",
+ type: "Training",
+ status: "In Progress",
+ progress: 60,
+ model: "Language Translator",
+ startTime: "2024-03-20 08:45:00",
+ endTime: null
+ },
+ {
+ id: 6,
+ name: "GAN Training",
+ type: "Training",
+ status: "Completed",
+ progress: 100,
+ model: "Image Generation",
+ startTime: "2024-03-19 11:00:00",
+ endTime: "2024-03-19 18:30:00"
+ },
+ {
+ id: 7,
+ name: "Model Optimization",
+ type: "Fine-tuning",
+ status: "In Progress",
+ progress: 82,
+ model: "Voice Synthesis",
+ startTime: "2024-03-20 09:30:00",
+ endTime: null
+ },
+ {
+ id: 8,
+ name: "Accuracy Testing",
+ type: "Testing",
+ status: "Queued",
+ progress: 0,
+ model: "Pose Estimation",
+ startTime: null,
+ endTime: null
+ },
+ {
+ id: 9,
+ name: "Transfer Learning",
+ type: "Training",
+ status: "Completed",
+ progress: 100,
+ model: "Scene Understanding",
+ startTime: "2024-03-19 13:15:00",
+ endTime: "2024-03-19 16:45:00"
+ },
+ {
+ id: 10,
+ name: "Model Validation",
+ type: "Evaluation",
+ status: "In Progress",
+ progress: 45,
+ model: "Text Summarizer",
+ startTime: "2024-03-20 11:00:00",
+ endTime: null
+ },
+ {
+ id: 11,
+ name: "Hyperparameter Tuning",
+ type: "Fine-tuning",
+ status: "Queued",
+ progress: 0,
+ model: "Speech Recognition",
+ startTime: null,
+ endTime: null
+ },
+ {
+ id: 12,
+ name: "Model Training - YOLO",
+ type: "Training",
+ status: "Completed",
+ progress: 100,
+ model: "Object Tracking",
+ startTime: "2024-03-19 09:00:00",
+ endTime: "2024-03-19 15:30:00"
+ },
+ {
+ id: 13,
+ name: "Performance Optimization",
+ type: "Fine-tuning",
+ status: "In Progress",
+ progress: 68,
+ model: "Image Segmentation",
+ startTime: "2024-03-20 10:00:00",
+ endTime: null
+ },
+ {
+ id: 14,
+ name: "Model Deployment Test",
+ type: "Testing",
+ status: "Completed",
+ progress: 100,
+ model: "Sentiment Analysis",
+ startTime: "2024-03-19 14:00:00",
+ endTime: "2024-03-19 16:00:00"
+ },
+ {
+ id: 15,
+ name: "Model Training - GPT",
+ type: "Training",
+ status: "In Progress",
+ progress: 92,
+ model: "Chatbot Model",
+ startTime: "2024-03-20 07:30:00",
+ endTime: null
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
const [formMode, setFormMode] = useState('create');
const [message, setMessage] = useState({ type: '', text: '' });
+ const nameInput = React.createRef();
+ const typeInput = React.createRef();
+ const statusInput = React.createRef();
+ const progressInput = React.createRef();
+
+ const formRefs = [
+ nameInput,
+ typeInput,
+ statusInput,
+ progressInput
+ ];
+
+ const inputList = [
+ {
+ type: 'info',
+ action: formMode === 'create' ? 'Create' : 'Update',
+ endpoint: 'ai/tasks',
+ button_value: formMode === 'create' ? '+ AI TASK' : 'UPDATE',
+ allowButtonAction: false
+ },
+ {
+ type: 'text',
+ name: 'NAME',
+ ref: nameInput,
+ value: selectedTask?.name || '',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'select',
+ name: 'TYPE',
+ ref: typeInput,
+ options: [
+ { value: 'text-to-image', label: 'Text to Image' },
+ { value: 'image-to-text', label: 'Image to Text' }
+ ],
+ value: selectedTask?.type || 'text-to-image',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'select',
+ name: 'STATUS',
+ ref: statusInput,
+ options: [
+ { value: 'In Progress', label: 'In Progress' },
+ { value: 'Completed', label: 'Completed' },
+ { value: 'Failed', label: 'Failed' },
+ { value: 'Cancelled', label: 'Cancelled' }
+ ],
+ value: selectedTask?.status || 'In Progress',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'number',
+ name: 'PROGRESS',
+ ref: progressInput,
+ value: selectedTask?.progress || 0,
+ min: 0,
+ max: 100,
+ onChange: null,
+ validationInfo: null
+ }
+ ];
+
const handleTaskSelect = (task) => {
setSelectedTask(task);
};
@@ -114,6 +315,14 @@ const AITasksDashboard = () => {
setSelectedTask(null);
};
+ const handleFormAction = (refs) => {
+ const formData = {};
+ refs.forEach((ref, index) => {
+ formData[inputList[index].name] = ref.current.value;
+ });
+ handleFormSubmit(formData);
+ };
+
const getTaskActions = (task) => {
const actions = [];
@@ -147,52 +356,45 @@ const AITasksDashboard = () => {
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'
- }
- ];
+ const filteredTasks = useMemo(() => {
+ return tasks.filter(task =>
+ task.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ task.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ task.status.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ task.model.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }, [tasks, searchQuery]);
return (
-
AI Tasks
-
+
AI Training
+
+
+ setSearchQuery(e.target.value)}
+ className="search-input"
+ />
+ {searchQuery && (
+
+ )}
+
+
+
{message.text && (
@@ -205,17 +407,17 @@ const AITasksDashboard = () => {
)}
{
const [renders, setRenders] = useState([
{
id: 1,
- name: 'Render A',
- type: '3D',
- status: 'Completed',
- lastModified: '2024-03-20',
- resolution: '1920x1080',
- progress: 100
+ name: "Character Animation",
+ type: "Animation",
+ status: "Completed",
+ progress: 100,
+ model: "Hero Character",
+ startTime: "2024-03-20 09:00:00",
+ endTime: "2024-03-20 11:30:00"
},
{
id: 2,
- name: 'Render B',
- type: '2D',
- status: 'In Progress',
- lastModified: '2024-03-19',
- resolution: '3840x2160',
- progress: 45
+ name: "Environment Lighting",
+ type: "Still",
+ status: "In Progress",
+ progress: 75,
+ model: "Forest Scene",
+ startTime: "2024-03-20 10:15:00",
+ endTime: null
+ },
+ {
+ id: 3,
+ name: "Product Showcase",
+ type: "360 View",
+ status: "Queued",
+ progress: 0,
+ model: "Sports Car",
+ startTime: null,
+ endTime: null
+ },
+ {
+ id: 4,
+ name: "Battle Scene",
+ type: "Animation",
+ status: "Completed",
+ progress: 100,
+ model: "Warriors",
+ startTime: "2024-03-19 15:00:00",
+ endTime: "2024-03-19 18:30:00"
+ },
+ {
+ id: 5,
+ name: "Architectural Visualization",
+ type: "Still",
+ status: "In Progress",
+ progress: 60,
+ model: "Modern House",
+ startTime: "2024-03-20 08:45:00",
+ endTime: null
+ },
+ {
+ id: 6,
+ name: "Character Portrait",
+ type: "Still",
+ status: "Completed",
+ progress: 100,
+ model: "Fantasy Character",
+ startTime: "2024-03-19 11:00:00",
+ endTime: "2024-03-19 12:30:00"
+ },
+ {
+ id: 7,
+ name: "Vehicle Animation",
+ type: "Animation",
+ status: "In Progress",
+ progress: 82,
+ model: "Racing Car",
+ startTime: "2024-03-20 09:30:00",
+ endTime: null
+ },
+ {
+ id: 8,
+ name: "Product Display",
+ type: "360 View",
+ status: "Queued",
+ progress: 0,
+ model: "Smartphone",
+ startTime: null,
+ endTime: null
+ },
+ {
+ id: 9,
+ name: "Nature Scene",
+ type: "Still",
+ status: "Completed",
+ progress: 100,
+ model: "Mountain Landscape",
+ startTime: "2024-03-19 13:15:00",
+ endTime: "2024-03-19 15:45:00"
+ },
+ {
+ id: 10,
+ name: "Character Walk Cycle",
+ type: "Animation",
+ status: "In Progress",
+ progress: 45,
+ model: "Robot Character",
+ startTime: "2024-03-20 11:00:00",
+ endTime: null
+ },
+ {
+ id: 11,
+ name: "Jewelry Showcase",
+ type: "360 View",
+ status: "Queued",
+ progress: 0,
+ model: "Diamond Ring",
+ startTime: null,
+ endTime: null
+ },
+ {
+ id: 12,
+ name: "City Flythrough",
+ type: "Animation",
+ status: "Completed",
+ progress: 100,
+ model: "Future City",
+ startTime: "2024-03-19 09:00:00",
+ endTime: "2024-03-19 14:30:00"
+ },
+ {
+ id: 13,
+ name: "Interior Design",
+ type: "Still",
+ status: "In Progress",
+ progress: 68,
+ model: "Living Room",
+ startTime: "2024-03-20 10:00:00",
+ endTime: null
+ },
+ {
+ id: 14,
+ name: "Product Animation",
+ type: "Animation",
+ status: "Completed",
+ progress: 100,
+ model: "Gaming Console",
+ startTime: "2024-03-19 14:00:00",
+ endTime: "2024-03-19 16:00:00"
+ },
+ {
+ id: 15,
+ name: "Character Showcase",
+ type: "360 View",
+ status: "In Progress",
+ progress: 92,
+ model: "Superhero",
+ startTime: "2024-03-20 07:30:00",
+ endTime: null
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
const [formMode, setFormMode] = useState('create');
const [message, setMessage] = useState({ type: '', text: '' });
+ const [searchQuery, setSearchQuery] = useState('');
+
+ const nameInput = React.createRef();
+ const typeInput = React.createRef();
+ const resolutionInput = React.createRef();
+ const threeDModelInput = React.createRef();
+
+ const formRefs = [
+ nameInput,
+ typeInput,
+ resolutionInput,
+ threeDModelInput
+ ];
+
+ const inputList = [
+ {
+ type: 'info',
+ action: formMode === 'create' ? 'Create' : 'Update',
+ endpoint: 'renders',
+ button_value: formMode === 'create' ? '+ RENDER' : 'UPDATE',
+ allowButtonAction: false
+ },
+ {
+ type: 'text',
+ name: 'Name',
+ ref: nameInput,
+ value: selectedRender?.name || '',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'text',
+ name: 'Resolution',
+ ref: resolutionInput,
+ value: selectedRender?.resolution || '',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'choice-listing',
+ name: '3D Model',
+ ref: threeDModelInput,
+ values: selectedRender?.threeDModel|| '',
+ onChange: null,
+ validationInfo: null
+ },
+ ];
const handleRenderSelect = (render) => {
setSelectedRender(render);
@@ -107,71 +286,198 @@ const RendersDashboard = () => {
];
};
- const formFields = [
+ const mockRenders = [
{
- name: 'name',
- label: 'Nazwa renderu',
- type: 'text',
- required: true,
- value: selectedRender?.name || ''
+ id: 1,
+ name: "Dragon Scene",
+ type: "Animation",
+ status: "Completed",
+ progress: 100,
+ model: "Dragon Model",
+ startTime: "2024-03-01 10:00:00",
+ endTime: "2024-03-01 12:30:00"
},
{
- name: 'type',
- label: 'Typ renderu',
- type: 'select',
- required: true,
- options: [
- { value: '3D', label: '3D' },
- { value: '2D', label: '2D' }
- ],
- value: selectedRender?.type || '3D'
+ id: 2,
+ name: "Castle Exterior",
+ type: "Still",
+ status: "In Progress",
+ progress: 65,
+ model: "Medieval Castle",
+ startTime: "2024-03-02 09:00:00",
+ endTime: null
},
{
- 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'
+ id: 3,
+ name: "Weapon Showcase",
+ type: "360 View",
+ status: "Queued",
+ progress: 0,
+ model: "Sci-fi Weapon",
+ startTime: null,
+ endTime: null
},
{
- 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'
+ id: 4,
+ name: "Forest Flythrough",
+ type: "Animation",
+ status: "Completed",
+ progress: 100,
+ model: "Forest Scene",
+ startTime: "2024-03-01 14:00:00",
+ endTime: "2024-03-01 16:00:00"
},
{
- name: 'progress',
- label: 'Postęp',
- type: 'number',
- required: true,
- min: 0,
- max: 100,
- value: selectedRender?.progress || 0
+ id: 5,
+ name: "Robot Animation",
+ type: "Animation",
+ status: "In Progress",
+ progress: 45,
+ model: "Robot Character",
+ startTime: "2024-03-02 11:00:00",
+ endTime: null
+ },
+ {
+ id: 6,
+ name: "Spaceship Launch",
+ type: "Animation",
+ status: "Completed",
+ progress: 100,
+ model: "Space Ship",
+ startTime: "2024-03-01 13:00:00",
+ endTime: "2024-03-01 15:30:00"
+ },
+ {
+ id: 7,
+ name: "Temple Interior",
+ type: "Still",
+ status: "In Progress",
+ progress: 78,
+ model: "Ancient Temple",
+ startTime: "2024-03-02 10:00:00",
+ endTime: null
+ },
+ {
+ id: 8,
+ name: "Sword Display",
+ type: "360 View",
+ status: "Queued",
+ progress: 0,
+ model: "Fantasy Sword",
+ startTime: null,
+ endTime: null
+ },
+ {
+ id: 9,
+ name: "City Timelapse",
+ type: "Animation",
+ status: "Completed",
+ progress: 100,
+ model: "City Block",
+ startTime: "2024-03-01 09:00:00",
+ endTime: "2024-03-01 11:30:00"
+ },
+ {
+ id: 10,
+ name: "Warrior Battle",
+ type: "Animation",
+ status: "In Progress",
+ progress: 89,
+ model: "Warrior Character",
+ startTime: "2024-03-02 13:00:00",
+ endTime: null
+ },
+ {
+ id: 11,
+ name: "Car Showcase",
+ type: "360 View",
+ status: "Queued",
+ progress: 0,
+ model: "Futuristic Car",
+ startTime: null,
+ endTime: null
+ },
+ {
+ id: 12,
+ name: "Mountain Vista",
+ type: "Still",
+ status: "Completed",
+ progress: 100,
+ model: "Mountain Range",
+ startTime: "2024-03-01 15:00:00",
+ endTime: "2024-03-01 16:30:00"
+ },
+ {
+ id: 13,
+ name: "Alien Movement",
+ type: "Animation",
+ status: "In Progress",
+ progress: 34,
+ model: "Alien Creature",
+ startTime: "2024-03-02 14:00:00",
+ endTime: null
+ },
+ {
+ id: 14,
+ name: "Staff Effects",
+ type: "Animation",
+ status: "Completed",
+ progress: 100,
+ model: "Magic Staff",
+ startTime: "2024-03-01 16:00:00",
+ endTime: "2024-03-01 18:30:00"
+ },
+ {
+ id: 15,
+ name: "Cave Exploration",
+ type: "Animation",
+ status: "In Progress",
+ progress: 56,
+ model: "Underground Cave",
+ startTime: "2024-03-02 12:00:00",
+ endTime: null
}
];
+ const filteredRenders = useMemo(() => {
+ return renders.filter(render =>
+ render.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ render.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ render.status.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ render.model.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }, [renders, searchQuery]);
+
return (
-
Rendered Materials
-
+
3D Rendering
+
+
+ setSearchQuery(e.target.value)}
+ className="search-input"
+ />
+ {searchQuery && (
+
+ )}
+
+
+
{message.text && (
@@ -184,17 +490,16 @@ const RendersDashboard = () => {
)}
{
const [selectedServer, setSelectedServer] = useState(null);
+ const [searchQuery, setSearchQuery] = useState('');
const [servers, setServers] = useState([
{
id: 1,
- name: 'Server A',
- type: 'Render',
- status: 'Online',
- lastModified: '2024-03-20',
- ip: '192.168.1.100'
+ name: "Render Node 1",
+ type: "Render Node",
+ status: "Active",
+ progress: 100,
+ ip: "192.168.1.101",
+ lastActive: "2024-03-20 11:30:00"
},
{
id: 2,
- name: 'Server B',
- type: 'AI',
- status: 'Offline',
- lastModified: '2024-03-19',
- ip: '192.168.1.101'
+ name: "AI Training Node 1",
+ type: "AI Training Node",
+ status: "In Progress",
+ progress: 75,
+ ip: "192.168.1.102",
+ lastActive: "2024-03-20 11:29:00"
+ },
+ {
+ id: 3,
+ name: "Storage Server 1",
+ type: "Storage Server",
+ status: "Active",
+ progress: 100,
+ ip: "192.168.1.103",
+ lastActive: "2024-03-20 11:30:00"
+ },
+ {
+ id: 4,
+ name: "Render Node 2",
+ type: "Render Node",
+ status: "Inactive",
+ progress: 0,
+ ip: "192.168.1.104",
+ lastActive: "2024-03-20 10:15:00"
+ },
+ {
+ id: 5,
+ name: "AI Training Node 2",
+ type: "AI Training Node",
+ status: "Active",
+ progress: 100,
+ ip: "192.168.1.105",
+ lastActive: "2024-03-20 11:30:00"
+ },
+ {
+ id: 6,
+ name: "Storage Server 2",
+ type: "Storage Server",
+ status: "Active",
+ progress: 100,
+ ip: "192.168.1.106",
+ lastActive: "2024-03-20 11:30:00"
+ },
+ {
+ id: 7,
+ name: "Render Node 3",
+ type: "Render Node",
+ status: "In Progress",
+ progress: 82,
+ ip: "192.168.1.107",
+ lastActive: "2024-03-20 11:29:00"
+ },
+ {
+ id: 8,
+ name: "AI Training Node 3",
+ type: "AI Training Node",
+ status: "Queued",
+ progress: 0,
+ ip: "192.168.1.108",
+ lastActive: "2024-03-20 11:00:00"
+ },
+ {
+ id: 9,
+ name: "Storage Server 3",
+ type: "Storage Server",
+ status: "Active",
+ progress: 100,
+ ip: "192.168.1.109",
+ lastActive: "2024-03-20 11:30:00"
+ },
+ {
+ id: 10,
+ name: "Render Node 4",
+ type: "Render Node",
+ status: "In Progress",
+ progress: 45,
+ ip: "192.168.1.110",
+ lastActive: "2024-03-20 11:29:00"
+ },
+ {
+ id: 11,
+ name: "AI Training Node 4",
+ type: "AI Training Node",
+ status: "Active",
+ progress: 100,
+ ip: "192.168.1.111",
+ lastActive: "2024-03-20 11:30:00"
+ },
+ {
+ id: 12,
+ name: "Storage Server 4",
+ type: "Storage Server",
+ status: "Inactive",
+ progress: 0,
+ ip: "192.168.1.112",
+ lastActive: "2024-03-20 10:45:00"
+ },
+ {
+ id: 13,
+ name: "Render Node 5",
+ type: "Render Node",
+ status: "Active",
+ progress: 100,
+ ip: "192.168.1.113",
+ lastActive: "2024-03-20 11:30:00"
+ },
+ {
+ id: 14,
+ name: "AI Training Node 5",
+ type: "AI Training Node",
+ status: "In Progress",
+ progress: 68,
+ ip: "192.168.1.114",
+ lastActive: "2024-03-20 11:29:00"
+ },
+ {
+ id: 15,
+ name: "Storage Server 5",
+ type: "Storage Server",
+ status: "Active",
+ progress: 100,
+ ip: "192.168.1.115",
+ lastActive: "2024-03-20 11:30:00"
}
]);
const [isFormVisible, setIsFormVisible] = useState(false);
const [formMode, setFormMode] = useState('create');
const [message, setMessage] = useState({ type: '', text: '' });
+ const nameInput = React.createRef();
+ const typeInput = React.createRef();
+ const ipInput = React.createRef();
+ const statusInput = React.createRef();
+
+ const formRefs = [
+ nameInput,
+ typeInput,
+ ipInput,
+ statusInput
+ ];
+
+ const inputList = [
+ {
+ type: 'info',
+ action: formMode === 'create' ? 'Create' : 'Update',
+ endpoint: 'servers',
+ button_value: formMode === 'create' ? '+ SERVER' : 'UPDATE'
+ },
+ {
+ type: 'text',
+ name: 'NAME',
+ ref: nameInput,
+ value: selectedServer?.name || '',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'select',
+ name: 'TYPE',
+ ref: typeInput,
+ options: [
+ { value: 'Render', label: 'Render' },
+ { value: 'AI', label: 'AI' },
+ { value: 'Storage', label: 'Storage' }
+ ],
+ value: selectedServer?.type || 'Render',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'text',
+ name: 'IP',
+ ref: ipInput,
+ value: selectedServer?.ip || '',
+ onChange: null,
+ validationInfo: null
+ },
+ {
+ type: 'select',
+ name: 'STATUS',
+ ref: statusInput,
+ options: [
+ { value: 'Online', label: 'Online' },
+ { value: 'Offline', label: 'Offline' },
+ { value: 'Maintenance', label: 'Maintenance' }
+ ],
+ value: selectedServer?.status || 'Offline',
+ onChange: null,
+ validationInfo: null
+ }
+ ];
+
const handleServerSelect = (server) => {
setSelectedServer(server);
};
@@ -104,58 +287,53 @@ const ServersDashboard = () => {
];
};
- 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'
- }
- ];
+ const handleFormAction = (refs) => {
+ const formData = {};
+ refs.forEach((ref, index) => {
+ formData[inputList[index].name] = ref.current.value;
+ });
+ handleFormSubmit(formData);
+ };
+
+ const filteredServers = useMemo(() => {
+ return servers.filter(server =>
+ server.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ server.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ server.status.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ server.ip.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }, [servers, searchQuery]);
return (
-
Servers
-
+
GPU Instances
+
+
+ setSearchQuery(e.target.value)}
+ className="search-input"
+ />
+ {searchQuery && (
+
+ )}
+
+
+
{message.text && (
@@ -168,17 +346,16 @@ const ServersDashboard = () => {
)}
{
return baseInputs;
};
+ const formRefs = [usernameInput, emailInput, currentPasswordInput, newPasswordInput, confirmPasswordInput];
+
+ const handleFormAction = (refs) => {
+ const formData = {};
+ formRefs.forEach((ref, index) => {
+ formData[getInputList()[index].name] = ref.current.value;
+ });
+ handleSubmit(formRefs);
+ };
+
return (
@@ -157,17 +167,24 @@ const UserSettings = () => {
)}
-
+ {isEditing && (
+
+
+ ({
+ ...field,
+ ref: formRefs[index],
+ type: field.type,
+ name: field.name,
+ onChange: field.onChange,
+ validationInfo: field.validationInfo
+ }))}
+ refList={formRefs}
+ action={handleFormAction}
+ />
+
+
+ )}
);
diff --git a/src/pages/index.js b/src/pages/index.js
new file mode 100644
index 0000000..653173a
--- /dev/null
+++ b/src/pages/index.js
@@ -0,0 +1,11 @@
+import React from 'react';
+
+const IndexPage = () => {
+ return (
+
+ {/* Reszta komponentów aplikacji */}
+
+ );
+};
+
+export default IndexPage;
\ No newline at end of file
diff --git a/src/redux/asyncThunks/aiModelCrudAsyncThunk.js b/src/redux/asyncThunks/aiModelCrudAsyncThunk.js
new file mode 100644
index 0000000..73c061d
--- /dev/null
+++ b/src/redux/asyncThunks/aiModelCrudAsyncThunk.js
@@ -0,0 +1,100 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import axios from 'axios';
+
+const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
+
+// Helper function to get auth header
+const getAuthHeader = (token) => ({
+ headers: { Authorization: `Bearer ${token}` }
+});
+
+export const fetchAiModels = createAsyncThunk(
+ 'aiModelCrud/fetchModels',
+ async (_, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.get(`${API_URL}/ai/models`, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const fetchAiModel = createAsyncThunk(
+ 'aiModelCrud/fetchModel',
+ async (modelId, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.get(`${API_URL}/ai/models/${modelId}`, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const createAiModel = createAsyncThunk(
+ 'aiModelCrud/createModel',
+ async (modelData, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.post(`${API_URL}/ai/models`, modelData, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const updateAiModel = createAsyncThunk(
+ 'aiModelCrud/updateModel',
+ async ({ modelId, updates }, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.put(`${API_URL}/ai/models/${modelId}`, updates, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const deleteAiModel = createAsyncThunk(
+ 'aiModelCrud/deleteModel',
+ async (modelId, { getState }) => {
+ const { token } = getState().userAuth;
+ await axios.delete(`${API_URL}/ai/models/${modelId}`, getAuthHeader(token));
+ return modelId;
+ }
+);
+
+export const fetchAiTasks = createAsyncThunk(
+ 'aiModelCrud/fetchTasks',
+ async ({ status, page = 1, limit = 10 }, { getState }) => {
+ const { token } = getState().userAuth;
+ const params = { page, limit };
+ if (status) params.status = status;
+
+ const response = await axios.get(`${API_URL}/ai/tasks`, {
+ ...getAuthHeader(token),
+ params
+ });
+ return response.data;
+ }
+);
+
+export const createAiTask = createAsyncThunk(
+ 'aiModelCrud/createTask',
+ async (taskData, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.post(`${API_URL}/ai/tasks`, taskData, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const updateAiTask = createAsyncThunk(
+ 'aiModelCrud/updateTask',
+ async ({ taskId, action }, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.put(
+ `${API_URL}/ai/tasks/${taskId}`,
+ { action },
+ getAuthHeader(token)
+ );
+ return response.data;
+ }
+);
+
+export const deleteAiTask = createAsyncThunk(
+ 'aiModelCrud/deleteTask',
+ async (taskId, { getState }) => {
+ const { token } = getState().userAuth;
+ await axios.delete(`${API_URL}/ai/tasks/${taskId}`, getAuthHeader(token));
+ return taskId;
+ }
+);
\ No newline at end of file
diff --git a/src/redux/asyncThunks/renderCrudAsyncThunk.js b/src/redux/asyncThunks/renderCrudAsyncThunk.js
new file mode 100644
index 0000000..c1eb835
--- /dev/null
+++ b/src/redux/asyncThunks/renderCrudAsyncThunk.js
@@ -0,0 +1,94 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import axios from 'axios';
+
+const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
+
+// Helper function to get auth header
+const getAuthHeader = (token) => ({
+ headers: { Authorization: `Bearer ${token}` }
+});
+
+export const fetchRenders = createAsyncThunk(
+ 'renderCrud/fetchRenders',
+ async ({ page = 1, limit = 10 }, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.get(`${API_URL}/renders`, {
+ ...getAuthHeader(token),
+ params: { page, limit }
+ });
+ return response.data;
+ }
+);
+
+export const fetchRender = createAsyncThunk(
+ 'renderCrud/fetchRender',
+ async (renderId, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.get(`${API_URL}/renders/${renderId}`, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const updateRender = createAsyncThunk(
+ 'renderCrud/updateRender',
+ async ({ renderId, updates }, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.put(`${API_URL}/renders/${renderId}`, updates, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const deleteRender = createAsyncThunk(
+ 'renderCrud/deleteRender',
+ async (renderId, { getState }) => {
+ const { token } = getState().userAuth;
+ await axios.delete(`${API_URL}/renders/${renderId}`, getAuthHeader(token));
+ return renderId;
+ }
+);
+
+export const fetchRenderTasks = createAsyncThunk(
+ 'renderCrud/fetchTasks',
+ async ({ status, page = 1, limit = 10 }, { getState }) => {
+ const { token } = getState().userAuth;
+ const params = { page, limit };
+ if (status) params.status = status;
+
+ const response = await axios.get(`${API_URL}/renders/tasks`, {
+ ...getAuthHeader(token),
+ params
+ });
+ return response.data;
+ }
+);
+
+export const createRenderTask = createAsyncThunk(
+ 'renderCrud/createTask',
+ async (taskData, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.post(`${API_URL}/renders/tasks`, taskData, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const updateRenderTask = createAsyncThunk(
+ 'renderCrud/updateTask',
+ async ({ taskId, action }, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.put(
+ `${API_URL}/renders/tasks/${taskId}`,
+ { action },
+ getAuthHeader(token)
+ );
+ return response.data;
+ }
+);
+
+export const deleteRenderTask = createAsyncThunk(
+ 'renderCrud/deleteTask',
+ async (taskId, { getState }) => {
+ const { token } = getState().userAuth;
+ await axios.delete(`${API_URL}/renders/tasks/${taskId}`, getAuthHeader(token));
+ return taskId;
+ }
+);
\ No newline at end of file
diff --git a/src/redux/asyncThunks/threeDModelCrudAsyncThunk.js b/src/redux/asyncThunks/threeDModelCrudAsyncThunk.js
new file mode 100644
index 0000000..ba57981
--- /dev/null
+++ b/src/redux/asyncThunks/threeDModelCrudAsyncThunk.js
@@ -0,0 +1,72 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import axios from 'axios';
+
+const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
+
+// Helper function to get auth header
+const getAuthHeader = (token) => ({
+ headers: { Authorization: `Bearer ${token}` }
+});
+
+export const fetchModels = createAsyncThunk(
+ 'threeDModelCrud/fetchModels',
+ async (_, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.get(`${API_URL}/models`, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const fetchModel = createAsyncThunk(
+ 'threeDModelCrud/fetchModel',
+ async (modelId, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.get(`${API_URL}/models/${modelId}`, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const createModel = createAsyncThunk(
+ 'threeDModelCrud/createModel',
+ async (modelData, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.post(`${API_URL}/models`, modelData, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const updateModel = createAsyncThunk(
+ 'threeDModelCrud/updateModel',
+ async ({ modelId, updates }, { getState }) => {
+ const { token } = getState().userAuth;
+ const response = await axios.put(`${API_URL}/models/${modelId}`, updates, getAuthHeader(token));
+ return response.data;
+ }
+);
+
+export const deleteModel = createAsyncThunk(
+ 'threeDModelCrud/deleteModel',
+ async (modelId, { getState }) => {
+ const { token } = getState().userAuth;
+ await axios.delete(`${API_URL}/models/${modelId}`, getAuthHeader(token));
+ return modelId;
+ }
+);
+
+export const uploadModel = createAsyncThunk(
+ 'threeDModelCrud/uploadModel',
+ async ({ user_id, file, token }) => {
+ const formData = new FormData();
+ formData.append('user_id', user_id);
+ formData.append('file', file);
+ formData.append('token', token);
+
+ const response = await axios.post(`${API_URL}/models/upload`, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ Authorization: `Bearer ${token}`
+ }
+ });
+ return response.data;
+ }
+);
\ No newline at end of file
diff --git a/src/redux/asyncThunks/userAuthAsyncThunk.js b/src/redux/asyncThunks/userAuthAsyncThunk.js
new file mode 100644
index 0000000..55264dd
--- /dev/null
+++ b/src/redux/asyncThunks/userAuthAsyncThunk.js
@@ -0,0 +1,32 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import axios from 'axios';
+
+const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
+
+export const loginUser = createAsyncThunk(
+ 'userAuth/login',
+ async (credentials) => {
+ const formData = new FormData();
+ formData.append('username', credentials.username);
+ formData.append('password', credentials.password);
+
+ const response = await axios.post(`${API_URL}/auth`, formData);
+ return response.data;
+ }
+);
+
+export const registerUser = createAsyncThunk(
+ 'userAuth/register',
+ async (userData) => {
+ const response = await axios.post(`${API_URL}/register`, userData);
+ return response.data;
+ }
+);
+
+export const changePassword = createAsyncThunk(
+ 'userAuth/changePassword',
+ async (passwordData) => {
+ const response = await axios.post(`${API_URL}/change-password`, passwordData);
+ return response.data;
+ }
+);
\ No newline at end of file
diff --git a/src/redux/slices/aiModelCrudSlice.js b/src/redux/slices/aiModelCrudSlice.js
new file mode 100644
index 0000000..4c5c980
--- /dev/null
+++ b/src/redux/slices/aiModelCrudSlice.js
@@ -0,0 +1,170 @@
+import { createSlice } from '@reduxjs/toolkit';
+import {
+ fetchAiModels,
+ fetchAiModel,
+ createAiModel,
+ updateAiModel,
+ deleteAiModel,
+ createAiTask,
+ updateAiTask,
+ deleteAiTask,
+ fetchAiTasks
+} from '../asyncThunks/aiModelCrudAsyncThunk';
+
+const initialState = {
+ models: [],
+ selectedModel: null,
+ tasks: [],
+ isLoading: false,
+ error: null
+};
+
+const aiModelCrudSlice = createSlice({
+ name: 'aiModelCrud',
+ initialState,
+ reducers: {
+ clearError: (state) => {
+ state.error = null;
+ },
+ setSelectedModel: (state, action) => {
+ state.selectedModel = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+ // Fetch Models
+ builder.addCase(fetchAiModels.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(fetchAiModels.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.models = action.payload;
+ });
+ builder.addCase(fetchAiModels.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Fetch Single Model
+ builder.addCase(fetchAiModel.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(fetchAiModel.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.selectedModel = action.payload;
+ });
+ builder.addCase(fetchAiModel.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Create Model
+ builder.addCase(createAiModel.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(createAiModel.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.models.push(action.payload);
+ });
+ builder.addCase(createAiModel.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Update Model
+ builder.addCase(updateAiModel.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(updateAiModel.fulfilled, (state, action) => {
+ state.isLoading = false;
+ const index = state.models.findIndex(model => model.id === action.payload.id);
+ if (index !== -1) {
+ state.models[index] = action.payload;
+ }
+ });
+ builder.addCase(updateAiModel.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Delete Model
+ builder.addCase(deleteAiModel.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(deleteAiModel.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.models = state.models.filter(model => model.id !== action.payload);
+ });
+ builder.addCase(deleteAiModel.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Fetch Tasks
+ builder.addCase(fetchAiTasks.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(fetchAiTasks.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.tasks = action.payload;
+ });
+ builder.addCase(fetchAiTasks.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Create Task
+ builder.addCase(createAiTask.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(createAiTask.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.tasks.push(action.payload);
+ });
+ builder.addCase(createAiTask.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Update Task
+ builder.addCase(updateAiTask.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(updateAiTask.fulfilled, (state, action) => {
+ state.isLoading = false;
+ const index = state.tasks.findIndex(task => task.id === action.payload.id);
+ if (index !== -1) {
+ state.tasks[index] = action.payload;
+ }
+ });
+ builder.addCase(updateAiTask.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Delete Task
+ builder.addCase(deleteAiTask.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(deleteAiTask.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.tasks = state.tasks.filter(task => task.id !== action.payload);
+ });
+ builder.addCase(deleteAiTask.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+ }
+});
+
+export const { clearError, setSelectedModel } = aiModelCrudSlice.actions;
+export const aiModelCrudSelector = (state) => state.aiModelCrud;
+export default aiModelCrudSlice.reducer;
\ No newline at end of file
diff --git a/src/redux/slices/renderCrudSlice.js b/src/redux/slices/renderCrudSlice.js
new file mode 100644
index 0000000..092f1fd
--- /dev/null
+++ b/src/redux/slices/renderCrudSlice.js
@@ -0,0 +1,155 @@
+import { createSlice } from '@reduxjs/toolkit';
+import {
+ fetchRenders,
+ fetchRender,
+ updateRender,
+ deleteRender,
+ fetchRenderTasks,
+ createRenderTask,
+ updateRenderTask,
+ deleteRenderTask
+} from '../asyncThunks/renderCrudAsyncThunk';
+
+const initialState = {
+ renders: [],
+ selectedRender: null,
+ tasks: [],
+ isLoading: false,
+ error: null
+};
+
+const renderCrudSlice = createSlice({
+ name: 'renderCrud',
+ initialState,
+ reducers: {
+ clearError: (state) => {
+ state.error = null;
+ },
+ setSelectedRender: (state, action) => {
+ state.selectedRender = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+ // Fetch Renders
+ builder.addCase(fetchRenders.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(fetchRenders.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.renders = action.payload;
+ });
+ builder.addCase(fetchRenders.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Fetch Single Render
+ builder.addCase(fetchRender.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(fetchRender.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.selectedRender = action.payload;
+ });
+ builder.addCase(fetchRender.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Update Render
+ builder.addCase(updateRender.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(updateRender.fulfilled, (state, action) => {
+ state.isLoading = false;
+ const index = state.renders.findIndex(render => render.id === action.payload.id);
+ if (index !== -1) {
+ state.renders[index] = action.payload;
+ }
+ });
+ builder.addCase(updateRender.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Delete Render
+ builder.addCase(deleteRender.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(deleteRender.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.renders = state.renders.filter(render => render.id !== action.payload);
+ });
+ builder.addCase(deleteRender.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Fetch Tasks
+ builder.addCase(fetchRenderTasks.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(fetchRenderTasks.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.tasks = action.payload;
+ });
+ builder.addCase(fetchRenderTasks.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Create Task
+ builder.addCase(createRenderTask.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(createRenderTask.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.tasks.push(action.payload);
+ });
+ builder.addCase(createRenderTask.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Update Task
+ builder.addCase(updateRenderTask.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(updateRenderTask.fulfilled, (state, action) => {
+ state.isLoading = false;
+ const index = state.tasks.findIndex(task => task.id === action.payload.id);
+ if (index !== -1) {
+ state.tasks[index] = action.payload;
+ }
+ });
+ builder.addCase(updateRenderTask.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Delete Task
+ builder.addCase(deleteRenderTask.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(deleteRenderTask.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.tasks = state.tasks.filter(task => task.id !== action.payload);
+ });
+ builder.addCase(deleteRenderTask.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+ }
+});
+
+export const { clearError, setSelectedRender } = renderCrudSlice.actions;
+export const renderCrudSelector = (state) => state.renderCrud;
+export default renderCrudSlice.reducer;
\ No newline at end of file
diff --git a/src/redux/slices/threeDModelCrudSlice.js b/src/redux/slices/threeDModelCrudSlice.js
new file mode 100644
index 0000000..a1f875d
--- /dev/null
+++ b/src/redux/slices/threeDModelCrudSlice.js
@@ -0,0 +1,124 @@
+import { createSlice } from '@reduxjs/toolkit';
+import {
+ fetchModels,
+ fetchModel,
+ createModel,
+ updateModel,
+ deleteModel,
+ uploadModel
+} from '../asyncThunks/threeDModelCrudAsyncThunk';
+
+const initialState = {
+ models: [],
+ selectedModel: null,
+ isLoading: false,
+ error: null,
+ upload_blend_file_status: ''
+};
+
+const threeDModelCrudSlice = createSlice({
+ name: 'threeDModelCrud',
+ initialState,
+ reducers: {
+ clearError: (state) => {
+ state.error = null;
+ },
+ setSelectedModel: (state, action) => {
+ state.selectedModel = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+ // Fetch Models
+ builder.addCase(fetchModels.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(fetchModels.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.models = action.payload;
+ });
+ builder.addCase(fetchModels.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Fetch Single Model
+ builder.addCase(fetchModel.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(fetchModel.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.selectedModel = action.payload;
+ });
+ builder.addCase(fetchModel.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Create Model
+ builder.addCase(createModel.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(createModel.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.models.push(action.payload);
+ });
+ builder.addCase(createModel.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Update Model
+ builder.addCase(updateModel.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(updateModel.fulfilled, (state, action) => {
+ state.isLoading = false;
+ const index = state.models.findIndex(model => model.id === action.payload.id);
+ if (index !== -1) {
+ state.models[index] = action.payload;
+ }
+ });
+ builder.addCase(updateModel.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Delete Model
+ builder.addCase(deleteModel.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(deleteModel.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.models = state.models.filter(model => model.id !== action.payload);
+ });
+ builder.addCase(deleteModel.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Upload Model
+ builder.addCase(uploadModel.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ state.upload_blend_file_status = 'uploading';
+ });
+ builder.addCase(uploadModel.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.upload_blend_file_status = { info: 'Upload successful' };
+ });
+ builder.addCase(uploadModel.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ state.upload_blend_file_status = { info: 'Upload failed' };
+ });
+ }
+});
+
+export const { clearError, setSelectedModel } = threeDModelCrudSlice.actions;
+export const threeDModelCrudSelector = (state) => state.threeDModelCrud;
+export default threeDModelCrudSlice.reducer;
\ No newline at end of file
diff --git a/src/redux/slices/userAuthSlice.js b/src/redux/slices/userAuthSlice.js
new file mode 100644
index 0000000..a6efb00
--- /dev/null
+++ b/src/redux/slices/userAuthSlice.js
@@ -0,0 +1,72 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { loginUser, registerUser, changePassword } from '../asyncThunks/userAuthAsyncThunk';
+
+const initialState = {
+ user: null,
+ token: null,
+ isLoading: false,
+ error: null
+};
+
+const userAuthSlice = createSlice({
+ name: 'userAuth',
+ initialState,
+ reducers: {
+ logout: (state) => {
+ state.user = null;
+ state.token = null;
+ state.error = null;
+ },
+ clearError: (state) => {
+ state.error = null;
+ }
+ },
+ extraReducers: (builder) => {
+ // Login
+ builder.addCase(loginUser.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(loginUser.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.user = action.payload.user;
+ state.token = action.payload.token;
+ });
+ builder.addCase(loginUser.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Register
+ builder.addCase(registerUser.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(registerUser.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.user = action.payload.user;
+ state.token = action.payload.token;
+ });
+ builder.addCase(registerUser.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+
+ // Change Password
+ builder.addCase(changePassword.pending, (state) => {
+ state.isLoading = true;
+ state.error = null;
+ });
+ builder.addCase(changePassword.fulfilled, (state) => {
+ state.isLoading = false;
+ });
+ builder.addCase(changePassword.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.error.message;
+ });
+ }
+});
+
+export const { logout, clearError } = userAuthSlice.actions;
+export const userAuthSelector = (state) => state.userAuth;
+export default userAuthSlice.reducer;
\ No newline at end of file
diff --git a/src/redux/store.js b/src/redux/store.js
new file mode 100644
index 0000000..f01abdc
--- /dev/null
+++ b/src/redux/store.js
@@ -0,0 +1,14 @@
+import { configureStore } from '@reduxjs/toolkit';
+import userAuthReducer from './slices/userAuthSlice';
+import threeDModelCrudReducer from './slices/threeDModelCrudSlice';
+import aiModelCrudReducer from './slices/aiModelCrudSlice';
+import renderCrudReducer from './slices/renderCrudSlice';
+
+export const store = configureStore({
+ reducer: {
+ userAuth: userAuthReducer,
+ threeDModelCrud: threeDModelCrudReducer,
+ aiModelCrud: aiModelCrudReducer,
+ renderCrud: renderCrudReducer
+ }
+});
\ No newline at end of file
diff --git a/src/styles/general.scss b/src/styles/general.scss
index c7668a1..d8eb7d7 100644
--- a/src/styles/general.scss
+++ b/src/styles/general.scss
@@ -252,15 +252,15 @@ body, html {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.list-generator-container {
- width: 100%;
+ width: 100%;
display: flex;
flex-direction: column;
gap: 20px;
.table-header {
- display: flex;
+ display: flex;
justify-content: space-between;
- align-items: center;
+ align-items: center;
h2 {
margin: 0;
@@ -313,13 +313,13 @@ body, html {
background-color: rgba($background-color, 0.1);
border-radius: 4px;
- .item-column-row {
+ .item-column-row {
font-weight: 600;
color: $title-color;
- }
}
+ }
- .items-list {
+ .items-list {
display: flex;
flex-direction: column;
gap: 10px;
@@ -332,7 +332,7 @@ body, html {
border-radius: 4px;
}
- .item-row {
+ .item-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
@@ -352,8 +352,8 @@ body, html {
}
.item-info {
- display: flex;
- align-items: center;
+ display: flex;
+ align-items: center;
gap: 10px;
.progress-bar {
@@ -504,6 +504,75 @@ body, html {
}
}
}
+
+ .pagination-controls {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px 0;
+ margin-top: 20px;
+ border-top: 1px solid rgba($subtitle-color, 0.1);
+
+ .items-per-page {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+
+ span {
+ color: $subtitle-color;
+ }
+
+ select {
+ padding: 5px 10px;
+ background: $background-color;
+ color: $title-color;
+ border: 1px solid $border-color;
+ border-radius: 4px;
+ cursor: pointer;
+
+ &:focus {
+ outline: none;
+ border-color: $first-color;
+ }
+
+ option {
+ background: $background-color;
+ color: $title-color;
+ }
+ }
+ }
+
+ .pagination-buttons {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+
+ .pagination-button {
+ padding: 5px 10px;
+ background: $background-color;
+ color: $title-color;
+ border: 1px solid $border-color;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.2s;
+
+ &:hover:not(:disabled) {
+ background: $first-color;
+ border-color: $first-color;
+ }
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+ }
+
+ .page-info {
+ color: $subtitle-color;
+ margin: 0 10px;
+ }
+ }
+ }
}
.list-row {
@@ -513,7 +582,7 @@ body, html {
padding: 15px;
background-color: white;
border-radius: 4px;
- margin-bottom: 10px;
+ margin-bottom: 10px;
cursor: pointer;
transition: all 0.2s;
@@ -521,7 +590,7 @@ body, html {
background-color: rgba($background-color, 0.05);
}
- .item-info {
+ .item-info {
display: flex;
flex-direction: column;
gap: 5px;
@@ -585,7 +654,7 @@ body, html {
gap: 10px;
.progress-bar {
- width: 100%;
+ width: 100%;
height: 8px;
background-color: rgba($subtitle-color, 0.1);
border-radius: 4px;
@@ -1046,25 +1115,29 @@ body, html {
}
}
-.float_form_model {
- position: fixed;
- width: 400px;
- padding: 50px;
+.upload_input_container {
+ border: dashed 2px rgba(0,128,0,1);
border-radius: 10px;
- color: green;
- background-color: rgba(22,28,29,1);
- margin-left: 50%;
+ width: 350px - 4px - 20px - 20px;
+ padding-left: 20px;
+ padding-right: 20px;
+
+ p {
+ font-weight: bold;
+ font-size: 12px;
+ text-align: center;
+ }
.upload_input {
width: 0px !important;
height: 0px !important;
- padding-top: 70px;
- padding-left: 400px;
- margin-left: 0px;
+ padding-top: 10px;
+ padding-left: 100%;
+ padding-bottom: 10px;
color: rgba(0,128,0,1);
font-family: Ubuntu;
- border: dashed 2px rgba(0,128,0,1);
overflow: hidden;
+ cursor: pointer;
}
button {
@@ -1332,34 +1405,79 @@ body, html {
display: flex;
justify-content: space-between;
align-items: center;
- padding: 20px;
+ margin-bottom: 20px;
h2 {
- color: $title-color;
margin: 0;
- font-size: 24px;
}
- .create-button {
+ .dashboard-controls {
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;
+ gap: 16px;
- i {
- font-size: 16px;
+ .search-container {
+ position: relative;
+
+ .search-input {
+ width: 250px;
+ padding: 8px 32px 8px 12px;
+ background: $input-background;
+ border: 1px solid $border-color;
+ border-radius: 5px;
+ color: white;
+ transition: border-color 0.3s;
+
+ &:hover {
+ border-color: rgba(111,108,106,1);
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $first-color;
+ }
+ }
+
+ .clear-search {
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ color: #666;
+ cursor: pointer;
+ font-size: 18px;
+ padding: 0;
+ line-height: 1;
+
+ &:hover {
+ color: #333;
+ }
+ }
}
- &:hover {
- background: darken($first-color, 10%);
- transform: translateY(-2px);
+ .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);
+ }
}
}
}
@@ -1377,12 +1495,65 @@ body, html {
z-index: 1000;
.form-container {
- background: $form-background;
- padding: 30px;
- border-radius: 10px;
- width: 500px;
max-width: 90%;
max-height: 90vh;
overflow-y: auto;
}
}
+
+/* Stylizacja natywnych suwaków */
+::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+}
+
+::-webkit-scrollbar-track {
+ background: $form-background;
+ border-radius: 5px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: $first-color;
+ border-radius: 5px;
+
+ &:hover {
+ background: darken($first-color, 10%);
+ }
+
+ &:active {
+ background: darken($first-color, 20%);
+ }
+}
+
+::-webkit-scrollbar-button {
+ background: $first-color;
+
+ &:hover {
+ background: darken($first-color, 10%);
+ }
+}
+
+::-webkit-scrollbar-button:vertical:start:decrement,
+::-webkit-scrollbar-button:vertical:end:increment,
+::-webkit-scrollbar-button:horizontal:start:decrement,
+::-webkit-scrollbar-button:horizontal:end:increment {
+ background-color: $first-color;
+}
+
+/* Firefox */
+* {
+ scrollbar-width: thin;
+ scrollbar-color: $first-color $form-background;
+}
+
+/* Style dla wyłączonych suwaków */
+::-webkit-scrollbar-thumb:disabled,
+::-webkit-scrollbar-button:disabled {
+ background: rgba($first-color, 1);
+ cursor: not-allowed;
+}
+
+/* Wymuszenie stylów nawet gdy content nie wymaga przewijania */
+.dashboard-content {
+ min-height: 100%; /* Wymusza pojawienie się suwaka */
+}