Compare commits

..

No commits in common. "master" and "fe-poprawki" have entirely different histories.

29 changed files with 14987 additions and 4267 deletions

7446
firm/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,8 +40,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11"
}
}

View File

@ -1 +0,0 @@
/* /index.html 200

BIN
firm/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,12 +1,43 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>FirmTracker</title>
</head>
<body>
<div id="root"></div>
</body>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>FirmTracker</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
firm/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
firm/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

25
firm/public/manifest.json Normal file
View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
firm/public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -47,9 +47,9 @@ const App = () => {
)}
<div className="w-3/4">
<Routes>
<Route path="/*" element={token ? <Navigate to="/harmonogram" /> : <Navigate to="/login" />} />
<Route path="/" element={token ? <Navigate to="/harmonogram" /> : <Navigate to="/login" />} />
<Route path="/login" element={token ? <Navigate to="/harmonogram" /> : <Login setToken={setToken} />} />
<Route path="/*" element={token ? <Navigate to="/transakcje" /> : <Navigate to="/login" />} />
<Route path="/" element={token ? <Navigate to="/transakcje" /> : <Navigate to="/login" />} />
<Route path="/login" element={token ? <Navigate to="/transakcje" /> : <Login setToken={setToken} />} />
<Route path="/transakcje" element={token ? <ZarzadzanieTransakcjami /> : <Navigate to="/login" />} />
<Route path="/transakcje/dodaj" element={token ? <DodawanieTransakcji /> : <Navigate to="/login" />} />
<Route path="/transakcje/edytuj/:id" element={token ? <EdycjaTransakcji /> : <Navigate to="/login" />} />

8
firm/src/App.test.js Normal file
View File

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -8,19 +8,21 @@ const DatePicker = ({ value, onChange, name, className, minDate, maxDate }) => {
return;
}
onChange({
target: {
name: name,
value: newValue,
},
});
onChange(e);
};
const getCurrentDate = () => {
const now = new Date();
const offset = now.getTimezoneOffset() * 60000;
const localISOTime = new Date(now.getTime() - offset).toISOString().slice(0, 16);
return localISOTime;
};
return (
<input
type="datetime-local"
name={name}
value={value}
value={value || getCurrentDate()}
onChange={handleChange}
className={className}
/>

View File

@ -61,7 +61,7 @@ const DodawanieProduktu = () => {
},
};
await axios.post('https://firmtracker-server.onrender.com/api/Products', payload, config);
await axios.post('https://localhost:7039/api/Products', payload, config);
setNewProduct({
name: '',
@ -73,6 +73,7 @@ const DodawanieProduktu = () => {
setError(null);
navigate('/produkty');
} catch (error) {
console.error('Błąd podczas dodawania produktu:', error);
setError('Wystąpił błąd podczas dodawania produktu. Spróbuj ponownie.');
}
};
@ -83,18 +84,22 @@ const DodawanieProduktu = () => {
<h2 className="text-2xl font-semibold text-gray-900 mb-8">Dodaj nowy produkt lub usługę</h2>
{error && <p className="text-red-500 mb-4">{error}</p>}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block mb-2 text-gray-700 font-medium">Nazwa</label>
<input
<input
type="text"
name="name"
value={newProduct.name}
onChange={handleInputChange}
placeholder="Nazwa produktu lub usługi"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
/></div>
<div>
<label className="block mb-2 text-gray-700 font-medium">Cena</label>
/>
<input
type="text"
name="description"
value={newProduct.description}
onChange={handleInputChange}
placeholder="Opis"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
<input
type="number"
step="0.01"
@ -104,75 +109,38 @@ const DodawanieProduktu = () => {
placeholder="Cena"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
</div>
<div>
<label className="block mb-2 text-gray-700 font-medium">Typ</label>
<div className="flex items-center space-x-6">
<label className="flex items-center space">
<input
type="radio"
name="type"
value="1"
checked={newProduct.type === "1"}
onChange={handleInputChange}
className="form-radio h-5 w-5 text-blue-500 focus:ring-blue-500"
/>
<span className="ml-2">Produkt</span>
</label>
<label className="flex items-center">
<input
type="radio"
name="type"
value="0"
checked={newProduct.type === "0"}
onChange={handleInputChange}
className="form-radio h-5 w-5 text-blue-500 focus:ring-blue-500"
/>
<span className="ml-2">Usługa</span>
</label>
</div>
</div>
<div>
<label className="block mb-2 text-gray-700 font-medium">Dostępność (ilość)</label>
<input
type="number"
name="availability"
value={newProduct.type === "0" ? 0 : newProduct.availability}
onChange={(e) => {
if (newProduct.type === "1") handleInputChange(e);
}}
disabled={newProduct.type === "0"}
className={`block w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none ${
newProduct.type === "0" ? "bg-gray-200 text-gray-500 cursor-not-allowed" : "border-gray-300"
}`}
/>
</div>
<div className="col-span-2">
<label className="block mb-2 text-gray-700 font-medium">Opis</label>
<input
type="text"
name="description"
value={newProduct.description}
<select
name="type"
value={newProduct.type}
onChange={handleInputChange}
placeholder="Opis"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
</div>
>
<option value="1">Produkt</option>
<option value="0">Usługa</option>
</select>
{newProduct.type === '1' && (
<input
type="number"
name="availability"
value={newProduct.availability}
onChange={handleInputChange}
placeholder="Dostępność (ilość)"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
)}
</div>
<div className="mt-6 flex justify-between">
<button
<button
onClick={handleCancel}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
>Anuluj</button>
<button
onClick={handleAddProduct}
className="bg-gradient-to-r from-green-500 to-green-700 text-white py-2 px-4 rounded-lg hover:from-green-600 hover:to-green-800 transition"
>
Dodaj
</button>
<button
onClick={handleCancel}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
>Anuluj</button>
</div>
</div>
</div>
</div>
);

View File

@ -45,9 +45,7 @@ const DodawanieTransakcji = () => {
},
};
const response = await axios.get('https://firmtracker-server.onrender.com/api/Products', config);
const response = await axios.get('https://localhost:7039/api/Products', config);
const productOptions = response.data.map(product => ({
value: product.id,
label: product.name,
@ -66,6 +64,7 @@ const DodawanieTransakcji = () => {
const handleInputChange = (event) => {
const { name, value } = event.target;
setNewTransaction({ ...newTransaction, [name]: value });
console.log(`po: ${name}, ${value}`)
};
const handleCancel = () => {
navigate('/transakcje');
@ -113,6 +112,8 @@ const DodawanieTransakcji = () => {
}
try {
console.log('Nowa transakcja:', newTransaction);
const token = localStorage.getItem('token');
if (!token) {
setError('Brak tokena. Użytkownik musi być zalogowany.');
@ -126,7 +127,7 @@ const DodawanieTransakcji = () => {
},
};
await axios.post('https://firmtracker-server.onrender.com/api/Transaction', newTransaction, config);
await axios.post('https://localhost:7039/api/Transaction', newTransaction, config);
setNewTransaction({
id: 0,
@ -148,6 +149,7 @@ const DodawanieTransakcji = () => {
});
navigate('/transakcje');
} catch (error) {
console.error('Błąd podczas dodawania transakcji:', error);
if (error.response && error.response.data) {
setError(error.response.data);
} else {
@ -188,6 +190,14 @@ const DodawanieTransakcji = () => {
</div>
{/*<input
type="datetime-local"
name="date"
value={newTransaction.date}
onChange={handleInputChange}
placeholder="Data"
className="block w-full mb-4 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>*/}
<label className="block mb-2 text-gray-700 font-medium">Produkty transakcji</label>
<div className="border border-gray-300 rounded-lg shadow-sm p-4 h-80 overflow-y-scroll">
{isLoading ? (
@ -216,13 +226,15 @@ const DodawanieTransakcji = () => {
placeholder="Ilość"
className="w-24 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{/*<button
onClick={() => handleRemoveProduct(index)}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
>
Usuń
</button>*/}
<button
onClick={() => handleRemoveProduct(index)}
className={`relative flex items-center justify-center w-10 h-10 rounded-full transition focus:outline-none ${newTransaction.transactionProducts.length > 1
? "text-gray-500 hover:text-red-600 hover:bg-red-100 active:bg-red-200"
: "text-gray-300 cursor-not-allowed"
}`}
disabled={newTransaction.transactionProducts.length <= 1}
onClick={handleRemoveProduct}
className="relative flex items-center justify-center w-10 h-10 rounded-full text-gray-500 hover:text-red-600 hover:bg-red-100 active:bg-red-200 transition focus:outline-none"
>
<MinusIcon className="w-5 h-5" />
</button>
@ -238,6 +250,14 @@ const DodawanieTransakcji = () => {
Dodaj produkt
</button>
</div>
{/*<input
type="text"
name="paymentType"
value={newTransaction.paymentType}
onChange={handleInputChange}
placeholder="Sposób płatności"
className="block w-full mb-4 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>*/}
<div className="mt-6 flex justify-between">
<div className="mb-4">
<label className="block mb-2 text-gray-700 font-medium">Metoda płatności</label>
@ -278,15 +298,13 @@ const DodawanieTransakcji = () => {
</div>
</div>
<div className="">
<label className="block mb-2 text-gray-700 font-medium">Rabat (%)</label>
<label className="block mb-2 text-gray-700 font-medium">Rabat</label>
<input
type="number"
name="discount"
value={newTransaction.discount}
onChange={handleInputChange}
placeholder="Rabat"
min="0"
max="100"
className="block w-full mb-4 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/></div>

View File

@ -23,7 +23,7 @@ const EdycjaProduktu = () => {
return;
}
const response = await axios.get(`https://firmtracker-server.onrender.com/api/Products/${id}`, {
const response = await axios.get(`https://localhost:7039/api/Products/${id}`, {
headers: {
Authorization: `Bearer ${token}`,
},
@ -38,6 +38,7 @@ const EdycjaProduktu = () => {
availability: productData.availability || '',
});
} catch (error) {
console.error('Błąd podczas pobierania produktu:', error);
setErrors({ general: 'Wystąpił błąd podczas pobierania danych produktu.' });
}
};
@ -113,10 +114,11 @@ const EdycjaProduktu = () => {
},
};
await axios.put(`https://firmtracker-server.onrender.com/api/Products/${id}`, payload, config);
await axios.put(`https://localhost:7039/api/Products/${id}`, payload, config);
setErrors({});
navigate('/produkty');
} catch (error) {
console.error('Błąd podczas zapisywania zmian:', error);
if (error.response && error.response.status === 400) {
setErrors({ general: error.response.data });
@ -131,10 +133,9 @@ const EdycjaProduktu = () => {
<div className="p-8 bg-white rounded-lg shadow-lg border border-gray-200 w-full max-w-4xl h-max">
<h2 className="text-2xl font-semibold text-gray-900 mb-4">Edycja produktu</h2>
{errors.general && <p className="text-red-500 mb-4">{errors.general}</p>}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="relative">
<label className="block mb-2 text-gray-700 font-medium">Nazwa</label>
<input
type="text"
name="name"
@ -145,8 +146,20 @@ const EdycjaProduktu = () => {
/>
{errors.name && <span className="absolute text-red-500 text-sm">{errors.name}</span>}
</div>
<div className="relative">
<input
type="text"
name="description"
value={product.description}
onChange={handleInputChange}
placeholder="Opis"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
{errors.description && <span className="absolute text-red-500 text-sm">{errors.description}</span>}
</div>
<div className="relative">
<label className="block mb-2 text-gray-700 font-medium">Cena</label>
<input
type="number"
step="0.01"
@ -158,80 +171,50 @@ const EdycjaProduktu = () => {
/>
{errors.price && <span className="absolute text-red-500 text-sm">{errors.price}</span>}
</div>
<div className="relative">
<label className="block mb-2 text-gray-700 font-medium">Typ</label>
<div className="flex items-center space-x-6">
<label className="flex items-center">
<input
type="radio"
name="type"
value="1"
checked={product.type === "1"}
onChange={handleInputChange}
className="form-radio h-5 w-5 text-blue-500 focus:ring-blue-500"
/>
<span className="ml-2">Produkt</span>
</label>
<label className="flex items-center">
<input
type="radio"
name="type"
value="0"
checked={product.type === "0"}
onChange={handleInputChange}
className="form-radio h-5 w-5 text-blue-500 focus:ring-blue-500"
/>
<span className="ml-2">Usługa</span>
</label>
<select
name="type"
value={product.type}
onChange={handleInputChange}
className="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
>
<option value="1">Produkt</option>
<option value="0">Usługa</option>
</select>
{errors.type && <span className="absolute text-red-500 text-sm">{errors.type}</span>}
</div>
</div>
<div>
<label className="block mb-2 text-gray-700 font-medium">Dostępność (ilość)</label>
<input
type="number"
name="availability"
value={product.type === "0" ? 0 : product.availability}
onChange={(e) => {
if (product.type === "1") handleInputChange(e);
}}
disabled={product.type === "0"}
className={`block w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none ${product.type === "0" ? "bg-gray-200 text-gray-500 cursor-not-allowed" : "border-gray-300"
}`}
/>
</div>
<div className="col-span-2">
<label className="block mb-2 text-gray-700 font-medium">Opis</label>
<input
type="text"
name="description"
value={product.description}
onChange={handleInputChange}
placeholder="Opis"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
</div>
{product.type === '1' && (
<div className="relative">
<input
type="number"
name="availability"
value={product.availability}
onChange={handleInputChange}
placeholder="Dostępność (ilość)"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
{errors.availability && <span className="absolute text-red-500 text-sm">{errors.availability}</span>}
</div>
)}
</div>
<div className="mt-6 flex justify-between">
<button
onClick={handleCancel}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
>Anuluj</button>
<button
onClick={handleSaveChanges}
className="bg-gradient-to-r from-green-500 to-green-700 text-white py-2 px-4 rounded-lg hover:from-green-600 hover:to-green-800 transition"
>
Zapisz zmiany
</button>
<button
onClick={handleCancel}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
>Anuluj
</button>
</div>
</div>
</div>
);
);
};
export default EdycjaProduktu;

View File

@ -1,9 +1,7 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import { useParams, useNavigate } from "react-router-dom";
import { ReactComponent as MinusIcon } from "../icons/minus-icon.svg";
import Select from "react-select";
import DatePicker from "./DatePicker";
const EdycjaTransakcji = () => {
const { id } = useParams();
@ -18,7 +16,6 @@ const EdycjaTransakcji = () => {
totalPrice: 0,
});
const [products, setProducts] = useState([]);
const [pendingRemovals, setPendingRemovals] = useState([]);
const [error, setError] = useState(null);
const [errors, setErrors] = useState({});
const navigate = useNavigate();
@ -39,10 +36,10 @@ const EdycjaTransakcji = () => {
try {
const [transactionRes, productsRes] = await Promise.all([
axios.get(`https://firmtracker-server.onrender.com/api/transaction/${id}`, {
axios.get(`https://localhost:7039/api/transaction/${id}`, {
headers: { Authorization: `Bearer ${token}` },
}),
axios.get("https://firmtracker-server.onrender.com/api/Products", {
axios.get("https://localhost:7039/api/Products", {
headers: { Authorization: `Bearer ${token}` },
}),
]);
@ -65,6 +62,7 @@ const EdycjaTransakcji = () => {
}));
setProducts(productOptions);
} catch (err) {
console.error(err);
setError("Wystąpił błąd podczas ładowania danych.");
}
};
@ -93,8 +91,8 @@ const EdycjaTransakcji = () => {
};
const handleCancel = () => {
navigate("/transakcje");
};
navigate('/transakcje');
}
const handleInputChange = (event) => {
const { name, value } = event.target;
@ -134,18 +132,45 @@ const EdycjaTransakcji = () => {
setErrors((prevErrors) => ({ ...prevErrors, [`quantity_${index}`]: null }));
};
const handleRemoveProduct = (index) => {
const handleRemoveProduct = async (index) => {
const token = getToken();
if (!token) {
console.error("Brak tokena, nie można usunąć produktu.");
setError("Użytkownik musi być zalogowany.");
return;
}
const productToRemove = transaction.transactionProducts[index];
setTransaction((prev) => ({
...prev,
transactionProducts: prev.transactionProducts.filter((_, i) => i !== index),
}));
if (productToRemove.id) {
setPendingRemovals((prev) => [...prev, productToRemove]);
console.log(productToRemove);
if (!productToRemove || !productToRemove.id) {
console.error("Nie znaleziono ID transakcyjnego produktu. Usuwanie lokalne.");
setTransaction((prev) => ({
...prev,
transactionProducts: prev.transactionProducts.filter((_, i) => i !== index),
}));
return;
}
try {
await axios.delete(
`https://localhost:7039/api/Transaction/${transaction.id}/product/${productToRemove.productID}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
console.log(`Produkt o ID transakcji ${productToRemove.id} został usunięty.`);
setTransaction((prev) => ({
...prev,
transactionProducts: prev.transactionProducts.filter((_, i) => i !== index),
}));
} catch (err) {
console.error("Błąd podczas usuwania produktu:", err.response?.data || err.message);
setError(err.response?.data?.message || "Nie udało się usunąć produktu. Spróbuj ponownie.");
}
};
const handleSaveChanges = async () => {
if (!validateForm()) return;
@ -166,182 +191,124 @@ const EdycjaTransakcji = () => {
try {
await axios.put(
`https://firmtracker-server.onrender.com/api/transaction/${transaction.id}`,
`https://localhost:7039/api/transaction/${transaction.id}`,
updatedTransaction,
{ headers: { Authorization: `Bearer ${token}` } }
);
for (const product of pendingRemovals) {
await axios.delete(
`https://firmtracker-server.onrender.com/api/Transaction/${transaction.id}/product/${product.productID}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
}
navigate("/transakcje");
} catch (err) {
console.error(err);
setError(err.response?.data || "Wystąpił błąd podczas zapisywania zmian.");
setError(err.response.data);
}
};
return (
<div className="bg-white p-8 rounded-lg shadow-lg max-w-4xl mx-auto mt-6">
<h2 className="text-2xl font-bold mb-6 text-gray-800">Edytuj transakcję</h2>
<h2 className="text-2xl font-bold mb-6 text-gray-800">Edycja Transakcji</h2>
{error && <p className="text-red-500 mb-4">{error}</p>}
<div className="mb-4 flex items-center space-x-4">
<div>
<label className="block mb-2 text-gray-700 font-medium">Data transakcji</label>
<DatePicker
type="datetime-local"
name="date"
value={transaction.date}
onChange={handleInputChange}
className="flex-1 mb-4 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
maxDate="2099-12-31T23:59"
/>
{errors.date && <span className="text-red-500 text-sm">{errors.date}</span>}
</div>
<div className="mb-4">
<input
type="datetime-local"
name="date"
value={transaction.date}
onChange={handleInputChange}
placeholder="Data"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{errors.date && <span className="text-red-500 text-sm">{errors.date}</span>}
</div>
<div className="pb-4 items-center">
<label className="block mb-2 text-gray-700 font-medium">Nr pracownika</label>
<div className="mb-4">
<input
type="number"
name="employeeId"
value={transaction.employeeId}
onChange={handleInputChange}
placeholder="Nr. Pracownika"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{errors.employeeId && <span className="text-red-500 text-sm">{errors.employeeId}</span>}
</div>
{transaction.transactionProducts.map((product, index) => (
<div key={index} className="mb-4">
<Select
value={products.find((p) => p.value === product.productID) || null}
onChange={(option) => handleProductChange(index, option)}
options={products}
className="mb-2"
placeholder="Wybierz produkt"
/>
{errors[`productID_${index}`] && (
<span className="text-red-500 text-sm">{errors[`productID_${index}`]}</span>
)}
<input
type="number"
name="employeeId"
value={transaction.employeeId}
onChange={handleInputChange}
placeholder="Nr pracownika"
className="flex mb-4 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
value={product.quantity}
onChange={(e) => handleQuantityChange(index, e.target.value)}
placeholder="Ilość"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{errors.employeeId && <span className="text-red-500 text-sm">{errors.employeeId}</span>}
{errors[`quantity_${index}`] && (
<span className="text-red-500 text-sm">{errors[`quantity_${index}`]}</span>
)}
<button
onClick={() => handleRemoveProduct(index)}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition mt-3"
>
Usuń
</button>
</div>
))}
<label className="block mb-2 text-gray-700 font-medium">Produkty transkacji</label>
<div className="border border-gray-300 rounded-lg shadow-sm p-4 h-80 overflow-y-scroll">
{transaction.transactionProducts.map((product, index) => (
<div key={index} className="mb-4 flex items-center space-x-4">
<Select
name={`productName-${index}`}
value={products.find((option) => option.value === product.productID)}
onChange={(selectedOption) => handleProductChange(index, selectedOption)}
options={products}
className="flex-1"
placeholder="Wybierz produkt..."
/>
<input
type="number"
name={`quantity-${index}`}
value={product.quantity}
onChange={(e) => handleQuantityChange(index, e.target.value)}
placeholder="Ilość"
className="w-24 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={() => handleRemoveProduct(index)}
className={`relative flex items-center justify-center w-10 h-10 rounded-full transition focus:outline-none ${transaction.transactionProducts.length > 1
? "text-gray-500 hover:text-red-600 hover:bg-red-100 active:bg-red-200"
: "text-gray-300 cursor-not-allowed"
}`}
disabled={transaction.transactionProducts.length <= 1}
>
<MinusIcon className="w-5 h-5" />
</button>
</div>
))}
<button
onClick={handleAddProduct}
className="bg-gradient-to-r from-blue-500 to-blue-700 text-white py-2 px-4 rounded-lg hover:from-blue-600 hover:to-blue-800 transition mb-3"
>
Dodaj produkt
</button>
</div>
<button
onClick={handleAddProduct}
className="bg-gradient-to-r from-blue-500 to-blue-700 text-white font-bold py-2 px-4 mb-3 rounded-lg shadow-md hover:from-blue-600 hover:to-blue-800 transition"
>
Dodaj produkt
</button>
<div className="mt-6 flex justify-between">
<div className="mb-4">
<label className="block mb-2 text-gray-700 font-medium">Metoda płatności</label>
<div className="flex space-x-4">
<label className="flex items-center">
<input
type="radio"
name="paymentType"
value="BLIK"
checked={transaction.paymentType === "BLIK"}
onChange={handleInputChange}
className="form-radio h-5 w-5 text-blue-500 focus:ring-blue-500"
/>
<span className="ml-2">BLIK</span>
</label>
<label className="flex items-center">
<input
type="radio"
name="paymentType"
value="Gotówka"
checked={transaction.paymentType === "Gotówka"}
onChange={handleInputChange}
className="form-radio h-5 w-5 text-blue-500 focus:ring-blue-500"
/>
<span className="ml-2">Gotówka</span>
</label>
<label className="flex items-center">
<input
type="radio"
name="paymentType"
value="Karta płatnicza"
checked={transaction.paymentType === "Karta płatnicza"}
onChange={handleInputChange}
className="form-radio h-5 w-5 text-blue-500 focus:ring-blue-500"
/>
<span className="ml-2">Karta płatnicza</span>
</label>
</div>
</div>
<div>
<label className="block mb-2 text-gray-700 font-medium">Rabat (%)</label>
<input
type="number"
name="discount"
value={transaction.discount}
onChange={handleInputChange}
placeholder="Rabat"
min="0"
max="100"
className="block w-full mb-4 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div>
<label className="block mb-2 text-gray-700 font-medium">Opis</label>
<div className="mb-4">
<input
type="text"
name="description"
value={transaction.description}
name="paymentType"
value={transaction.paymentType}
onChange={handleInputChange}
placeholder="Opis"
className="block w-full mb-4 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Sposób płatności"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{errors.paymentType && <span className="text-red-500 text-sm">{errors.paymentType}</span>}
</div>
<div className="mb-4">
<input
type="number"
name="discount"
value={transaction.discount}
onChange={handleInputChange}
placeholder="Rabat"
className="block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{errors.discount && <span className="text-red-500 text-sm">{errors.discount}</span>}
</div>
<div className="mt-6 flex justify-between">
<button
onClick={handleSaveChanges}
className="bg-gradient-to-r from-blue-500 to-blue-700 text-white font-bold py-2 px-4 rounded-lg shadow-md hover:from-blue-600 hover:to-blue-800 transition"
className="bg-gradient-to-r from-green-500 to-green-700 text-white font-bold py-2 px-4 rounded-lg shadow-md hover:from-green-600 hover:to-green-800 transition"
>
Zapisz zmiany
</button>
<button
onClick={handleCancel}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
className="bg-gradient-to-r from-red-500 to-red-700 text-white font-bold py-2 px-4 rounded-lg shadow-md hover:from-red-600 hover:to-red-800 transition ml-4"
>
Anuluj
</button>
</div>
</div>
);
);
};
export default EdycjaTransakcji;

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect , useCallback } from 'react';
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Harmonogram = () => {
@ -28,11 +28,12 @@ const Harmonogram = () => {
const fetchWorkdays = async () => {
try {
const response = await axios.get('https://firmtracker-server.onrender.com/api/Workday/user/workdays', {
const response = await axios.get('https://localhost:7039/api/Workday/user/workdays', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
});
setWorkdays(response.data);
} catch (error) {
console.error('Błąd podczas pobierania dni roboczych:', error);
}
};
@ -45,12 +46,13 @@ const Harmonogram = () => {
try {
await axios.post(
'https://firmtracker-server.onrender.com/api/workday/start',
'https://localhost:7039/api/workday/start',
{},
{ headers: { Authorization: `Bearer ${token}` } }
);
setIsWorking(true);
} catch (error) {
console.error(error);
}
};
@ -63,7 +65,7 @@ const Harmonogram = () => {
try {
await axios.post(
'https://firmtracker-server.onrender.com/api/workday/stop',
'https://localhost:7039/api/workday/stop',
{},
{ headers: { Authorization: `Bearer ${token}` } }
);
@ -91,7 +93,7 @@ const Harmonogram = () => {
return `${year}-${month}-${day}`;
};
const generateDaysInMonth = useCallback(() => {
const generateDaysInMonth = () => {
const firstDayOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
const lastDayOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth() + 1, 0);
const firstDayWeekday = firstDayOfMonth.getDay() === 0 ? 6 : firstDayOfMonth.getDay() - 1;
@ -99,34 +101,34 @@ const Harmonogram = () => {
const days = [];
for (let i = 0; i < firstDayWeekday; i++) {
days.push(null);
days.push(null);
}
for (let i = 1; i <= numberOfDaysInMonth; i++) {
const day = new Date(displayDate.getFullYear(), displayDate.getMonth(), i);
const formattedDate = formatDate(day);
const day = new Date(displayDate.getFullYear(), displayDate.getMonth(), i);
const formattedDate = formatDate(day);
const dayWork = workdays.find(workday => {
const startDate = new Date(workday.startTime.split('T')[0]);
const endDate = new Date(workday.endTime.split('T')[0]);
return (
formattedDate === workday.startTime.split('T')[0] ||
(day >= startDate && day <= endDate)
);
});
const dayWork = workdays.find(workday => {
const startDate = new Date(workday.startTime.split('T')[0]);
const endDate = new Date(workday.endTime.split('T')[0]);
return (
formattedDate === workday.startTime.split('T')[0] ||
(day >= startDate && day <= endDate)
);
});
days.push({
number: i,
type: dayWork ? (dayWork.absence ? 'absence' : 'working') : 'default'
});
days.push({
number: i,
type: dayWork ? (dayWork.absence ? 'absence' : 'working') : 'default'
});
}
setDaysInMonth(days);
}, [displayDate, workdays]);
};
useEffect(() => {
generateDaysInMonth();
}, [generateDaysInMonth]);
}, [displayDate, workdays]);
const changeMonth = (direction) => {
setManualDateChange(true);
@ -164,6 +166,8 @@ const Harmonogram = () => {
);
});
console.log("Selected Date:", formattedDate);
console.log("Day Status:", dayStatus);
if (dayStatus) {
if (dayStatus.absence && dayStatus.absence.trim() !== "") {
@ -176,7 +180,7 @@ const Harmonogram = () => {
try {
setLoading(true);
const response = await axios.get(
`https://firmtracker-server.onrender.com/api/Workday/user/day/info/${formattedDate}`,
`https://localhost:7039/api/Workday/user/day/info/${formattedDate}`,
{ headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } }
);
const workdayDetails = response.data;
@ -189,6 +193,7 @@ const Harmonogram = () => {
absence: null,
});
} catch (error) {
console.error("Błąd podczas pobierania danych dnia roboczego:", error);
setSelectedDay({
date: formattedDate,
dayOfWeek: formatDayOfWeek(selectedDate),
@ -209,11 +214,6 @@ const Harmonogram = () => {
return (
<div className='p-10 ml-11'>
<div className="flex items-center justify-between py-6 px-8 bg-gradient-to-r from-blue-500 to-teal-500 rounded-xl shadow-md mb-6">
<h1 className="text-white text-4xl font-semibold">Harmonogram</h1>
</div>
<div className="container mx-auto px-4 py-6">
<div className="flex justify-between items-center mb-6">
<div className="text-lg font-semibold">
@ -227,6 +227,7 @@ const Harmonogram = () => {
{isWorking ? 'Zakończ pracę' : 'Rozpocznij pracę'}
</button>
</div>
<div className="flex gap-6">
<div className="w-2/3">
<div className="flex justify-between items-center mb-4">
@ -258,7 +259,7 @@ const Harmonogram = () => {
</div>
</div>
<div className="w-1/3 bg-gray-100 p-4 rounded-lg h-[350px]">
<div className="w-1/3 bg-gray-100 p-4 rounded-lg">
{loading ? (
<p className="text-center text-blue-500">Ładowanie danych...</p>
) : selectedDay ? (
@ -298,7 +299,7 @@ const Harmonogram = () => {
<p className="text-center text-gray-500">Wybierz dzień, aby zobaczyć szczegóły.</p>
)}
</div>
</div>
</div>
</div>
);

View File

@ -1,20 +1,19 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import {ReactComponent as EditIcon} from '../icons/edit.svg';
import {ReactComponent as KoszIcon} from '../icons/delete.svg';
import editIcon from '../icons/edit.png';
import koszIcon from '../icons/kosz.png';
import { useNavigate } from 'react-router-dom';
const ListaProduktow = ({ onAdd }) => {
const [products, setProducts] = useState([]);
const [deleteProductId, setDeleteProductId] = useState(null);
const [showModal, setShowModal] = useState(false);
const [deleteError, setDeleteError] = useState(null);
const navigate = useNavigate();
const fetchProducts = async () => {
const token = localStorage.getItem('token');
try {
const response = await axios.get('https://firmtracker-server.onrender.com/api/products', {
const response = await axios.get('https://localhost:7039/api/products', {
headers: { Authorization: `Bearer ${token}` },
});
setProducts(response.data);
@ -27,11 +26,6 @@ const ListaProduktow = ({ onAdd }) => {
fetchProducts();
}, []);
const openDeleteConfirmation = (productId) => {
setDeleteProductId(productId);
setShowModal(true);
};
const handleDeleteProduct = async () => {
const token = localStorage.getItem('token');
if (!token) {
@ -45,15 +39,13 @@ const ListaProduktow = ({ onAdd }) => {
}
try {
await axios.delete(`https://firmtracker-server.onrender.com/api/Products/${deleteProductId}`, {
await axios.delete(`https://localhost:7039/api/Products/${deleteProductId}`, {
headers: { Authorization: `Bearer ${token}` },
});
fetchProducts();
setShowModal(false);
setDeleteProductId(null);
} catch (error) {
setShowModal(false);
setDeleteError(error.response?.data || 'Nieznany błąd');
console.error('Błąd podczas usuwania produktu:', error);
}
};
@ -65,9 +57,9 @@ const ListaProduktow = ({ onAdd }) => {
return (
<div>
<div className="flex items-center justify-between py-6 px-8 bg-gradient-to-r from-blue-500 to-teal-500 rounded-xl shadow-md mb-6">
<h1 className="text-white text-4xl font-semibold">Katalog produktów oraz usług</h1>
<h1 className="text-white text-4xl font-semibold">Katalog Produktów oraz usług</h1>
<button onClick={onAdd} className="bg-gradient-to-r from-green-500 to-green-700 text-white py-2 px-4 rounded-lg hover:from-green-600 hover:to-green-800 transition">
Dodaj produkt
Dodaj Produkt
</button>
</div>
<div className="mt-5">
@ -84,7 +76,7 @@ const ListaProduktow = ({ onAdd }) => {
</thead>
<tbody className="text-gray-600">
{products.map((product) => (
<tr key={product.id} className="group hover:bg-gray-100 transition-colors">
<tr key={product.id} className="hover:bg-gray-50 transition-colors">
<td className="p-3">{product.id}</td>
<td className="p-3">{product.name}</td>
<td className="p-3">{product.description}</td>
@ -92,27 +84,31 @@ const ListaProduktow = ({ onAdd }) => {
<td className="p-3 text-center">
{product.type === 0 ? "" : product.availability}
</td>
<td className="p-3 flex justify-center space-x-2">
<div className="flex justify-center items-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<button
onClick={() => handleEditProduct(product.id)}
className="text-blue-500 hover:bg-blue-200 active:bg-blue-300 focus:outline-none p-2 rounded-full transition-colors"
>
<EditIcon className="w-5 h-5" />
</button>
<button
onClick={() => openDeleteConfirmation(product.id)}
className="text-red-500 hover:bg-red-200 active:bg-red-300 focus:outline-none p-2 rounded-full transition-colors"
>
<KoszIcon className="w-5 h-5" />
</button>
</div>
<td className="p-3 flex justify-center items-center space-x-2">
<button
onClick={() => handleEditProduct(product.id)}
className="bg-gradient-to-r from-blue-500 to-blue-700 text-white py-2 px-4 rounded-lg hover:from-blue-600 hover:to-blue-800 transition"
>
<img src={editIcon} alt="Edytuj" className="inline w-5 mr-2" />
Edytuj
</button>
<button
onClick={() => {
setDeleteProductId(product.id);
setShowModal(true);
}}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
>
<img src={koszIcon} alt="Usuń" className="inline w-5 mr-2" />
Usuń
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
))}
</tbody>
</table>
</div>
{showModal && (
<div className="fixed inset-0 bg-gray-500 bg-opacity-50 flex justify-center items-center z-50">
@ -135,20 +131,6 @@ const ListaProduktow = ({ onAdd }) => {
</div>
</div>
)}
{deleteError && (
<div className="fixed inset-0 bg-gray-500 bg-opacity-50 flex justify-center items-center z-50">
<div className="bg-white p-6 rounded-md shadow-lg w-96">
<h2 className="text-lg font-bold mb-4">Usuwanie produktu nie powiodło się.</h2>
<p className="text-red-500">{deleteError}</p>
<button
onClick={() => setDeleteError(null)}
className="bg-gray-500 text-white py-2 px-4 rounded"
>
Wróć
</button>
</div>
</div>
)}
</div>
);
};

View File

@ -1,5 +1,7 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
//import editIcon from '../icons/edit.png';
//import koszIcon from '../icons/kosz.png';
import {ReactComponent as EditIcon} from '../icons/edit.svg';
import {ReactComponent as KoszIcon} from '../icons/delete.svg';
import { useNavigate } from 'react-router-dom';
@ -9,7 +11,6 @@ const ListaTransakcji = ({ onAdd}) => {
const [transactions, setTransactions] = useState([]);
const [deleteTransactionId, setDeleteTransactionId] = useState(null);
const [showModal, setShowModal] = useState(false);
const [deleteError, setDeleteError] = useState(null);
const navigate = useNavigate();
const fetchTransactions = async () => {
@ -19,7 +20,7 @@ const ListaTransakcji = ({ onAdd}) => {
return;
}
try {
const response = await axios.get('https://firmtracker-server.onrender.com/api/Transaction', {
const response = await axios.get('https://localhost:7039/api/Transaction', {
headers: {
Authorization: `Bearer ${token}`,
},
@ -37,7 +38,7 @@ const ListaTransakcji = ({ onAdd}) => {
return;
}
try {
const response = await axios.get('https://firmtracker-server.onrender.com/api/Products', {
const response = await axios.get('https://localhost:7039/api/Products', {
headers: {
Authorization: `Bearer ${token}`,
},
@ -64,7 +65,7 @@ const ListaTransakcji = ({ onAdd}) => {
const handleDeleteTransaction = async () => {
try {
await axios.delete(`https://firmtracker-server.onrender.com/api/transaction/${deleteTransactionId}`, {
await axios.delete(`https://localhost:7039/api/transaction/${deleteTransactionId}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
@ -73,9 +74,8 @@ const ListaTransakcji = ({ onAdd}) => {
setShowModal(false);
setDeleteTransactionId(null);
} catch (error) {
setShowModal(false);
setDeleteError(error.response?.data || 'Nieznany błąd');
console.error('Błąd podczas usuwania produktu:', error);}
console.error('Błąd podczas usuwania transakcji:', error);
}
};
const openDeleteConfirmation = (transactionId) => {
@ -93,7 +93,7 @@ const ListaTransakcji = ({ onAdd}) => {
<div className="flex items-center justify-between py-6 px-8 bg-gradient-to-r from-blue-500 to-teal-500 rounded-xl shadow-md mb-6">
<h1 className="text-white text-4xl font-semibold">Lista transakcji</h1>
<button onClick={onAdd} className="bg-gradient-to-r from-green-500 to-green-700 text-white py-2 px-4 rounded-lg hover:from-green-600 hover:to-green-800 transition">
Dodaj transakcję
Dodaj Transakcję
</button>
</div>
<div className="w-8/10 mx-auto mt-2">
@ -103,7 +103,8 @@ const ListaTransakcji = ({ onAdd}) => {
<tr>
<th className="p-3 text-left">ID</th>
<th className="p-3 text-left">Data</th>
<th className="p-3 text-left">Produkty</th>
<th className="p-3 text-left">Produkt</th>
<th className="p-3 text-left">Ilość</th>
<th className="p-3 text-left">Kwota</th>
<th className="p-3 text-left">Metoda płatności</th>
<th className="p-3 text-center">Nr pracownika</th>
@ -115,9 +116,16 @@ const ListaTransakcji = ({ onAdd}) => {
<tr key={transaction.id} className="group hover:bg-gray-100 transition-colors">
<td className="p-3">{transaction.id}</td>
<td className="p-3">{formatDate(transaction.date)}</td>
<td className="p-3 truncate max-w-xs" title={transaction.transactionProducts.map(product => product.product.name).join(', ')}>
{transaction.transactionProducts.map(product => product.product.name).join(', ')}
</td>
<td className="p-3">
{transaction.transactionProducts.map(product => (
<div key={product.id}>{product.product.name}</div>
))}
</td>
<td className="p-3">
{transaction.transactionProducts.map(product => (
<div key={product.id}>{product.quantity}</div>
))}
</td>
<td className="p-3">{formatPrice(transaction.totalPrice)}</td>
<td className="p-3">{transaction.paymentType}</td>
<td className="p-3 text-center">{transaction.employeeId}</td>
@ -125,12 +133,14 @@ const ListaTransakcji = ({ onAdd}) => {
<div className="flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<button
onClick={() => handleEditTransaction(transaction.id)}
//className="bg-gradient-to-r from-blue-500 to-blue-700 text-white py-2 px-4 rounded-lg hover:from-blue-600 hover:to-blue-800 transition"
className="text-blue-500 hover:bg-blue-200 active:bg-blue-300 focus:outline-none p-2 rounded-full transition-colors"
>
<EditIcon className = "w-5 h-5"/>
</button>
<button
onClick={() => openDeleteConfirmation(transaction.id)}
//className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
className="text-red-500 hover:bg-red-200 active:bg-red-300 focus:outline-none p-2 rounded-full transition-colors"
>
<KoszIcon className = "w-5 h-5"/>
@ -152,10 +162,7 @@ const ListaTransakcji = ({ onAdd}) => {
<h2 className="text-lg font-bold mb-4">Czy na pewno chcesz usunąć transakcję?</h2>
<div className="flex justify-between">
<button
onClick={() => {
handleDeleteTransaction();
setShowModal(false);
}}
onClick={handleDeleteTransaction}
className="bg-red-500 text-white py-2 px-4 rounded"
>
Tak
@ -170,20 +177,6 @@ const ListaTransakcji = ({ onAdd}) => {
</div>
</div>
)}
{deleteError && (
<div className="fixed inset-0 bg-gray-500 bg-opacity-50 flex justify-center items-center z-50">
<div className="bg-white p-6 rounded-md shadow-lg w-96">
<h2 className="text-lg font-bold mb-4">Usuwanie transakcji nie powiodło się.</h2>
<p className="text-red-500">{deleteError}</p>
<button
onClick={() => setDeleteError(null)}
className="bg-gray-500 text-white py-2 px-4 rounded"
>
Wróć
</button>
</div>
</div>
)}
</div>
);
};

View File

@ -11,7 +11,7 @@ const Login = ({ setToken }) => {
const handleLogin = async (e) => {
e.preventDefault();
try {
const response = await axios.post('https://firmtracker-server.onrender.com/api/user/login', { email, password });
const response = await axios.post('https://localhost:7039/api/user/login', { email, password });
const token = response.data;
setToken(token);
@ -23,7 +23,7 @@ const Login = ({ setToken }) => {
};
return (
<div className="flex justify-center items-center h-screen w-screen">
<div className="flex justify-center items-center h-screen">
<form onSubmit={handleLogin} className="p-4 bg-white shadow-md rounded">
<h2 className="text-2xl mb-4 text-center">Logowanie</h2>
{error && <p className="text-red-500">{error}</p>}

View File

@ -7,7 +7,7 @@ const Navbar = ({ setToken }) => {
const navigate = useNavigate();
const [showDropdown, setShowDropdown] = useState(false);
const [userRole, setUserRole] = useState('');
const dropdownRef = useRef(null);
const dropdownRef = useRef(null); // Ref do obsługi kliknięć poza dropdownem
useEffect(() => {
const fetchUserRole = async () => {
@ -15,7 +15,7 @@ const Navbar = ({ setToken }) => {
if (!token) return;
try {
const response = await axios.get('https://firmtracker-server.onrender.com/api/user/role', {
const response = await axios.get('https://localhost:7039/api/user/role', {
headers: {
'Authorization': `Bearer ${token}`,
},

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import WidokHarmonogramu from './WidokHarmonogramu';
import DatePicker from './DatePicker';
const PanelAdministratora = () => {
const [selectedOption, setSelectedOption] = useState('harmonogramy');
@ -12,55 +11,11 @@ const PanelAdministratora = () => {
const [selectedEmail, setSelectedEmail] = useState('');
const [workdays, setWorkdays] = useState([]);
const [absenceType, setAbsenceType] = useState('');
const [userEmail, setUserEmail] = useState('');
const [userPassword, setUserPassword] = useState('');
const [userRole, setUserRole] = useState('');
const [changePasswordEmail, setChangePasswordEmail] = useState('');
const [changePasswordValue, setChangePasswordValue] = useState('');
const [errors, setErrors] = useState({});
const validateInputs = () => {
const newErrors = {};
if (!userEmail) newErrors.email = "Pole email jest wymagane.";
if (!userPassword) newErrors.password = "Pole hasło jest wymagane.";
if (!userRole) newErrors.role = "Wybór roli jest wymagany.";
return newErrors;
};
const addUser = async () => {
const validationErrors = validateInputs();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
setErrors({});
try {
await axios.post(
"https://firmtracker-server.onrender.com/api/user/create",
{
login: userEmail,
email: userEmail,
password: userPassword,
role: userRole,
newEncryption: true,
},
{
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
}
);
setUserEmail("");
setUserPassword("");
setUserRole("");
} catch (error) {
console.error("Błąd podczas tworzenia konta:", error);
}
};
// Funkcja pobierania emaili
const fetchEmails = async () => {
try {
const response = await axios.get('https://firmtracker-server.onrender.com/api/user/emails', {
const response = await axios.get('https://localhost:7039/api/user/emails', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
});
setEmails(response.data);
@ -69,32 +24,7 @@ const PanelAdministratora = () => {
}
};
const changePassword = async () => {
if (!changePasswordEmail || !changePasswordValue) {
alert("Wszystkie pola muszą być wypełnione!");
return;
}
try {
await axios.post(
'https://firmtracker-server.onrender.com/api/user/ChangeUserPassword',
{
email: changePasswordEmail,
password: changePasswordValue,
},
{
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
}
);
setChangePasswordEmail('');
setChangePasswordValue('');
} catch (error) {
console.error('Błąd podczas zmiany hasła:', error);
}
};
// Funkcja dodawania absencji
const addAbsence = async () => {
if (!selectedEmail || !absenceType || !startDate || !endDate) {
alert("Wszystkie pola muszą być wypełnione!");
@ -102,7 +32,7 @@ const PanelAdministratora = () => {
}
try {
await axios.post('https://firmtracker-server.onrender.com/api/Workday/absence/add', {
await axios.post('https://localhost:7039/api/Workday/user/absence/add', {
userEmail: selectedEmail,
absenceType: absenceType,
startTime: startDate,
@ -117,6 +47,7 @@ const PanelAdministratora = () => {
}
};
// Funkcja pobierania raportu
const downloadReport = async () => {
if (!reportType || !startDate || !endDate) {
alert("Wszystkie pola muszą być wypełnione!");
@ -124,7 +55,7 @@ const PanelAdministratora = () => {
}
try {
const response = await axios.get('https://firmtracker-server.onrender.com/api/Pdf/download', {
const response = await axios.get('https://localhost:7039/api/Pdf/download', {
params: {
reportType: reportType,
startDate: startDate,
@ -145,6 +76,7 @@ const PanelAdministratora = () => {
}
};
// Funkcja pobierania harmonogramów
const fetchWorkdays = async (userEmail) => {
if (!userEmail) {
setWorkdays([]);
@ -152,7 +84,7 @@ const PanelAdministratora = () => {
}
try {
const response = await axios.get(`https://firmtracker-server.onrender.com/api/Workday/user/${userEmail}/workdays`, {
const response = await axios.get(`https://localhost:7039/api/Workday/user/${userEmail}/workdays`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
});
setWorkdays(response.data);
@ -161,6 +93,7 @@ const PanelAdministratora = () => {
}
};
// UseEffect do pobierania danych emaili przy pierwszym renderowaniu
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
@ -168,6 +101,7 @@ const PanelAdministratora = () => {
}
}, []);
// UseEffect do pobierania harmonogramu przy zmianie emaila
useEffect(() => {
if (selectedEmail) {
fetchWorkdays(selectedEmail);
@ -177,9 +111,9 @@ const PanelAdministratora = () => {
return (
<div className='p-10 ml-11'>
<div className='flex items-center justify-between py-6 px-8 bg-gradient-to-r from-blue-500 to-teal-500 rounded-xl shadow-md mb-6'>
<h1 className="text-white text-4xl font-semibold">Panel administratora</h1>
<h1 className="text-white text-4xl font-semibold">Panel Administratora</h1>
<div className="mr-10 text-lg flex">
<div className='px-5'>
<div className='px-10'>
<button
onClick={() => setSelectedOption('harmonogramy')}
className={`
@ -189,7 +123,7 @@ const PanelAdministratora = () => {
Harmonogramy
</button>
</div>
<div className='px-5'>
<div className='px-10'>
<button
onClick={() => setSelectedOption('absencje')}
className={`
@ -199,7 +133,7 @@ const PanelAdministratora = () => {
Absencje
</button>
</div>
<div className='px-5'>
<div>
<button
onClick={() => setSelectedOption('raporty')}
className={`
@ -209,26 +143,6 @@ const PanelAdministratora = () => {
Raporty
</button>
</div>
<div className='px-5'>
<button
onClick={() => setSelectedOption('konta')}
className={`
${selectedOption === 'konta' ? 'text-white font-bold' : 'text-gray-200'}
hover:text-white hover:bg-indigo-600 hover:rounded-lg transition duration-300 ease-in-out
`}>
Konta
</button>
</div>
<div className='px-5'>
<button
onClick={() => setSelectedOption('hasła')}
className={`
${selectedOption === 'hasła' ? 'text-white font-bold' : 'text-gray-200'}
hover:text-white hover:bg-indigo-600 hover:rounded-lg transition duration-300 ease-in-out
`}>
Hasła
</button>
</div>
</div>
</div>
@ -238,7 +152,7 @@ const PanelAdministratora = () => {
<div className="flex justify-center items-start pt-10">
<div className="bg-white p-8 rounded-xl shadow-lg w-full max-w-md">
<div className="mb-6">
<h2 className="text-2xl font-semibold text-center mb-4">Pobierz raport</h2>
<h2 className="text-2xl font-semibold text-center mb-4">Pobierz Raport</h2>
<div className="mb-4">
<label htmlFor="reportType" className="block text-lg font-medium text-gray-700 mb-2">Wybierz typ raportu:</label>
@ -256,25 +170,23 @@ const PanelAdministratora = () => {
<div className="mb-4">
<label htmlFor="startDate" className="block text-lg font-medium text-gray-700 mb-2">Wybierz datę początkową:</label>
<DatePicker
<input
type="datetime-local"
id="startDate"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
maxDate="2099-12-31T23:59"
/>
</div>
<div className="mb-6">
<label htmlFor="endDate" className="block text-lg font-medium text-gray-700 mb-2">Wybierz datę końcową:</label>
<DatePicker
<input
type="datetime-local"
id="endDate"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
maxDate="2099-12-31T23:59"
/>
</div>
@ -282,7 +194,7 @@ const PanelAdministratora = () => {
onClick={downloadReport}
className="w-full bg-blue-500 text-white py-3 px-4 rounded-lg mt-4 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300"
>
Pobierz raport
Pobierz Raport
</button>
</div>
</div>
@ -322,7 +234,7 @@ const PanelAdministratora = () => {
<div className="flex justify-center items-start pt-10">
<div className="bg-white p-8 rounded-xl shadow-lg w-full max-w-md">
<div className="mb-6">
<h2 className="text-2xl font-semibold text-center mb-4">Dodaj absencję</h2>
<h2 className="text-2xl font-semibold text-center mb-4">Dodaj Absencję</h2>
<div className="mb-4">
<label htmlFor="email" className="block text-lg font-medium text-gray-700 mb-2">Wybierz email:</label>
@ -355,25 +267,23 @@ const PanelAdministratora = () => {
<div className="mb-4">
<label htmlFor="startDate" className="block text-lg font-medium text-gray-700 mb-2">Wybierz datę początkową:</label>
<DatePicker
<input
type="datetime-local"
id="startDate"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
maxDate="2099-12-31T23:59"
/>
</div>
<div className="mb-6">
<label htmlFor="endDate" className="block text-lg font-medium text-gray-700 mb-2">Wybierz datę końcową:</label>
<DatePicker
<input
type="datetime-local"
id="endDate"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
maxDate="2099-12-31T23:59"
/>
</div>
@ -381,138 +291,13 @@ const PanelAdministratora = () => {
onClick={addAbsence}
className="w-full bg-blue-500 text-white py-3 px-4 rounded-lg mt-4 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300"
>
Dodaj absencję
Dodaj Absencję
</button>
</div>
</div>
</div>
)}
{selectedOption === 'konta' && (
<div className="flex justify-center items-start pt-10">
<div className="bg-white p-8 rounded-xl shadow-lg w-full max-w-md">
<div className="mb-6">
<h2 className="text-2xl font-semibold text-center mb-4">Dodaj konto</h2>
<div className="mb-4">
<label htmlFor="email" className="block text-lg font-medium text-gray-700 mb-2">
Email:
</label>
<input
type="email"
id="email"
value={userEmail}
onChange={(e) => setUserEmail(e.target.value)}
className={`w-full p-3 border ${
errors.email ? "border-red-500" : "border-gray-300"
} rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500`}
/>
{errors.email && <p className="text-red-500 text-sm">{errors.email}</p>}
</div>
<div className="mb-4">
<label htmlFor="password" className="block text-lg font-medium text-gray-700 mb-2">
Hasło:
</label>
<input
type="password"
id="password"
value={userPassword}
onChange={(e) => setUserPassword(e.target.value)}
className={`w-full p-3 border ${
errors.password ? "border-red-500" : "border-gray-300"
} rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500`}
/>
{errors.password && <p className="text-red-500 text-sm">{errors.password}</p>}
</div>
<div className="mb-4">
<label htmlFor="role" className="block text-lg font-medium text-gray-700 mb-2">
Rola:
</label>
<div className="flex items-center space-x-4">
<label className="flex items-center space-x-2">
<input
type="radio"
name="role"
value="user"
checked={userRole === "user"}
onChange={(e) => setUserRole(e.target.value)}
className="form-radio text-blue-500 focus:ring-blue-500"
/>
<span className="text-gray-700">User</span>
</label>
<label className="flex items-center space-x-2">
<input
type="radio"
name="role"
value="admin"
checked={userRole === "admin"}
onChange={(e) => setUserRole(e.target.value)}
className="form-radio text-blue-500 focus:ring-blue-500"
/>
<span className="text-gray-700">Admin</span>
</label>
</div>
{errors.role && <p className="text-red-500 text-sm">{errors.role}</p>}
</div>
<button
onClick={addUser}
className="w-full bg-blue-500 text-white py-3 px-4 rounded-lg mt-4 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300"
>
Dodaj konto
</button>
</div>
</div>
</div>
)}
{selectedOption === 'hasła' && (
<div className="flex justify-center items-start pt-10">
<div className="bg-white p-8 rounded-xl shadow-lg w-full max-w-md">
<div className="mb-6">
<h2 className="text-2xl font-semibold text-center mb-4">Zmień hasło</h2>
<div className="mb-4">
<label htmlFor="email" className="block text-lg font-medium text-gray-700 mb-2">Wybierz email:</label>
<select
id="email"
value={changePasswordEmail}
onChange={(e) => setChangePasswordEmail(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Wybierz...</option>
{emails.map((email) => (
<option key={email} value={email}>{email}</option>
))}
</select>
</div>
<div className="mb-4">
<label htmlFor="changePasswordValue" className="block text-lg font-medium text-gray-700 mb-2">
Nowe hasło:
</label>
<input
type="password"
id="changePasswordValue"
value={changePasswordValue}
onChange={(e) => setChangePasswordValue(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<button
onClick={changePassword}
className="w-full bg-blue-500 text-white py-3 px-4 rounded-lg mt-4 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300"
>
Zmień hasło
</button>
</div>
</div>
</div>
)}
</div>
);
};

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import {ReactComponent as KoszIcon} from '../icons/delete.svg';
import DatePicker from './DatePicker';
import koszIcon from "../icons/kosz.png";
const Raporty = () => {
const [fromDate, setFromDate] = useState('');
@ -18,7 +17,7 @@ const Raporty = () => {
return;
}
try {
const response = await axios.get('https://firmtracker-server.onrender.com/api/Report', {
const response = await axios.get('https://localhost:7039/api/Report', {
headers: {
Authorization: `Bearer ${token}`
}
@ -29,31 +28,6 @@ const Raporty = () => {
}
};
const validateYear = (dateString) => {
const year = dateString.split('-')[0];
return year.length === 4 && /^\d{4}$/.test(year);
};
const handleFromDateChange = (e) => {
const value = e.target.value;
if (validateYear(value)) {
setFromDate(value);
setError(null);
} else {
setError('Rok w dacie "Od" musi być 4-cyfrowy.');
}
};
const handleToDateChange = (e) => {
const value = e.target.value;
if (validateYear(value)) {
setToDate(value);
setError(null);
} else {
setError('Rok w dacie "Do" musi być 4-cyfrowy.');
}
};
const openDeleteConfirmation = (reportId) => {
setDeleteReportId(reportId);
setShowDeleteModal(true);
@ -76,7 +50,7 @@ const Raporty = () => {
const token = localStorage.getItem('token');
try {
console.log('Wysyłane dane:', fromDate, toDate);
const response = await axios.post('https://firmtracker-server.onrender.com/api/Report', {
const response = await axios.post('https://localhost:7039/api/Report', {
fromDate,
toDate
}, {
@ -99,7 +73,7 @@ const Raporty = () => {
const handleDeleteReport = async (reportId) => {
const token = localStorage.getItem('token');
try {
await axios.delete(`https://firmtracker-server.onrender.com/api/Report/${reportId}`, {
await axios.delete(`https://localhost:7039/api/Report/${reportId}`, {
headers: {
Authorization: `Bearer ${token}`
}
@ -132,25 +106,23 @@ const Raporty = () => {
<div className="mb-6 flex items-center space-x-6">
<div className="flex-1">
<label htmlFor="fromDate" className="block text-lg font-medium text-gray-700 mb-2">Od:</label>
<DatePicker
<input
type="datetime-local"
id="fromDate"
value={fromDate}
onChange={handleFromDateChange}
onChange={(e) => setFromDate(e.target.value)}
className="w-full px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
maxDate="2099-12-31T23:59"
/>
</div>
<div className="flex-1">
<label htmlFor="toDate" className="block text-lg font-medium text-gray-700 mb-2">Do:</label>
<DatePicker
<input
type="datetime-local"
id="toDate"
value={toDate}
onChange={handleToDateChange}
onChange={(e) => setToDate(e.target.value)}
className="w-full px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
maxDate="2099-12-31T23:59"
/>
</div>
</div>
@ -161,17 +133,10 @@ const Raporty = () => {
>
Generuj raport
</button>
{error && (
<div className="mt-4 text-red-500 text-center font-semibold">
{error}
</div>
)}
</div>
<div className="mt-5">
<table className="min-w-full border-collapse table-auto shadow-lg">
<thead className="bg-gray-200 text-gray-700">
@ -182,34 +147,47 @@ const Raporty = () => {
<th className="p-2 text-left">Suma dochodów</th>
<th className="p-2 text-left">Suma wydatków</th>
<th className="p-2 text-left">Bilans</th>
<th className="p-2 text-center">Usuń</th>
<th className="p-2 text-center"></th>
</tr>
</thead>
<tbody>
{reports.map((report) => (
<tr key={report.id} className="group hover:bg-gray-100 transition-colors">
<tr key={report.id} className="hover:bg-gray-50">
<td className="p-2">{report.id}</td>
<td className="p-2">{formatDate(report.fromDate)}</td>
<td className="p-2">{formatDate(report.toDate)}</td>
<td className="p-2">{report.totalIncome}</td>
<td className="p-2">{report.totalExpenses}</td>
<td className="p-2">{report.totalBalance}</td>
<td className="p-3 flex justify-center space-x-2">
<div className="flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<button
onClick={() => openDeleteConfirmation(report.id)}
className="text-red-500 hover:bg-red-200 active:bg-red-300 focus:outline-none p-2 rounded-full transition-colors"
>
<KoszIcon className = "w-5 h-5"/>
</button>
</div>
</td>
<td className="p-2 text-center">
<button
onClick={() => openDeleteConfirmation(report.id)}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
>
<img src={koszIcon} alt="Usuń" className="inline w-5 mr-2" /> Usuń
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{error && (
<div className="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
<div className="bg-white p-8 rounded-lg">
<h2 className="text-2xl font-bold mb-4">Błąd</h2>
<p>{error}</p>
<button
onClick={() => window.location.reload()}
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg"
>
Zamknij
</button>
</div>
</div>
)}
{showDeleteModal && (
<div className="fixed inset-0 bg-gray-500 bg-opacity-50 flex justify-center items-center z-50">
<div className="bg-white p-6 rounded-md shadow-lg w-96">

View File

@ -13,36 +13,36 @@ const Sidebar = ({ userRole }) => {
<ul>
{userRole !== 'User' && (
<>
<Link to="/panel" className="text-black px-10 py-2 block font-customFont text-center w-full hover:bg-gray-300 transition duration-100">
<Link to="/panel" className="text-black px-10 py-2 block font-customFont text-center w-max">
<li className='flex items-center'>
<img src={adminIcon} alt="Obrazek 1" className="w-7 h-7 mr-2" />
Panel administratora
Panel Administratora
</li></Link>
</>
)}
<Link to="/produkty" className="text-black px-10 py-2 block font-customFont text-center w-full hover:bg-gray-300 transition duration-100 ">
<Link to="/produkty" className="text-black px-10 py-2 block font-customFont text-center w-max">
<li className='flex items-center'>
<img src={produktIcon} alt="Obrazek 1" className="w-7 h-7 mr-2" />
Produkty
</li></Link>
<Link to="/transakcje" className="text-black px-10 py-2 block font-customFont text-center w-full hover:bg-gray-300 transition duration-100 ">
<Link to="/transakcje" className="text-black px-10 py-2 block font-customFont text-center w-max">
<li className='flex items-center'>
<img src={transakcjeIcon} alt="Obrazek 1" className="w-7 h-7 mr-2" />
Transakcje
</li></Link>
<Link to="/harmonogram" className="text-black px-10 py-2 block font-customFont text-center w-full flex-item-center hover:bg-gray-300 transition duration-100 ">
<Link to="/harmonogram" className="text-black px-10 py-2 block font-customFont text-center w-max flex-item-center">
<li className='flex items-center'>
<img src={harmonogramIcon} alt="Obrazek 1" className="w-7 h-7 mr-2" />
Harmonogram
</li></Link>
{userRole !== 'User' && (
<>
<Link to="/wydatki" className="text-black px-10 py-2 block font-customFont text-center w-full flex-item-center hover:bg-gray-300 transition duration-100">
<Link to="/wydatki" className="text-black px-10 py-2 block font-customFont text-center w-max flex-item-center">
<li className='flex items-center'>
<img src={wydatkiIcon} alt="Obrazek 1" className="w-7 h-7 mr-2" />
Wydatki
</li></Link>
<Link to="/raporty" className="text-black px-10 py-2 block font-customFont text-center w-full flex-item-center hover:bg-gray-300 transition duration-100">
<Link to="/raporty" className="text-black px-10 py-2 block font-customFont text-center w-max flex-item-center">
<li className='flex items-center'>
<img src={raportyIcon} alt="Obrazek 1" className="w-7 h-7 mr-2" />
Raporty

View File

@ -1,15 +1,20 @@
import React, { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const WidokHarmonogramu = ({ workdays }) => {
const [currentDate, setCurrentDate] = useState(new Date());
const [displayDate, setDisplayDate] = useState(new Date());
const [daysInMonth, setDaysInMonth] = useState([]);
const [manualDateChange, setManualDateChange] = useState(false);
const [isWorking, setIsWorking] = useState(false);
const [selectedDay, setSelectedDay] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
generateDaysInMonth();
}, [displayDate, workdays]);
const generateDaysInMonth = useCallback(() => {
const generateDaysInMonth = () => {
const firstDayOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
const lastDayOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth() + 1, 0);
const firstDayWeekday = firstDayOfMonth.getDay() === 0 ? 6 : firstDayOfMonth.getDay() - 1;
@ -17,35 +22,30 @@ const WidokHarmonogramu = ({ workdays }) => {
const days = [];
for (let i = 0; i < firstDayWeekday; i++) {
days.push(null);
days.push(null);
}
for (let i = 1; i <= numberOfDaysInMonth; i++) {
const day = new Date(displayDate.getFullYear(), displayDate.getMonth(), i);
const formattedDate = formatDate(day);
const day = new Date(displayDate.getFullYear(), displayDate.getMonth(), i);
const formattedDate = formatDate(day);
const dayWork = workdays.find(workday => {
const startDate = new Date(workday.startTime.split('T')[0]);
const endDate = new Date(workday.endTime.split('T')[0]);
return (
formattedDate === workday.startTime.split('T')[0] ||
(day >= startDate && day <= endDate)
);
});
const dayWork = workdays.find(workday => {
const startDate = new Date(workday.startTime.split('T')[0]);
const endDate = new Date(workday.endTime.split('T')[0]);
return (
formattedDate === workday.startTime.split('T')[0] ||
(day >= startDate && day <= endDate)
);
});
days.push({
number: i,
type: dayWork ? (dayWork.absence ? 'absence' : 'working') : 'default'
});
days.push({
number: i,
type: dayWork ? (dayWork.absence ? 'absence' : 'working') : 'default'
});
}
setDaysInMonth(days);
}, [displayDate, workdays]);
useEffect(() => {
generateDaysInMonth();
}, [generateDaysInMonth, manualDateChange]);
};
const changeMonth = (direction) => {
setManualDateChange(true);
@ -69,8 +69,7 @@ const WidokHarmonogramu = ({ workdays }) => {
const handleDayClick = async (day) => {
if (!day) return;
setLoading(true);
const selectedDate = new Date(displayDate.getFullYear(), displayDate.getMonth(), day, 0, 0, 0, 0);
const formattedDate = formatDate(selectedDate);
@ -97,7 +96,7 @@ const WidokHarmonogramu = ({ workdays }) => {
} else {
try {
const response = await axios.get(
`https://firmtracker-server.onrender.com/api/Workday/user/day/info/${formattedDate}`,
`https://localhost:7039/api/Workday/user/day/info/${formattedDate}`,
{ headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } }
);
const workdayDetails = response.data;
@ -125,7 +124,6 @@ const WidokHarmonogramu = ({ workdays }) => {
absence: "Brak danych o tym dniu",
});
}
setLoading(false);
};
@ -174,7 +172,7 @@ const WidokHarmonogramu = ({ workdays }) => {
</div>
</div>
<div className="w-1/3 bg-gray-100 p-4 rounded-lg h-[350px] border border-gray-300 rounded-lg shadow-sm p-4 h-80 overflow-y-scroll">
<div className="w-1/3 bg-gray-100 p-4 rounded-lg">
{loading ? (
<p className="text-center text-blue-500">Ładowanie danych...</p>
) : selectedDay ? (

View File

@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { ReactComponent as KoszIcon } from '../icons/delete.svg';
import DatePicker from './DatePicker';
import koszIcon from "../icons/kosz.png";
const Wydatki = () => {
const [expenses, setExpenses] = useState([]);
const [showModal, setShowModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [error, setError] = useState(null);
const [deleteExpenseId, setDeleteExpenseId] = useState(null);
@ -21,7 +21,7 @@ const Wydatki = () => {
return;
}
try {
const response = await axios.get('https://firmtracker-server.onrender.com/api/Expenses', {
const response = await axios.get('https://localhost:7039/api/Expenses', {
headers: { Authorization: `Bearer ${token}` },
});
setExpenses(response.data);
@ -40,19 +40,15 @@ const Wydatki = () => {
return;
}
if (newExpense.value <= 0) {
setError('Wartość wydatku musi być liczbą dodatnią.');
return;
}
const token = localStorage.getItem('token');
try {
const response = await axios.post('https://firmtracker-server.onrender.com/api/Expenses', newExpense, {
const response = await axios.post('https://localhost:7039/api/Expenses', newExpense, {
headers: { Authorization: `Bearer ${token}` },
});
const addedExpense = response.data;
setExpenses([...expenses, addedExpense]);
setNewExpense({ date: '', value: '', description: '' });
setShowModal(false);
} catch (error) {
console.error('Błąd podczas dodawania wydatku:', error);
setError('Wystąpił błąd podczas dodawania wydatku.');
@ -62,10 +58,11 @@ const Wydatki = () => {
const handleDeleteExpense = async () => {
const token = localStorage.getItem('token');
try {
await axios.delete(`https://firmtracker-server.onrender.com/api/Expenses/${deleteExpenseId}`, {
await axios.delete(`https://localhost:7039/api/Expenses/${deleteExpenseId}`, {
headers: { Authorization: `Bearer ${token}` },
});
// Optimistically update the local state by filtering out the deleted expense
setExpenses(expenses.filter(expense => expense.id !== deleteExpenseId));
setDeleteExpenseId(null);
setShowDeleteModal(false);
@ -90,81 +87,19 @@ const Wydatki = () => {
return date.toLocaleDateString('pl-PL', options).replace(",", "");
};
const handleDateChange = (e) => {
setNewExpense({ ...newExpense, date: e.target.value });
};
const handleValueChange = (e) => {
setNewExpense({ ...newExpense, value: e.target.value });
};
const handleDescriptionChange = (e) => {
setNewExpense({ ...newExpense, description: e.target.value });
};
return (
<div className="p-10 ml-11">
<div className="flex items-center justify-between py-6 px-8 bg-gradient-to-r from-blue-500 to-teal-500 rounded-xl shadow-md mb-6">
<h1 className="text-white text-4xl font-semibold">Wydatki</h1>
</div>
<div className="bg-white shadow-lg p-8 rounded-xl max-w-3xl mx-auto">
<div className="mb-6">
<div className="flex flex-col space-y-6">
<div className="flex space-x-6">
<div className="flex-1">
<label htmlFor="expenseDate" className="block text-sm font-medium text-gray-700">Data</label>
<DatePicker
type="datetime-local"
id="expenseDate"
value={newExpense.date}
onChange={handleDateChange}
className="mt-1 py-2 px-3 block w-full shadow-md sm:text-sm rounded-lg border-gray-300 focus:ring-indigo-500 focus:border-indigo-500"
maxDate="2099-12-31T23:59"
/>
</div>
<div className="flex-1">
<label htmlFor="expenseValue" className="block text-sm font-medium text-gray-700">Wartość</label>
<input
type="number"
id="expenseValue"
value={newExpense.value}
onChange={handleValueChange}
className="mt-1 py-2 px-3 block w-full shadow-md sm:text-sm rounded-lg border-gray-300 focus:ring-indigo-500 focus:border-indigo-500"
/>
</div>
</div>
<div>
<label htmlFor="expenseDescription" className="block text-sm font-medium text-gray-700">Opis</label>
<textarea
id="expenseDescription"
value={newExpense.description}
onChange={handleDescriptionChange}
className="mt-1 py-2 px-3 block w-full shadow-md sm:text-sm rounded-lg border-gray-300 focus:ring-indigo-500 focus:border-indigo-500"
rows="4"
/>
</div>
<button
onClick={handleAddExpense}
type="button"
className="bg-gradient-to-r from-blue-500 to-blue-700 text-white font-semibold py-3 px-6 rounded-lg shadow-md hover:from-blue-600 hover:to-blue-800 transition duration-300 ease-in-out w-full"
>
Dodaj
</button>
</div>
<div className="mt-5">
<div className="flex items-center justify-between py-6 px-8 bg-gradient-to-r from-blue-500 to-teal-500 rounded-xl shadow-md mb-6">
<div className="text-white text-4xl font-semibold">Wydatki</div>
<button
onClick={() => setShowModal(true)}
className="bg-gradient-to-r from-green-500 to-green-700 text-white py-2 px-4 rounded-lg hover:from-green-600 hover:to-green-800 transition"
>
<span>Dodaj wydatek</span>
</button>
</div>
{error && (
<div className="mt-4 text-red-500 text-center font-semibold">
{error}
</div>
)}
</div>
<div className="mt-5">
<table className="w-full rounded-lg shadow-lg">
<thead className="bg-gray-100 text-gray-700">
<tr>
@ -172,25 +107,24 @@ const Wydatki = () => {
<th className="p-3 text-left">Data</th>
<th className="p-3 text-left">Wartość</th>
<th className="p-3 text-left">Opis</th>
<th className="p-3 text-center">Usuń</th>
<th className="p-3 text-center"></th>
</tr>
</thead>
<tbody className="text-gray-600">
{expenses.map(expense => (
<tr key={expense.id} className="group hover:bg-gray-100 transition-colors">
<tr key={expense.id} className="hover:bg-gray-50 transition-colors">
<td className="p-3">{expense.id}</td>
<td className="p-3">{formatDate(expense.date)}</td>
<td className="p-3">{expense.value} </td>
<td className="p-3">{expense.description}</td>
<td className="p-3 flex justify-center space-x-2">
<div className="flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<button
onClick={() => openDeleteConfirmation(expense.id)}
className="text-red-500 hover:bg-red-200 active:bg-red-300 focus:outline-none p-2 rounded-full transition-colors"
>
<KoszIcon className="w-5 h-5" />
</button>
</div>
<td className="p-3 text-center">
<button
onClick={() => openDeleteConfirmation(expense.id)}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition"
>
<img src={koszIcon} alt="Usuń" className="inline w-5 mr-2" />
<span>Usuń</span>
</button>
</td>
</tr>
))}
@ -198,13 +132,81 @@ const Wydatki = () => {
</table>
</div>
{showModal && (
<div className="fixed z-10 inset-0 overflow-y-auto">
<div className="flex items-center justify-center min-h-screen">
<div className="fixed inset-0 bg-gray-500 opacity-50"></div>
<div className="bg-gradient-to-r from-blue-500 to-purple-600 p-1 rounded-lg shadow-xl transform transition-all sm:max-w-lg sm:w-full">
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
<div className="px-4 py-5 sm:px-6 bg-gradient-to-r from-indigo-500 to-indigo-700 text-white">
<h3 className="text-lg font-medium">Dodaj nowy wydatek</h3>
</div>
<div className="bg-white px-4 py-5 sm:p-6">
<div className="grid grid-cols-6 gap-6">
<div className="col-span-6 sm:col-span-3">
<label htmlFor="expenseDate" className="block text-sm font-medium text-gray-700">Data</label>
<input
type="datetime-local"
id="expenseDate"
value={newExpense.date}
onChange={(e) => setNewExpense({ ...newExpense, date: e.target.value })}
className="mt-1 py-2 px-3 block w-full shadow-md sm:text-sm rounded-lg border-gray-300 focus:ring-indigo-500 focus:border-indigo-500"
/>
</div>
<div className="col-span-6 sm:col-span-3">
<label htmlFor="expenseValue" className="block text-sm font-medium text-gray-700">Wartość</label>
<input
type="number"
id="expenseValue"
value={newExpense.value}
onChange={(e) => setNewExpense({ ...newExpense, value: e.target.value })}
className="mt-1 py-2 px-3 block w-full shadow-md sm:text-sm rounded-lg border-gray-300 focus:ring-indigo-500 focus:border-indigo-500"
/>
</div>
<div className="col-span-6">
<label htmlFor="expenseDescription" className="block text-sm font-medium text-gray-700">Opis</label>
<textarea
id="expenseDescription"
value={newExpense.description}
onChange={(e) => setNewExpense({ ...newExpense, description: e.target.value })}
className="mt-1 py-2 px-3 block w-full shadow-md sm:text-sm rounded-lg border-gray-300 focus:ring-indigo-500 focus:border-indigo-500"
rows="4"
/>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
onClick={handleAddExpense}
type="button"
className="w-full inline-flex justify-center rounded-md shadow-lg px-4 py-2 bg-gradient-to-r from-green-400 to-green-600 text-base font-medium text-white hover:bg-green-500 sm:text-sm sm:leading-5"
>
Dodaj
</button>
<button
onClick={() => setShowModal(false)}
type="button"
className="mt-3 sm:mt-0 sm:ml-3 w-full inline-flex justify-center rounded-md shadow-md px-4 py-2 bg-white text-base font-medium text-gray-700 hover:text-gray-500 border-gray-300"
>
Anuluj
</button>
</div>
</div>
</div>
</div>
</div>
)}
{showDeleteModal && (
<div className="fixed inset-0 bg-gray-500 bg-opacity-50 flex justify-center items-center z-50">
<div className="bg-white p-6 rounded-md shadow-lg w-96">
<h2 className="text-lg font-bold mb-4">Czy na pewno chcesz usunąć ten wydatek?</h2>
<div className="flex justify-between">
<button
onClick={() => { handleDeleteExpense(); setShowDeleteModal(false); window.scrollTo({ top: 0 }) }}
onClick={() => { handleDeleteExpense(); setShowDeleteModal(false); }}
className="bg-red-500 text-white py-2 px-4 rounded"
>
Tak
@ -219,6 +221,21 @@ const Wydatki = () => {
</div>
</div>
)}
{error && (
<div className="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
<div className="bg-white p-8 rounded-lg">
<h2 className="text-2xl font-bold mb-4">Błąd</h2>
<p>{error}</p>
<button
onClick={() => setError(null)}
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg"
>
Zamknij
</button>
</div>
</div>
)}
</div>
);
};

View File

@ -21,4 +21,4 @@ const ZarzadzanieProduktami = () => {
);
};
export default ZarzadzanieProduktami;
export default ZarzadzanieProduktami;

View File

@ -9,4 +9,8 @@ root.render(
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

10339
firm/yarn.lock Normal file

File diff suppressed because it is too large Load Diff