poprawy wizualne

This commit is contained in:
Wiktor Szynaka 2025-01-01 22:23:08 +01:00
parent 44fa0d8503
commit 8e6726348d
7 changed files with 281 additions and 254 deletions

View File

@ -190,14 +190,6 @@ const DodawanieTransakcji = () => {
</div> </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> <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"> <div className="border border-gray-300 rounded-lg shadow-sm p-4 h-80 overflow-y-scroll">
{isLoading ? ( {isLoading ? (

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import axios from "axios"; import axios from "axios";
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate } from "react-router-dom";
import {ReactComponent as MinusIcon} from "../icons/minus-icon.svg"
import Select from "react-select"; import Select from "react-select";
const EdycjaTransakcji = () => { const EdycjaTransakcji = () => {
@ -204,111 +205,154 @@ const EdycjaTransakcji = () => {
return ( return (
<div className="bg-white p-8 rounded-lg shadow-lg max-w-4xl mx-auto mt-6"> <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>} {error && <p className="text-red-500 mb-4">{error}</p>}
<div className="mb-4"> <div className="mb-4 flex items-center space-x-4">
<input <div>
type="datetime-local" <label className="block mb-2 text-gray-700 font-medium">Data transakcji</label>
name="date" <input
value={transaction.date} type="datetime-local"
onChange={handleInputChange} name="date"
placeholder="Data" value={transaction.date}
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" 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"
{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"
/> />
{errors[`productID_${index}`] && ( {errors.date && <span className="text-red-500 text-sm">{errors.date}</span>}
<span className="text-red-500 text-sm">{errors[`productID_${index}`]}</span> </div>
)} </div>
<div className="pb-4 items-center">
<label className="block mb-2 text-gray-700 font-medium">Nr pracownika</label>
<input <input
type="number" type="number"
value={product.quantity} name="employeeId"
onChange={(e) => handleQuantityChange(index, e.target.value)} value={transaction.employeeId}
placeholder="Ilość" onChange={handleInputChange}
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="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}`] && ( {errors.employeeId && <span className="text-red-500 text-sm">{errors.employeeId}</span>}
<span className="text-red-500 text-sm">{errors[`quantity_${index}`]}</span>
)}
<button
onClick={() => handleRemoveProduct(index)}
className="bg-gradient-to-r from-red-500 to-red-700 text-white py-2 px-4 rounded-lg hover:from-red-600 hover:to-red-800 transition mt-3"
>
Usuń
</button>
</div> </div>
))}
<button <label className="block mb-2 text-gray-700 font-medium">Produkty transkacji</label>
onClick={handleAddProduct} <div className="border border-gray-300 rounded-lg shadow-sm p-4 h-80 overflow-y-scroll">
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" {transaction.transactionProducts.map((product, index) => (
> <div key={index} className="mb-4 flex items-center space-x-4">
Dodaj produkt <Select
</button> 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 text-gray-500 hover:text-red-600 hover:bg-red-100 active:bg-red-200 transition focus:outline-none"
>
<MinusIcon className="w-5 h-5" />
</button>
</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"
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 <input
type="text" type="text"
name="paymentType" name="description"
value={transaction.paymentType} value={transaction.description}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="Sposób płatności" placeholder="Opis"
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" 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>
<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"> <div className="mt-6 flex justify-between">
<button <button
onClick={handleSaveChanges} 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 Zapisz zmiany
</button> </button>
<button <button
onClick={handleCancel} 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 Anuluj
</button> </button>
</div> </div>
</div> </div>
); );
}; };
export default EdycjaTransakcji; export default EdycjaTransakcji;

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import axios from 'axios'; import axios from 'axios';
import editIcon from '../icons/edit.png'; import {ReactComponent as EditIcon} from '../icons/edit.svg';
import koszIcon from '../icons/kosz.png'; import {ReactComponent as KoszIcon} from '../icons/delete.svg';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
const ListaProduktow = ({ onAdd }) => { const ListaProduktow = ({ onAdd }) => {
@ -26,6 +26,11 @@ const ListaProduktow = ({ onAdd }) => {
fetchProducts(); fetchProducts();
}, []); }, []);
const openDeleteConfirmation = (productId) => {
setDeleteProductId(productId);
setShowModal(true);
};
const handleDeleteProduct = async () => { const handleDeleteProduct = async () => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
if (!token) { if (!token) {
@ -76,7 +81,7 @@ const ListaProduktow = ({ onAdd }) => {
</thead> </thead>
<tbody className="text-gray-600"> <tbody className="text-gray-600">
{products.map((product) => ( {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.id}</td>
<td className="p-3">{product.name}</td> <td className="p-3">{product.name}</td>
<td className="p-3">{product.description}</td> <td className="p-3">{product.description}</td>
@ -84,25 +89,20 @@ const ListaProduktow = ({ onAdd }) => {
<td className="p-3 text-center"> <td className="p-3 text-center">
{product.type === 0 ? "" : product.availability} {product.type === 0 ? "" : product.availability}
</td> </td>
<td className="p-3 flex justify-center items-center space-x-2"> <div className="flex justify-center items-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<button <button
onClick={() => handleEditProduct(product.id)} 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" className="text-blue-500 hover:bg-blue-200 active:bg-blue-300 focus:outline-none p-2 rounded-full transition-colors"
> >
<img src={editIcon} alt="Edytuj" className="inline w-5 mr-2" /> <EditIcon className = "w-5 h-5"/>
Edytuj </button>
</button> <button
<button onClick={() => openDeleteConfirmation(product.id)}
onClick={() => { className="text-red-500 hover:bg-red-200 active:bg-red-300 focus:outline-none p-2 rounded-full transition-colors"
setDeleteProductId(product.id); >
setShowModal(true); <KoszIcon className = "w-5 h-5"/>
}} </button>
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" </div>
>
<img src={koszIcon} alt="Usuń" className="inline w-5 mr-2" />
Usuń
</button>
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@ -1,7 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import axios from 'axios'; 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 EditIcon} from '../icons/edit.svg';
import {ReactComponent as KoszIcon} from '../icons/delete.svg'; import {ReactComponent as KoszIcon} from '../icons/delete.svg';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -133,14 +131,12 @@ const ListaTransakcji = ({ onAdd}) => {
<div className="flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"> <div className="flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<button <button
onClick={() => handleEditTransaction(transaction.id)} 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" 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"/> <EditIcon className = "w-5 h-5"/>
</button> </button>
<button <button
onClick={() => openDeleteConfirmation(transaction.id)} 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" 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"/> <KoszIcon className = "w-5 h-5"/>

View File

@ -12,7 +12,6 @@ const PanelAdministratora = () => {
const [workdays, setWorkdays] = useState([]); const [workdays, setWorkdays] = useState([]);
const [absenceType, setAbsenceType] = useState(''); const [absenceType, setAbsenceType] = useState('');
// Funkcja pobierania emaili
const fetchEmails = async () => { const fetchEmails = async () => {
try { try {
const response = await axios.get('https://localhost:7039/api/user/emails', { const response = await axios.get('https://localhost:7039/api/user/emails', {
@ -24,7 +23,6 @@ const PanelAdministratora = () => {
} }
}; };
// Funkcja dodawania absencji
const addAbsence = async () => { const addAbsence = async () => {
if (!selectedEmail || !absenceType || !startDate || !endDate) { if (!selectedEmail || !absenceType || !startDate || !endDate) {
alert("Wszystkie pola muszą być wypełnione!"); alert("Wszystkie pola muszą być wypełnione!");
@ -47,7 +45,6 @@ const PanelAdministratora = () => {
} }
}; };
// Funkcja pobierania raportu
const downloadReport = async () => { const downloadReport = async () => {
if (!reportType || !startDate || !endDate) { if (!reportType || !startDate || !endDate) {
alert("Wszystkie pola muszą być wypełnione!"); alert("Wszystkie pola muszą być wypełnione!");
@ -76,7 +73,6 @@ const PanelAdministratora = () => {
} }
}; };
// Funkcja pobierania harmonogramów
const fetchWorkdays = async (userEmail) => { const fetchWorkdays = async (userEmail) => {
if (!userEmail) { if (!userEmail) {
setWorkdays([]); setWorkdays([]);
@ -93,7 +89,6 @@ const PanelAdministratora = () => {
} }
}; };
// UseEffect do pobierania danych emaili przy pierwszym renderowaniu
useEffect(() => { useEffect(() => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
if (token) { if (token) {
@ -101,7 +96,6 @@ const PanelAdministratora = () => {
} }
}, []); }, []);
// UseEffect do pobierania harmonogramu przy zmianie emaila
useEffect(() => { useEffect(() => {
if (selectedEmail) { if (selectedEmail) {
fetchWorkdays(selectedEmail); fetchWorkdays(selectedEmail);
@ -113,7 +107,7 @@ const PanelAdministratora = () => {
<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='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="mr-10 text-lg flex">
<div className='px-10'> <div className='px-5'>
<button <button
onClick={() => setSelectedOption('harmonogramy')} onClick={() => setSelectedOption('harmonogramy')}
className={` className={`
@ -123,7 +117,7 @@ const PanelAdministratora = () => {
Harmonogramy Harmonogramy
</button> </button>
</div> </div>
<div className='px-10'> <div className='px-5'>
<button <button
onClick={() => setSelectedOption('absencje')} onClick={() => setSelectedOption('absencje')}
className={` className={`
@ -133,7 +127,7 @@ const PanelAdministratora = () => {
Absencje Absencje
</button> </button>
</div> </div>
<div> <div className='px-5'>
<button <button
onClick={() => setSelectedOption('raporty')} onClick={() => setSelectedOption('raporty')}
className={` className={`

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import axios from 'axios'; import axios from 'axios';
import koszIcon from "../icons/kosz.png"; import {ReactComponent as KoszIcon} from '../icons/delete.svg';
const Raporty = () => { const Raporty = () => {
const [fromDate, setFromDate] = useState(''); const [fromDate, setFromDate] = useState('');
@ -28,6 +28,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) => { const openDeleteConfirmation = (reportId) => {
setDeleteReportId(reportId); setDeleteReportId(reportId);
setShowDeleteModal(true); setShowDeleteModal(true);
@ -110,18 +135,18 @@ const Raporty = () => {
type="datetime-local" type="datetime-local"
id="fromDate" id="fromDate"
value={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" 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"
/> />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<label htmlFor="toDate" className="block text-lg font-medium text-gray-700 mb-2">Do:</label> <label htmlFor="toDate" className="block text-lg font-medium text-gray-700 mb-2">Do:</label>
<input <input
type="datetime-local" type="datetime-local"
id="toDate" id="toDate"
value={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" 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"
/> />
</div> </div>
@ -133,10 +158,17 @@ const Raporty = () => {
> >
Generuj raport Generuj raport
</button> </button>
{error && (
<div className="mt-4 text-red-500 text-center font-semibold">
{error}
</div>
)}
</div> </div>
<div className="mt-5"> <div className="mt-5">
<table className="min-w-full border-collapse table-auto shadow-lg"> <table className="min-w-full border-collapse table-auto shadow-lg">
<thead className="bg-gray-200 text-gray-700"> <thead className="bg-gray-200 text-gray-700">
@ -147,47 +179,34 @@ const Raporty = () => {
<th className="p-2 text-left">Suma dochodów</th> <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">Suma wydatków</th>
<th className="p-2 text-left">Bilans</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> </tr>
</thead> </thead>
<tbody> <tbody>
{reports.map((report) => ( {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">{report.id}</td>
<td className="p-2">{formatDate(report.fromDate)}</td> <td className="p-2">{formatDate(report.fromDate)}</td>
<td className="p-2">{formatDate(report.toDate)}</td> <td className="p-2">{formatDate(report.toDate)}</td>
<td className="p-2">{report.totalIncome}</td> <td className="p-2">{report.totalIncome}</td>
<td className="p-2">{report.totalExpenses}</td> <td className="p-2">{report.totalExpenses}</td>
<td className="p-2">{report.totalBalance}</td> <td className="p-2">{report.totalBalance}</td>
<td className="p-2 text-center"> <td className="p-3 flex justify-center space-x-2">
<button <div className="flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
onClick={() => openDeleteConfirmation(report.id)} <button
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" 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"
<img src={koszIcon} alt="Usuń" className="inline w-5 mr-2" /> Usuń >
</button> <KoszIcon className = "w-5 h-5"/>
</td> </button>
</div>
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
</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={() => 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 && ( {showDeleteModal && (
<div className="fixed inset-0 bg-gray-500 bg-opacity-50 flex justify-center items-center z-50"> <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"> <div className="bg-white p-6 rounded-md shadow-lg w-96">

View File

@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import axios from 'axios'; import axios from 'axios';
import koszIcon from "../icons/kosz.png"; import { ReactComponent as KoszIcon } from '../icons/delete.svg';
const Wydatki = () => { const Wydatki = () => {
const [expenses, setExpenses] = useState([]); const [expenses, setExpenses] = useState([]);
const [showModal, setShowModal] = useState(false); const [fromDate, setFromDate] = useState('');
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [deleteExpenseId, setDeleteExpenseId] = useState(null); const [deleteExpenseId, setDeleteExpenseId] = useState(null);
@ -40,6 +40,11 @@ const Wydatki = () => {
return; return;
} }
if (newExpense.value <= 0) {
setError('Wartość wydatku musi być liczbą dodatnią.');
return;
}
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
try { try {
const response = await axios.post('https://localhost:7039/api/Expenses', newExpense, { const response = await axios.post('https://localhost:7039/api/Expenses', newExpense, {
@ -48,7 +53,6 @@ const Wydatki = () => {
const addedExpense = response.data; const addedExpense = response.data;
setExpenses([...expenses, addedExpense]); setExpenses([...expenses, addedExpense]);
setNewExpense({ date: '', value: '', description: '' }); setNewExpense({ date: '', value: '', description: '' });
setShowModal(false);
} catch (error) { } catch (error) {
console.error('Błąd podczas dodawania wydatku:', error); console.error('Błąd podczas dodawania wydatku:', error);
setError('Wystąpił błąd podczas dodawania wydatku.'); setError('Wystąpił błąd podczas dodawania wydatku.');
@ -62,7 +66,6 @@ const Wydatki = () => {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
// Optimistically update the local state by filtering out the deleted expense
setExpenses(expenses.filter(expense => expense.id !== deleteExpenseId)); setExpenses(expenses.filter(expense => expense.id !== deleteExpenseId));
setDeleteExpenseId(null); setDeleteExpenseId(null);
setShowDeleteModal(false); setShowDeleteModal(false);
@ -87,19 +90,80 @@ const Wydatki = () => {
return date.toLocaleDateString('pl-PL', options).replace(",", ""); 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 ( return (
<div className="p-10 ml-11"> <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="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 className="text-white text-4xl font-semibold">Wydatki</div> </div>
<button
onClick={() => setShowModal(true)} <div className="bg-white shadow-lg p-8 rounded-xl max-w-3xl mx-auto">
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" <div className="mb-6">
> <div className="flex flex-col space-y-6">
<span>Dodaj wydatek</span> <div className="flex space-x-6">
</button> <div className="flex-1">
<label htmlFor="expenseDate" className="block text-sm font-medium text-gray-700">Data</label>
<input
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"
/>
</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> </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"> <table className="w-full rounded-lg shadow-lg">
<thead className="bg-gray-100 text-gray-700"> <thead className="bg-gray-100 text-gray-700">
<tr> <tr>
@ -107,24 +171,25 @@ const Wydatki = () => {
<th className="p-3 text-left">Data</th> <th className="p-3 text-left">Data</th>
<th className="p-3 text-left">Wartość</th> <th className="p-3 text-left">Wartość</th>
<th className="p-3 text-left">Opis</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> </tr>
</thead> </thead>
<tbody className="text-gray-600"> <tbody className="text-gray-600">
{expenses.map(expense => ( {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">{expense.id}</td>
<td className="p-3">{formatDate(expense.date)}</td> <td className="p-3">{formatDate(expense.date)}</td>
<td className="p-3">{expense.value} </td> <td className="p-3">{expense.value} </td>
<td className="p-3">{expense.description}</td> <td className="p-3">{expense.description}</td>
<td className="p-3 text-center"> <td className="p-3 flex justify-center space-x-2">
<button <div className="flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
onClick={() => openDeleteConfirmation(expense.id)} <button
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" 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"
<img src={koszIcon} alt="Usuń" className="inline w-5 mr-2" /> >
<span>Usuń</span> <KoszIcon className="w-5 h-5" />
</button> </button>
</div>
</td> </td>
</tr> </tr>
))} ))}
@ -132,74 +197,6 @@ const Wydatki = () => {
</table> </table>
</div> </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 && ( {showDeleteModal && (
<div className="fixed inset-0 bg-gray-500 bg-opacity-50 flex justify-center items-center z-50"> <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"> <div className="bg-white p-6 rounded-md shadow-lg w-96">
@ -221,21 +218,6 @@ const Wydatki = () => {
</div> </div>
</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> </div>
); );
}; };