diff --git a/README.md b/README.md index 57af35a..632a4cd 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,59 @@ -# DataLoom -Project is to design and implement a web-based GUI for data wrangling, aimed at simplifying the process of managing and transforming tabular datasets. This application will serve as a graphical interface for the powerful Python library, allowing users to perform complex data manipulation tasks without the need for in-depth programming knowledge. +DataLoom -### Apps and Packages +Project is to design and implement a web-based GUI for data wrangling, aimed at simplifying the process of managing and transforming tabular datasets. This application will serve as a graphical interface for the powerful Python library, allowing users to perform complex data manipulation tasks without the need for in-depth programming knowledge. -- `frontend`: a React.js app -- `backend`: Python(FastAPI) app +Apps and Packages -### Run Application -**Set Up Environment Variables** : -Create a `.env` file in the `apps/backend` directory and add details as per `.env.sample` file. +frontend: a React.js app -**Installing FastApi Backend** : In the `apps/backend` directory, run `python3 -m venv env`, then run `. env/scripts/activate` (On Windows), then ensure all required dependencies are installed by running `pip install -r requirements.txt`. +backend: Python (FastAPI) app + +Run Application + +Set Up Environment Variables + +Create a .env file in the apps/backend directory and add details as per .env.sample file. + +Installing FastAPI Backend + +Navigate to the apps/backend directory: + +cd apps/backend + +Create and activate a virtual environment: + +On Windows: + +python3 -m venv env +. env/Scripts/activate + +On macOS/Linux: + +python3 -m venv env +source env/bin/activate + +Install the required dependencies: + +pip install -r requirements.txt + +Installing Turbo and Dependencies + +Install Turbo globally if not already installed: + +npm install -g turbo + +Navigate to the project root and install dependencies: -**To run the project**, run the following command: -``` cd DataLoom +npm install --legacy-peer-deps + +Running the Application + +To start the project, run the following command from the root directory: + npm run dev -``` -The backend server will start and be accessible at `http://127.0.0.1:8000`. +The backend server will start and be accessible at http://127.0.0.1:8000. + +The frontend will run in development mode, utilizing Turbo for monorepo management, and will be accessible at http://localhost:5173/. + diff --git a/frontend/package.json b/frontend/package.json index d56fd6f..4ffe0b9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", @@ -10,25 +10,33 @@ "preview": "vite preview" }, "dependencies": { + "@hookform/resolvers": "^4.0.0", "@repo/ui": "*", + "@tanstack/react-query": "^5.66.0", "axios": "^1.7.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.54.2", "react-icons": "^5.2.1", - "react-router-dom": "^6.24.0" + "react-router-dom": "^6.24.0", + "sonner": "^1.7.4", + "tailwind-merge": "^3.0.1", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", "@vitejs/plugin-react": "^4.2.1", - "autoprefixer": "^10.4.19", + "autoprefixer": "^10.4.20", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", - "postcss": "^8.4.38", - "tailwindcss": "^3.4.4", + "postcss": "^8.5.2", + "tailwindcss": "^3.4.17", "vite": "^5.2.0" } -} +} \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1ee0c8c..ef4477a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,11 +4,16 @@ import {BrowserRouter as Router, Route, Routes, useLocation} from 'react-router- import DataScreen from './Components/DataScreen'; import HomeScreen from './Components/Homescreen'; import Navbar from './Components/Navbar'; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +const queryClient = new QueryClient(); function App() { return ( + + ); } diff --git a/frontend/src/Components/Homescreen.jsx b/frontend/src/Components/Homescreen.jsx index 61bc6af..0d812f4 100644 --- a/frontend/src/Components/Homescreen.jsx +++ b/frontend/src/Components/Homescreen.jsx @@ -1,113 +1,106 @@ - -import { useState, useEffect } from "react"; +import { useState } from "react"; import { useNavigate } from "react-router-dom"; +import { toast } from "sonner"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { uploadDataset, getRecentProjects, getDatasetDetails } from "../api.js"; const HomeScreen = () => { const [showModal, setShowModal] = useState(false); const [fileUpload, setFileUpload] = useState(null); - const [projectName, setProjectName] = useState(""); - const [projectDescription, setProjectDescription] = useState(""); - const [recentProjects, setRecentProjects] = useState([]); + const [formData, setFormData] = useState({ + projectName: "", + projectDescription: "" + }); + const [errors, setErrors] = useState({}); const navigate = useNavigate(); + const queryClient = useQueryClient(); - useEffect(() => { - fetchRecentProjects(); - }, []); - - const fetchRecentProjects = async () => { - try { + // React Query for fetching recent projects + const { data: recentProjects = [] } = useQuery({ + queryKey: ["recentProjects"], + queryFn: async () => { const response = await getRecentProjects(); - setRecentProjects(response.data); - } catch (error) { - console.error("Error fetching recent projects:", error); - } - }; - - const handleNewProjectClick = () => { - setShowModal(true); - }; - + return response.data; + }, + }); + + // React Query mutation for uploading dataset + const uploadMutation = useMutation({ + mutationFn: async ({ file, projectName, projectDescription }) => { + return await uploadDataset(file, projectName, projectDescription); + }, + onSuccess: (data) => { + queryClient.invalidateQueries(["recentProjects"]); + toast.success("Project created successfully!"); + setShowModal(false); + navigate("/data", { state: { datasetId: data.dataset_id, apiData: data } }); + }, + onError: (error) => { + toast.error("Error creating project: " + error.message); + }, + }); + + const handleNewProjectClick = () => setShowModal(true); const handleCloseModal = () => { setShowModal(false); + setFormData({ projectName: "", projectDescription: "" }); + setErrors({}); }; - const handleSubmitModal = async (event) => { - event.preventDefault(); - - if (!fileUpload) { - alert("Please select a file to upload"); - return; + const validateForm = () => { + const newErrors = {}; + if (!formData.projectName.trim()) { + newErrors.projectName = "Project name is required"; } - - if (!projectName.trim()) { - alert("Project Name cannot be empty"); - return; + if (!formData.projectDescription.trim()) { + newErrors.projectDescription = "Project description is required"; } - - if (!projectDescription.trim()) { - alert("Project Description cannot be empty"); - return; + if (!fileUpload) { + newErrors.file = "Please select a file to upload"; } + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; - const formData = new FormData(); - formData.append("file", fileUpload); - formData.append("projectName", projectName); - formData.append("projectDescription", projectDescription); + const handleSubmit = async (e) => { + e.preventDefault(); + if (!validateForm()) return; - try { - const data = await uploadDataset( - fileUpload, - projectName, - projectDescription - ); - console.log("Backend response data:", data); - - const datasetId = data.dataset_id; - console.log("Dataset ID:", datasetId); - - if (datasetId) { - navigate("/data", { state: { datasetId, apiData: data } }); - console.log( - "Navigating to data screen with datasetId and the data:", - datasetId, - data - ); - } else { - console.error("Dataset ID is undefined."); - alert("Error: Dataset ID is undefined."); - } - } catch (error) { - console.error("Error uploading file:", error); - alert("Error uploading file. Please try again."); - } - - setShowModal(false); - fetchRecentProjects(); // Refresh the recent projects list + uploadMutation.mutate({ + file: fileUpload, + projectName: formData.projectName, + projectDescription: formData.projectDescription, + }); }; const handleFileUpload = (event) => { const file = event.target.files[0]; setFileUpload(file); - console.log(file); + }; + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); }; const handleRecentProjectClick = async (datasetId) => { + if (!datasetId) { + toast.error("No project selected"); + return; + } + try { - // Fetch dataset details const data = await getDatasetDetails(datasetId); - console.log("Dataset details:", data); - - // Navigate to the data screen with the fetched data navigate("/data", { state: { datasetId, apiData: data } }); } catch (error) { - console.error("Error fetching dataset details:", error); - alert("Error fetching dataset details. Please try again."); + toast.error("Error fetching project details"); } }; - - // Default project names for buttons if there are less than 3 recent projects + // Default project names for buttons const defaultProjectNames = ["No Project", "No Project", "No Project"]; const projectNames = recentProjects .map((project) => project.name) @@ -119,87 +112,103 @@ const HomeScreen = () => {

Welcome to{" "} - DataLoom, + DataLoom

your one-stop for{" "} Dataset Transformations - .

-
+ +
- - - + {[0, 1, 2].map((index) => ( + + ))}
+ {showModal && ( -
-
-
-

Project Name

- setProjectName(e.target.value)} - /> -

Upload Dataset

- -

Project Description

- setProjectDescription(e.target.value)} - /> -
- - -
+
+
+

Create New Project

+
+
+ + + {errors.projectName && ( +

{errors.projectName}

+ )} +
+ +
+ + + {errors.file && ( +

{errors.file}

+ )} +
+ +
+ +