Compare commits
16 Commits
fe-poprawk
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
5fd901ef38 | ||
|
8cf726a51f | ||
|
049e8d3195 | ||
|
9b225ad036 | ||
|
5be297af61 | ||
|
cc44e23621 | ||
|
f53526db82 | ||
3643fd1377 | |||
|
7296120871 | ||
fca08213ff | |||
|
f7b4919212 | ||
|
a25a3d4011 | ||
|
93b6ab331a | ||
|
d193cbe51e | ||
|
8e6726348d | ||
44fa0d8503 |
7382
firm/package-lock.json
generated
7382
firm/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -40,5 +40,8 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11"
|
||||
}
|
||||
}
|
||||
|
1
firm/public/_redirects
Normal file
1
firm/public/_redirects
Normal file
@ -0,0 +1 @@
|
||||
/* /index.html 200
|
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB |
@ -1,43 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<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 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>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
@ -1,25 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
@ -47,9 +47,9 @@ const App = () => {
|
||||
)}
|
||||
<div className="w-3/4">
|
||||
<Routes>
|
||||
<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="/*" 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="/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" />} />
|
||||
|
@ -1,8 +0,0 @@
|
||||
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();
|
||||
});
|
@ -8,21 +8,19 @@ const DatePicker = ({ value, onChange, name, className, minDate, maxDate }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(e);
|
||||
onChange({
|
||||
target: {
|
||||
name: name,
|
||||
value: newValue,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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 || getCurrentDate()}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
className={className}
|
||||
/>
|
||||
|
@ -61,7 +61,7 @@ const DodawanieProduktu = () => {
|
||||
},
|
||||
};
|
||||
|
||||
await axios.post('https://localhost:7039/api/Products', payload, config);
|
||||
await axios.post('https://firmtracker-server.onrender.com/api/Products', payload, config);
|
||||
|
||||
setNewProduct({
|
||||
name: '',
|
||||
@ -73,7 +73,6 @@ 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.');
|
||||
}
|
||||
};
|
||||
@ -84,22 +83,18 @@ 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">
|
||||
<input
|
||||
<div>
|
||||
<label className="block mb-2 text-gray-700 font-medium">Nazwa</label>
|
||||
<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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
/></div>
|
||||
<div>
|
||||
<label className="block mb-2 text-gray-700 font-medium">Cena</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
@ -109,38 +104,75 @@ 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"
|
||||
/>
|
||||
<select
|
||||
name="type"
|
||||
value={newProduct.type}
|
||||
</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}
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
<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
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
);
|
||||
|
@ -45,7 +45,9 @@ const DodawanieTransakcji = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const response = await axios.get('https://localhost:7039/api/Products', config);
|
||||
|
||||
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/Products', config);
|
||||
const productOptions = response.data.map(product => ({
|
||||
value: product.id,
|
||||
label: product.name,
|
||||
@ -64,7 +66,6 @@ const DodawanieTransakcji = () => {
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setNewTransaction({ ...newTransaction, [name]: value });
|
||||
console.log(`po: ${name}, ${value}`)
|
||||
};
|
||||
const handleCancel = () => {
|
||||
navigate('/transakcje');
|
||||
@ -112,8 +113,6 @@ const DodawanieTransakcji = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Nowa transakcja:', newTransaction);
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
setError('Brak tokena. Użytkownik musi być zalogowany.');
|
||||
@ -127,7 +126,7 @@ const DodawanieTransakcji = () => {
|
||||
},
|
||||
};
|
||||
|
||||
await axios.post('https://localhost:7039/api/Transaction', newTransaction, config);
|
||||
await axios.post('https://firmtracker-server.onrender.com/api/Transaction', newTransaction, config);
|
||||
|
||||
setNewTransaction({
|
||||
id: 0,
|
||||
@ -149,7 +148,6 @@ 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 {
|
||||
@ -190,14 +188,6 @@ 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 ? (
|
||||
@ -226,15 +216,13 @@ 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}
|
||||
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"
|
||||
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}
|
||||
>
|
||||
<MinusIcon className="w-5 h-5" />
|
||||
</button>
|
||||
@ -250,14 +238,6 @@ 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>
|
||||
@ -298,13 +278,15 @@ 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>
|
||||
|
||||
|
@ -23,7 +23,7 @@ const EdycjaProduktu = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await axios.get(`https://localhost:7039/api/Products/${id}`, {
|
||||
const response = await axios.get(`https://firmtracker-server.onrender.com/api/Products/${id}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@ -38,7 +38,6 @@ 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.' });
|
||||
}
|
||||
};
|
||||
@ -114,11 +113,10 @@ const EdycjaProduktu = () => {
|
||||
},
|
||||
};
|
||||
|
||||
await axios.put(`https://localhost:7039/api/Products/${id}`, payload, config);
|
||||
await axios.put(`https://firmtracker-server.onrender.com/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 });
|
||||
@ -133,9 +131,10 @@ 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"
|
||||
@ -146,20 +145,8 @@ 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"
|
||||
@ -171,50 +158,80 @@ const EdycjaProduktu = () => {
|
||||
/>
|
||||
{errors.price && <span className="absolute text-red-500 text-sm">{errors.price}</span>}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="relative">
|
||||
<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>
|
||||
<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>
|
||||
{errors.type && <span className="absolute text-red-500 text-sm">{errors.type}</span>}
|
||||
</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>
|
||||
<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>
|
||||
</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;
|
||||
|
@ -1,7 +1,9 @@
|
||||
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();
|
||||
@ -16,6 +18,7 @@ const EdycjaTransakcji = () => {
|
||||
totalPrice: 0,
|
||||
});
|
||||
const [products, setProducts] = useState([]);
|
||||
const [pendingRemovals, setPendingRemovals] = useState([]);
|
||||
const [error, setError] = useState(null);
|
||||
const [errors, setErrors] = useState({});
|
||||
const navigate = useNavigate();
|
||||
@ -36,10 +39,10 @@ const EdycjaTransakcji = () => {
|
||||
|
||||
try {
|
||||
const [transactionRes, productsRes] = await Promise.all([
|
||||
axios.get(`https://localhost:7039/api/transaction/${id}`, {
|
||||
axios.get(`https://firmtracker-server.onrender.com/api/transaction/${id}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}),
|
||||
axios.get("https://localhost:7039/api/Products", {
|
||||
axios.get("https://firmtracker-server.onrender.com/api/Products", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}),
|
||||
]);
|
||||
@ -62,7 +65,6 @@ const EdycjaTransakcji = () => {
|
||||
}));
|
||||
setProducts(productOptions);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError("Wystąpił błąd podczas ładowania danych.");
|
||||
}
|
||||
};
|
||||
@ -91,8 +93,8 @@ const EdycjaTransakcji = () => {
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
navigate('/transakcje');
|
||||
}
|
||||
navigate("/transakcje");
|
||||
};
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
@ -132,45 +134,18 @@ const EdycjaTransakcji = () => {
|
||||
setErrors((prevErrors) => ({ ...prevErrors, [`quantity_${index}`]: null }));
|
||||
};
|
||||
|
||||
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 handleRemoveProduct = (index) => {
|
||||
const productToRemove = transaction.transactionProducts[index];
|
||||
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.");
|
||||
|
||||
setTransaction((prev) => ({
|
||||
...prev,
|
||||
transactionProducts: prev.transactionProducts.filter((_, i) => i !== index),
|
||||
}));
|
||||
|
||||
if (productToRemove.id) {
|
||||
setPendingRemovals((prev) => [...prev, productToRemove]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const handleSaveChanges = async () => {
|
||||
if (!validateForm()) return;
|
||||
@ -191,124 +166,182 @@ const EdycjaTransakcji = () => {
|
||||
|
||||
try {
|
||||
await axios.put(
|
||||
`https://localhost:7039/api/transaction/${transaction.id}`,
|
||||
`https://firmtracker-server.onrender.com/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);
|
||||
setError(err.response?.data || "Wystąpił błąd podczas zapisywania zmian.");
|
||||
}
|
||||
};
|
||||
|
||||
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">Edycja Transakcji</h2>
|
||||
<h2 className="text-2xl font-bold mb-6 text-gray-800">Edytuj transakcję</h2>
|
||||
|
||||
{error && <p className="text-red-500 mb-4">{error}</p>}
|
||||
|
||||
<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="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"
|
||||
<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[`productID_${index}`] && (
|
||||
<span className="text-red-500 text-sm">{errors[`productID_${index}`]}</span>
|
||||
)}
|
||||
{errors.date && <span className="text-red-500 text-sm">{errors.date}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-4 items-center">
|
||||
<label className="block mb-2 text-gray-700 font-medium">Nr pracownika</label>
|
||||
<input
|
||||
type="number"
|
||||
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"
|
||||
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"
|
||||
/>
|
||||
{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>
|
||||
{errors.employeeId && <span className="text-red-500 text-sm">{errors.employeeId}</span>}
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<div className="mb-4">
|
||||
<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>
|
||||
<input
|
||||
type="text"
|
||||
name="paymentType"
|
||||
value={transaction.paymentType}
|
||||
name="description"
|
||||
value={transaction.description}
|
||||
onChange={handleInputChange}
|
||||
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"
|
||||
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"
|
||||
/>
|
||||
{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-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"
|
||||
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"
|
||||
>
|
||||
Zapisz zmiany
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
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"
|
||||
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>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default EdycjaTransakcji;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect , useCallback } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
const Harmonogram = () => {
|
||||
@ -28,12 +28,11 @@ const Harmonogram = () => {
|
||||
|
||||
const fetchWorkdays = async () => {
|
||||
try {
|
||||
const response = await axios.get('https://localhost:7039/api/Workday/user/workdays', {
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/Workday/user/workdays', {
|
||||
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
|
||||
});
|
||||
setWorkdays(response.data);
|
||||
} catch (error) {
|
||||
console.error('Błąd podczas pobierania dni roboczych:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -46,13 +45,12 @@ const Harmonogram = () => {
|
||||
|
||||
try {
|
||||
await axios.post(
|
||||
'https://localhost:7039/api/workday/start',
|
||||
'https://firmtracker-server.onrender.com/api/workday/start',
|
||||
{},
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
setIsWorking(true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -65,7 +63,7 @@ const Harmonogram = () => {
|
||||
|
||||
try {
|
||||
await axios.post(
|
||||
'https://localhost:7039/api/workday/stop',
|
||||
'https://firmtracker-server.onrender.com/api/workday/stop',
|
||||
{},
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
@ -93,7 +91,7 @@ const Harmonogram = () => {
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
const generateDaysInMonth = () => {
|
||||
const generateDaysInMonth = useCallback(() => {
|
||||
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;
|
||||
@ -101,34 +99,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();
|
||||
}, [displayDate, workdays]);
|
||||
}, [generateDaysInMonth]);
|
||||
|
||||
const changeMonth = (direction) => {
|
||||
setManualDateChange(true);
|
||||
@ -166,8 +164,6 @@ const Harmonogram = () => {
|
||||
);
|
||||
});
|
||||
|
||||
console.log("Selected Date:", formattedDate);
|
||||
console.log("Day Status:", dayStatus);
|
||||
|
||||
if (dayStatus) {
|
||||
if (dayStatus.absence && dayStatus.absence.trim() !== "") {
|
||||
@ -180,7 +176,7 @@ const Harmonogram = () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(
|
||||
`https://localhost:7039/api/Workday/user/day/info/${formattedDate}`,
|
||||
`https://firmtracker-server.onrender.com/api/Workday/user/day/info/${formattedDate}`,
|
||||
{ headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } }
|
||||
);
|
||||
const workdayDetails = response.data;
|
||||
@ -193,7 +189,6 @@ const Harmonogram = () => {
|
||||
absence: null,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Błąd podczas pobierania danych dnia roboczego:", error);
|
||||
setSelectedDay({
|
||||
date: formattedDate,
|
||||
dayOfWeek: formatDayOfWeek(selectedDate),
|
||||
@ -214,6 +209,11 @@ 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,7 +227,6 @@ 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">
|
||||
@ -259,7 +258,7 @@ const Harmonogram = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-1/3 bg-gray-100 p-4 rounded-lg">
|
||||
<div className="w-1/3 bg-gray-100 p-4 rounded-lg h-[350px]">
|
||||
{loading ? (
|
||||
<p className="text-center text-blue-500">Ładowanie danych...</p>
|
||||
) : selectedDay ? (
|
||||
@ -299,7 +298,7 @@ const Harmonogram = () => {
|
||||
<p className="text-center text-gray-500">Wybierz dzień, aby zobaczyć szczegóły.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,19 +1,20 @@
|
||||
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';
|
||||
|
||||
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://localhost:7039/api/products', {
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/products', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
setProducts(response.data);
|
||||
@ -26,6 +27,11 @@ const ListaProduktow = ({ onAdd }) => {
|
||||
fetchProducts();
|
||||
}, []);
|
||||
|
||||
const openDeleteConfirmation = (productId) => {
|
||||
setDeleteProductId(productId);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteProduct = async () => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
@ -39,13 +45,15 @@ const ListaProduktow = ({ onAdd }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.delete(`https://localhost:7039/api/Products/${deleteProductId}`, {
|
||||
await axios.delete(`https://firmtracker-server.onrender.com/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);
|
||||
}
|
||||
};
|
||||
@ -57,9 +65,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">
|
||||
@ -76,7 +84,7 @@ const ListaProduktow = ({ onAdd }) => {
|
||||
</thead>
|
||||
<tbody className="text-gray-600">
|
||||
{products.map((product) => (
|
||||
<tr key={product.id} className="hover:bg-gray-50 transition-colors">
|
||||
<tr key={product.id} className="group hover:bg-gray-100 transition-colors">
|
||||
<td className="p-3">{product.id}</td>
|
||||
<td className="p-3">{product.name}</td>
|
||||
<td className="p-3">{product.description}</td>
|
||||
@ -84,31 +92,27 @@ const ListaProduktow = ({ onAdd }) => {
|
||||
<td className="p-3 text-center">
|
||||
{product.type === 0 ? "" : product.availability}
|
||||
</td>
|
||||
<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 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>
|
||||
</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">
|
||||
@ -131,6 +135,20 @@ 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>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,5 @@
|
||||
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';
|
||||
@ -11,6 +9,7 @@ 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 () => {
|
||||
@ -20,7 +19,7 @@ const ListaTransakcji = ({ onAdd}) => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await axios.get('https://localhost:7039/api/Transaction', {
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/Transaction', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@ -38,7 +37,7 @@ const ListaTransakcji = ({ onAdd}) => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await axios.get('https://localhost:7039/api/Products', {
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/Products', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@ -65,7 +64,7 @@ const ListaTransakcji = ({ onAdd}) => {
|
||||
|
||||
const handleDeleteTransaction = async () => {
|
||||
try {
|
||||
await axios.delete(`https://localhost:7039/api/transaction/${deleteTransactionId}`, {
|
||||
await axios.delete(`https://firmtracker-server.onrender.com/api/transaction/${deleteTransactionId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
@ -74,8 +73,9 @@ const ListaTransakcji = ({ onAdd}) => {
|
||||
setShowModal(false);
|
||||
setDeleteTransactionId(null);
|
||||
} catch (error) {
|
||||
console.error('Błąd podczas usuwania transakcji:', error);
|
||||
}
|
||||
setShowModal(false);
|
||||
setDeleteError(error.response?.data || 'Nieznany błąd');
|
||||
console.error('Błąd podczas usuwania produktu:', 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,8 +103,7 @@ 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">Produkt</th>
|
||||
<th className="p-3 text-left">Ilość</th>
|
||||
<th className="p-3 text-left">Produkty</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>
|
||||
@ -116,16 +115,9 @@ 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">
|
||||
{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 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">{formatPrice(transaction.totalPrice)}</td>
|
||||
<td className="p-3">{transaction.paymentType}</td>
|
||||
<td className="p-3 text-center">{transaction.employeeId}</td>
|
||||
@ -133,14 +125,12 @@ 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"/>
|
||||
@ -162,7 +152,10 @@ const ListaTransakcji = ({ onAdd}) => {
|
||||
<h2 className="text-lg font-bold mb-4">Czy na pewno chcesz usunąć tę transakcję?</h2>
|
||||
<div className="flex justify-between">
|
||||
<button
|
||||
onClick={handleDeleteTransaction}
|
||||
onClick={() => {
|
||||
handleDeleteTransaction();
|
||||
setShowModal(false);
|
||||
}}
|
||||
className="bg-red-500 text-white py-2 px-4 rounded"
|
||||
>
|
||||
Tak
|
||||
@ -177,6 +170,20 @@ 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>
|
||||
);
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ const Login = ({ setToken }) => {
|
||||
const handleLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const response = await axios.post('https://localhost:7039/api/user/login', { email, password });
|
||||
const response = await axios.post('https://firmtracker-server.onrender.com/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">
|
||||
<div className="flex justify-center items-center h-screen w-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>}
|
||||
|
@ -7,7 +7,7 @@ const Navbar = ({ setToken }) => {
|
||||
const navigate = useNavigate();
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const [userRole, setUserRole] = useState('');
|
||||
const dropdownRef = useRef(null); // Ref do obsługi kliknięć poza dropdownem
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserRole = async () => {
|
||||
@ -15,7 +15,7 @@ const Navbar = ({ setToken }) => {
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await axios.get('https://localhost:7039/api/user/role', {
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/user/role', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
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');
|
||||
@ -11,11 +12,55 @@ 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://localhost:7039/api/user/emails', {
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/user/emails', {
|
||||
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
|
||||
});
|
||||
setEmails(response.data);
|
||||
@ -24,7 +69,32 @@ const PanelAdministratora = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Funkcja dodawania absencji
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const addAbsence = async () => {
|
||||
if (!selectedEmail || !absenceType || !startDate || !endDate) {
|
||||
alert("Wszystkie pola muszą być wypełnione!");
|
||||
@ -32,7 +102,7 @@ const PanelAdministratora = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post('https://localhost:7039/api/Workday/user/absence/add', {
|
||||
await axios.post('https://firmtracker-server.onrender.com/api/Workday/absence/add', {
|
||||
userEmail: selectedEmail,
|
||||
absenceType: absenceType,
|
||||
startTime: startDate,
|
||||
@ -47,7 +117,6 @@ const PanelAdministratora = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Funkcja pobierania raportu
|
||||
const downloadReport = async () => {
|
||||
if (!reportType || !startDate || !endDate) {
|
||||
alert("Wszystkie pola muszą być wypełnione!");
|
||||
@ -55,7 +124,7 @@ const PanelAdministratora = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('https://localhost:7039/api/Pdf/download', {
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/Pdf/download', {
|
||||
params: {
|
||||
reportType: reportType,
|
||||
startDate: startDate,
|
||||
@ -76,7 +145,6 @@ const PanelAdministratora = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Funkcja pobierania harmonogramów
|
||||
const fetchWorkdays = async (userEmail) => {
|
||||
if (!userEmail) {
|
||||
setWorkdays([]);
|
||||
@ -84,7 +152,7 @@ const PanelAdministratora = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(`https://localhost:7039/api/Workday/user/${userEmail}/workdays`, {
|
||||
const response = await axios.get(`https://firmtracker-server.onrender.com/api/Workday/user/${userEmail}/workdays`, {
|
||||
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
|
||||
});
|
||||
setWorkdays(response.data);
|
||||
@ -93,7 +161,6 @@ const PanelAdministratora = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// UseEffect do pobierania danych emaili przy pierwszym renderowaniu
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
@ -101,7 +168,6 @@ const PanelAdministratora = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// UseEffect do pobierania harmonogramu przy zmianie emaila
|
||||
useEffect(() => {
|
||||
if (selectedEmail) {
|
||||
fetchWorkdays(selectedEmail);
|
||||
@ -111,9 +177,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-10'>
|
||||
<div className='px-5'>
|
||||
<button
|
||||
onClick={() => setSelectedOption('harmonogramy')}
|
||||
className={`
|
||||
@ -123,7 +189,7 @@ const PanelAdministratora = () => {
|
||||
Harmonogramy
|
||||
</button>
|
||||
</div>
|
||||
<div className='px-10'>
|
||||
<div className='px-5'>
|
||||
<button
|
||||
onClick={() => setSelectedOption('absencje')}
|
||||
className={`
|
||||
@ -133,7 +199,7 @@ const PanelAdministratora = () => {
|
||||
Absencje
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div className='px-5'>
|
||||
<button
|
||||
onClick={() => setSelectedOption('raporty')}
|
||||
className={`
|
||||
@ -143,6 +209,26 @@ 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>
|
||||
|
||||
@ -152,7 +238,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>
|
||||
@ -170,23 +256,25 @@ 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>
|
||||
<input
|
||||
<DatePicker
|
||||
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>
|
||||
<input
|
||||
<DatePicker
|
||||
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>
|
||||
|
||||
@ -194,7 +282,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>
|
||||
@ -234,7 +322,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>
|
||||
@ -267,23 +355,25 @@ 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>
|
||||
<input
|
||||
<DatePicker
|
||||
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>
|
||||
<input
|
||||
<DatePicker
|
||||
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>
|
||||
|
||||
@ -291,13 +381,138 @@ 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>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import koszIcon from "../icons/kosz.png";
|
||||
import {ReactComponent as KoszIcon} from '../icons/delete.svg';
|
||||
import DatePicker from './DatePicker';
|
||||
|
||||
const Raporty = () => {
|
||||
const [fromDate, setFromDate] = useState('');
|
||||
@ -17,7 +18,7 @@ const Raporty = () => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await axios.get('https://localhost:7039/api/Report', {
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/Report', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
@ -28,6 +29,31 @@ 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);
|
||||
@ -50,7 +76,7 @@ const Raporty = () => {
|
||||
const token = localStorage.getItem('token');
|
||||
try {
|
||||
console.log('Wysyłane dane:', fromDate, toDate);
|
||||
const response = await axios.post('https://localhost:7039/api/Report', {
|
||||
const response = await axios.post('https://firmtracker-server.onrender.com/api/Report', {
|
||||
fromDate,
|
||||
toDate
|
||||
}, {
|
||||
@ -73,7 +99,7 @@ const Raporty = () => {
|
||||
const handleDeleteReport = async (reportId) => {
|
||||
const token = localStorage.getItem('token');
|
||||
try {
|
||||
await axios.delete(`https://localhost:7039/api/Report/${reportId}`, {
|
||||
await axios.delete(`https://firmtracker-server.onrender.com/api/Report/${reportId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
@ -106,23 +132,25 @@ 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>
|
||||
<input
|
||||
<DatePicker
|
||||
type="datetime-local"
|
||||
id="fromDate"
|
||||
value={fromDate}
|
||||
onChange={(e) => setFromDate(e.target.value)}
|
||||
onChange={handleFromDateChange}
|
||||
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>
|
||||
<input
|
||||
<DatePicker
|
||||
type="datetime-local"
|
||||
id="toDate"
|
||||
value={toDate}
|
||||
onChange={(e) => setToDate(e.target.value)}
|
||||
onChange={handleToDateChange}
|
||||
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>
|
||||
@ -133,10 +161,17 @@ 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">
|
||||
@ -147,47 +182,34 @@ 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"></th>
|
||||
<th className="p-2 text-center">Usuń</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{reports.map((report) => (
|
||||
<tr key={report.id} className="hover:bg-gray-50">
|
||||
<tr key={report.id} className="group hover:bg-gray-100 transition-colors">
|
||||
<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-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>
|
||||
<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>
|
||||
</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">
|
||||
|
@ -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-max">
|
||||
<Link to="/panel" className="text-black px-10 py-2 block font-customFont text-center w-full hover:bg-gray-300 transition duration-100">
|
||||
<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-max">
|
||||
<Link to="/produkty" className="text-black px-10 py-2 block font-customFont text-center w-full hover:bg-gray-300 transition duration-100 ">
|
||||
<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-max">
|
||||
<Link to="/transakcje" className="text-black px-10 py-2 block font-customFont text-center w-full hover:bg-gray-300 transition duration-100 ">
|
||||
<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-max flex-item-center">
|
||||
<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 ">
|
||||
<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-max flex-item-center">
|
||||
<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">
|
||||
<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-max flex-item-center">
|
||||
<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">
|
||||
<li className='flex items-center'>
|
||||
<img src={raportyIcon} alt="Obrazek 1" className="w-7 h-7 mr-2" />
|
||||
Raporty
|
||||
|
@ -1,20 +1,15 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } 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 = () => {
|
||||
const generateDaysInMonth = useCallback(() => {
|
||||
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;
|
||||
@ -22,30 +17,35 @@ 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,7 +69,8 @@ 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);
|
||||
|
||||
@ -96,7 +97,7 @@ const WidokHarmonogramu = ({ workdays }) => {
|
||||
} else {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`https://localhost:7039/api/Workday/user/day/info/${formattedDate}`,
|
||||
`https://firmtracker-server.onrender.com/api/Workday/user/day/info/${formattedDate}`,
|
||||
{ headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } }
|
||||
);
|
||||
const workdayDetails = response.data;
|
||||
@ -124,6 +125,7 @@ const WidokHarmonogramu = ({ workdays }) => {
|
||||
absence: "Brak danych o tym dniu",
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
@ -172,7 +174,7 @@ const WidokHarmonogramu = ({ workdays }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-1/3 bg-gray-100 p-4 rounded-lg">
|
||||
<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">
|
||||
{loading ? (
|
||||
<p className="text-center text-blue-500">Ładowanie danych...</p>
|
||||
) : selectedDay ? (
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import koszIcon from "../icons/kosz.png";
|
||||
import { ReactComponent as KoszIcon } from '../icons/delete.svg';
|
||||
import DatePicker from './DatePicker';
|
||||
|
||||
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://localhost:7039/api/Expenses', {
|
||||
const response = await axios.get('https://firmtracker-server.onrender.com/api/Expenses', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
setExpenses(response.data);
|
||||
@ -40,15 +40,19 @@ 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://localhost:7039/api/Expenses', newExpense, {
|
||||
const response = await axios.post('https://firmtracker-server.onrender.com/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.');
|
||||
@ -58,11 +62,10 @@ const Wydatki = () => {
|
||||
const handleDeleteExpense = async () => {
|
||||
const token = localStorage.getItem('token');
|
||||
try {
|
||||
await axios.delete(`https://localhost:7039/api/Expenses/${deleteExpenseId}`, {
|
||||
await axios.delete(`https://firmtracker-server.onrender.com/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);
|
||||
@ -87,19 +90,81 @@ 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="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 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>
|
||||
|
||||
{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>
|
||||
@ -107,24 +172,25 @@ 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"></th>
|
||||
<th className="p-3 text-center">Usuń</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-gray-600">
|
||||
{expenses.map(expense => (
|
||||
<tr key={expense.id} className="hover:bg-gray-50 transition-colors">
|
||||
<tr key={expense.id} className="group hover:bg-gray-100 transition-colors">
|
||||
<td className="p-3">{expense.id}</td>
|
||||
<td className="p-3">{formatDate(expense.date)}</td>
|
||||
<td className="p-3">{expense.value} zł</td>
|
||||
<td className="p-3">{expense.description}</td>
|
||||
<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 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>
|
||||
</tr>
|
||||
))}
|
||||
@ -132,81 +198,13 @@ 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); }}
|
||||
onClick={() => { handleDeleteExpense(); setShowDeleteModal(false); window.scrollTo({ top: 0 }) }}
|
||||
className="bg-red-500 text-white py-2 px-4 rounded"
|
||||
>
|
||||
Tak
|
||||
@ -221,21 +219,6 @@ 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>
|
||||
);
|
||||
};
|
||||
|
@ -21,4 +21,4 @@ const ZarzadzanieProduktami = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ZarzadzanieProduktami;
|
||||
export default ZarzadzanieProduktami;
|
@ -9,8 +9,4 @@ 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
10339
firm/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user