чюпеп

This commit is contained in:
2025-05-22 22:55:35 +04:00
parent e9ee33ffe3
commit a7caa70b07
10 changed files with 418 additions and 77 deletions

18
db.json Normal file
View File

@@ -0,0 +1,18 @@
{
"deliveries": [
{
"id": "578a",
"trackingNumber": "IVN012021",
"destination": "гоголя10",
"status": "Доставлено",
"customer": "Парт петрович wwww"
},
{
"id": "0e0f",
"trackingNumber": "IVN123456",
"destination": "Москва",
"status": "В пути",
"customer": "1Иванов Иван"
}
]
}

56
package-lock.json generated
View File

@@ -9,7 +9,8 @@
"version": "0.0.0",
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.0"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
@@ -3169,6 +3170,53 @@
"node": ">=0.10.0"
}
},
"node_modules/react-router": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
"integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz",
"integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
"license": "MIT",
"dependencies": {
"react-router": "7.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/react-router/node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -3319,6 +3367,12 @@
"node": ">= 18"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",

View File

@@ -7,11 +7,13 @@
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"server": "json-server --watch db.json --port 3001"
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.0"
},
"devDependencies": {
"@eslint/js": "^9.25.0",

View File

@@ -134,3 +134,141 @@
.form-actions button[type="button"]:hover {
background-color: #d32f2f;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.stats-link, .back-link {
padding: 8px 16px;
background: #4CAF50;
color: white;
text-decoration: none;
border-radius: 4px;
transition: background 0.3s;
}
.stats-link:hover, .back-link:hover {
background: #45a049;
}
.deliveries-container {
margin-top: 20px;
}
.no-deliveries {
text-align: center;
color: #666;
font-style: italic;
}
.loading, .error {
text-align: center;
padding: 20px;
font-size: 1.2em;
}
.error {
color: #f44336;
}
/* Стили для статистики */
.stats-container {
max-width: 800px;
margin: 0 auto;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 20px;
margin-top: 30px;
}
.stat-card {
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stat-card h3 {
margin-top: 0;
color: #333;
}
.stat-card p {
font-size: 24px;
font-weight: bold;
margin: 10px 0 0;
}
.delivery-item {
border: 1px solid #e0e0e0;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
background-color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.delivery-number {
color: #646cff;
margin-top: 0;
margin-bottom: 15px;
font-size: 1.3em;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.delivery-details {
margin-bottom: 15px;
}
.delivery-details h4 {
margin: 8px 0;
font-size: 1.1em;
color: #555;
}
.delivery-customer h4 {
margin: 8px 0;
font-size: 1.1em;
color: #555;
}
.status {
font-weight: normal;
margin: 8px 0;
}
.delivery-actions {
margin-top: 20px;
display: flex;
gap: 10px;
border-top: 1px solid #eee;
padding-top: 15px;
}
.delivery-actions button {
padding: 8px 15px;
cursor: pointer;
border-radius: 4px;
border: none;
background-color: #646cff;
color: white;
font-size: 0.9em;
transition: background-color 0.2s;
}
.delivery-actions button:last-child {
background-color: #f44336;
}
.delivery-actions button:hover {
opacity: 0.9;
}

View File

@@ -1,39 +1,22 @@
import { useState } from 'react'
import Header from './components/Header'
import DeliveryList from './components/DeliveryList'
import './App.css'
import { Routes, Route } from 'react-router-dom';
import Header from './components/Header';
import DeliveryList from './components/DeliveryList';
import DeliveryStats from './components/DeliveryStats';
import './App.css';
function App() {
const [deliveries, setDeliveries] = useState([
{ id: 1, trackingNumber: 'IVN123456', destination: 'Москва', status: 'В пути', customer: 'Иванов Иван' },
{ id: 2, trackingNumber: 'IVN654321', destination: 'Санкт-Петербург', status: 'Доставлено', customer: 'Петров Петр' }
])
const addDelivery = (newDelivery) => {
setDeliveries([...deliveries, { ...newDelivery, id: Date.now() }])
}
const updateDelivery = (updatedDelivery) => {
setDeliveries(deliveries.map(d => d.id === updatedDelivery.id ? updatedDelivery : d))
}
const deleteDelivery = (id) => {
setDeliveries(deliveries.filter(d => d.id !== id))
}
return (
<div className="app">
<Header />
<div className="container">
<DeliveryList
deliveries={deliveries}
onAdd={addDelivery}
onUpdate={updateDelivery}
onDelete={deleteDelivery}
/>
<Routes>
<Route path="/" element={<DeliveryList />} />
<Route path="/deliveries" element={<DeliveryList />} />
<Route path="/stats" element={<DeliveryStats />} />
</Routes>
</div>
</div>
)
);
}
export default App
export default App;

View File

@@ -1,20 +1,25 @@
const DeliveryItem = ({ delivery, onEdit, onDelete }) => {
return (
<div className="delivery-item">
<div className="delivery-info">
<h3>Трек-номер: {delivery.trackingNumber}</h3>
<p>Направление: {delivery.destination}</p>
<p>Статус: <span className={`status-${delivery.status.toLowerCase().replace(' ', '-')}`}>
{delivery.status}
</span></p>
<p>Клиент: {delivery.customer}</p>
<h3 className="delivery-number">Трек-номер: {delivery.trackingNumber}</h3>
<div className="delivery-details">
<h4>Направление: {delivery.destination}</h4>
<p className={`status status-${delivery.status.toLowerCase().replace(' ', '-')}`}>
Статус: {delivery.status}
</p>
</div>
<div className="delivery-customer">
<h4>Клиент: {delivery.customer}</h4>
</div>
<div className="delivery-actions">
<button onClick={() => onEdit(delivery)}>Редактировать</button>
<button onClick={() => onDelete(delivery.id)}>Удалить</button>
</div>
</div>
)
}
);
};
export default DeliveryItem
export default DeliveryItem;

View File

@@ -1,26 +1,35 @@
import { useState } from 'react'
import DeliveryItem from './DeliveryItem'
import DeliveryForm from './DeliveryForm'
import { useState } from 'react';
import { useDeliveries } from '../hooks/useDeliveries';
import DeliveryItem from './DeliveryItem';
import DeliveryForm from './DeliveryForm';
import { Link } from 'react-router-dom';
const DeliveryList = ({ deliveries, onAdd, onUpdate, onDelete }) => {
const [editingDelivery, setEditingDelivery] = useState(null)
const DeliveryList = () => {
const { deliveries, loading, error, addDelivery, updateDelivery, deleteDelivery } = useDeliveries();
const [editingDelivery, setEditingDelivery] = useState(null);
const handleEdit = (delivery) => {
setEditingDelivery(delivery)
}
const handleSubmit = (formData) => {
if (formData.id) {
onUpdate(formData)
} else {
onAdd(formData)
const handleSubmit = async (formData) => {
try {
if (formData.id) {
await updateDelivery(formData);
} else {
await addDelivery(formData);
}
setEditingDelivery(null);
} catch (err) {
console.error('Error saving delivery:', err);
}
setEditingDelivery(null)
}
};
if (loading) return <div className="loading">Загрузка данных...</div>;
if (error) return <div className="error">Ошибка: {error}</div>;
return (
<div className="delivery-list">
<h2>Список доставок</h2>
<div className="list-header">
<h2>Список доставок</h2>
<Link to="/stats" className="stats-link">Статистика</Link>
</div>
<DeliveryForm
key={editingDelivery ? editingDelivery.id : 'new'}
@@ -30,17 +39,21 @@ const DeliveryList = ({ deliveries, onAdd, onUpdate, onDelete }) => {
/>
<div className="deliveries">
{deliveries.map(delivery => (
<DeliveryItem
key={delivery.id}
delivery={delivery}
onEdit={handleEdit}
onDelete={onDelete}
/>
))}
{deliveries.length === 0 ? (
<p className="no-deliveries">Нет доступных доставок</p>
) : (
deliveries.map(delivery => (
<DeliveryItem
key={delivery.id}
delivery={delivery}
onEdit={setEditingDelivery}
onDelete={deleteDelivery}
/>
))
)}
</div>
</div>
)
}
);
};
export default DeliveryList
export default DeliveryList;

View File

@@ -0,0 +1,38 @@
import { useDeliveries } from '../hooks/useDeliveries';
import { Link } from 'react-router-dom';
const DeliveryStats = () => {
const { getStats } = useDeliveries();
const stats = getStats();
return (
<div className="stats-container">
<h2>Статистика доставок</h2>
<div className="stats-grid">
<div className="stat-card">
<h3>Всего</h3>
<p>{stats.total}</p>
</div>
<div className="stat-card">
<h3>Принято</h3>
<p>{stats['Принято'] || 0}</p>
</div>
<div className="stat-card">
<h3>В пути</h3>
<p>{stats['В пути'] || 0}</p>
</div>
<div className="stat-card">
<h3>Доставлено</h3>
<p>{stats['Доставлено'] || 0}</p>
</div>
<div className="stat-card">
<h3>Отменено</h3>
<p>{stats['Отменено'] || 0}</p>
</div>
</div>
<Link to="/" className="back-link"> Назад к списку</Link>
</div>
);
};
export default DeliveryStats;

View File

@@ -0,0 +1,87 @@
import { useState, useEffect } from 'react';
const API_URL = 'http://localhost:3001/deliveries';
export const useDeliveries = () => {
const [deliveries, setDeliveries] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchDeliveries = async () => {
try {
const response = await fetch(API_URL);
if (!response.ok) throw new Error('Server error');
const data = await response.json();
setDeliveries(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchDeliveries();
}, []);
const addDelivery = async (delivery) => {
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(delivery)
});
const newDelivery = await response.json();
setDeliveries(prev => [...prev, newDelivery]);
return newDelivery;
} catch (err) {
setError(err.message);
throw err;
}
};
const updateDelivery = async (delivery) => {
try {
const response = await fetch(`${API_URL}/${delivery.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(delivery)
});
const updated = await response.json();
setDeliveries(prev => prev.map(d => d.id === delivery.id ? updated : d));
return updated;
} catch (err) {
setError(err.message);
throw err;
}
};
const deleteDelivery = async (id) => {
try {
await fetch(`${API_URL}/${id}`, { method: 'DELETE' });
setDeliveries(prev => prev.filter(d => d.id !== id));
} catch (err) {
setError(err.message);
throw err;
}
};
const getStats = () => {
return deliveries.reduce((acc, curr) => {
acc.total++;
acc[curr.status] = (acc[curr.status] || 0) + 1;
return acc;
}, { total: 0 });
};
return {
deliveries,
loading,
error,
addDelivery,
updateDelivery,
deleteDelivery,
getStats,
refetch: fetchDeliveries
};
};

View File

@@ -1,10 +1,13 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
);