DONE
This commit is contained in:
parent
7d736c10b5
commit
52e76173ea
File diff suppressed because one or more lines are too long
@ -1,31 +1,26 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Container } from 'react-bootstrap';
|
import { Container } from 'react-bootstrap';
|
||||||
import { Toaster } from 'react-hot-toast';
|
import { Toaster } from 'react-hot-toast';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import Footer from './components/footer/Footer.jsx';
|
import Footer from './components/footer/Footer.jsx';
|
||||||
import Navigation from './components/navigation/Navigation.jsx';
|
import Navigation from './components/navigation/Navigation.jsx';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
|
|
||||||
import store from './Reducer/store'; // Путь к вашему store
|
import { UserProvider } from './Context/UserContext';
|
||||||
|
|
||||||
|
// Путь к вашему store
|
||||||
const App = ({ routes }) => {
|
const App = ({ routes }) => {
|
||||||
useEffect(() => {
|
|
||||||
const storedUser = localStorage.getItem('user');
|
|
||||||
if (storedUser) {
|
|
||||||
store.dispatch({ type: 'SET_USER', payload: JSON.parse(storedUser) });
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Provider store={store}>
|
<UserProvider>
|
||||||
<Navigation routes={routes}></Navigation>
|
<Navigation routes={routes}></Navigation>
|
||||||
<Container className='p-2' as='main' fluid>
|
<Container className='p-2' as='main' fluid>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Container>
|
</Container>
|
||||||
<Footer />
|
<Footer />
|
||||||
<Toaster position='top-center' reverseOrder={true} />
|
<Toaster position='top-center' reverseOrder={true} />
|
||||||
</Provider>
|
</UserProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
32
Lab5/src/Context/UserContext.jsx
Normal file
32
Lab5/src/Context/UserContext.jsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Файл UserContext.jsx
|
||||||
|
|
||||||
|
import React, { createContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const UserContext = createContext();
|
||||||
|
|
||||||
|
export const UserProvider = ({ children }) => {
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const storedUser = localStorage.getItem('user');
|
||||||
|
if (storedUser) {
|
||||||
|
setUser(JSON.parse(storedUser));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const login = (userData) => {
|
||||||
|
setUser(userData);
|
||||||
|
localStorage.setItem('user', JSON.stringify(userData));
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
setUser(null);
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{ user, login, logout }}>
|
||||||
|
{children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -1,21 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Route, Navigate } from 'react-router-dom';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
const ProtectedRoute = ({ element: Element, adminOnly, ...rest }) => {
|
|
||||||
const user = useSelector((state) => state.user);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
// Если пользователь не авторизован, перенаправляем на страницу входа
|
|
||||||
return <Navigate to="/page5" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adminOnly && user.status !== 'admin') {
|
|
||||||
// Если запрошен доступ только для администраторов, но пользователь не администратор
|
|
||||||
return <Navigate to="/" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Route {...rest} element={<Element />} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProtectedRoute;
|
|
@ -1,7 +0,0 @@
|
|||||||
// store.js
|
|
||||||
import { createStore } from 'redux';
|
|
||||||
import userReducer from './userReducer';
|
|
||||||
|
|
||||||
const store = createStore(userReducer);
|
|
||||||
|
|
||||||
export default store;
|
|
@ -1,23 +0,0 @@
|
|||||||
// userReducer.js
|
|
||||||
const initialState = {
|
|
||||||
user: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const userReducer = (state = initialState, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'SET_USER':
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
user: action.payload,
|
|
||||||
};
|
|
||||||
case 'LOGOUT':
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
user: null,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default userReducer;
|
|
@ -28,19 +28,27 @@ export const getLine = async (id) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Функция для обновления новости
|
// Функция для обновления новости
|
||||||
export const updateLine = async (id, newData) => {
|
export const updateNews = async (newsId, newData) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_URL}/${id}`, {
|
// Ваш код для обновления информации о новости по идентификатору newsId
|
||||||
|
// Например, отправка запроса на сервер и обновление данных
|
||||||
|
const response = await fetch(`API_ENDPOINT/news/${newsId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newData),
|
body: JSON.stringify(newData),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка обновления информации о новости');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возвращаем обновленные данные о новости
|
||||||
|
return await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка при обновлении новости:', error);
|
console.error('Произошла ошибка при обновлении информации о новости:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,8 +33,6 @@ const Lines = () => {
|
|||||||
handleFormClose,
|
handleFormClose,
|
||||||
} = useLinesFormModal(handleLinesChange);
|
} = useLinesFormModal(handleLinesChange);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
|
@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { PencilFill, PencilSquare, Trash3 } from 'react-bootstrap-icons';
|
import { PencilFill, PencilSquare, Trash3 } from 'react-bootstrap-icons';
|
||||||
|
|
||||||
const LinesTableRow = ({
|
const LinesTableRow = ({
|
||||||
index, line, onDelete, onEdit, onEditInPage,
|
index, line, onDelete, onEdit,
|
||||||
}) => {
|
}) => {
|
||||||
const handleAnchorClick = (event, action) => {
|
const handleAnchorClick = (event, action) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { Container, Nav, Navbar, Button } from 'react-bootstrap';
|
import { Container, Nav, Navbar, Button } from 'react-bootstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { logoutUser } from './js/userActions';
|
import { UserContext } from '../../Context/UserContext.jsx';
|
||||||
import logo from '../../assets/logo.png';
|
import logo from '../../assets/logo.png';
|
||||||
|
import useHandleLogout from './utils/useHandleLogout';
|
||||||
import './Navigation.css';
|
import './Navigation.css';
|
||||||
|
|
||||||
const Navigation = ({ routes }) => {
|
const Navigation = ({ routes }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const indexPageLink = routes.find((route) => route.index === true);
|
const indexPageLink = routes.find((route) => route.index === true);
|
||||||
const pages = routes.filter((route) => Object.prototype.hasOwnProperty.call(route, 'title'));
|
const pages = routes.filter((route) => Object.prototype.hasOwnProperty.call(route, 'title'));
|
||||||
const user = useSelector((state) => state.user); // Получение пользователя из хранилища Redux
|
const { user} = useContext(UserContext);
|
||||||
const dispatch = useDispatch();
|
const handleLogout = useHandleLogout();
|
||||||
|
|
||||||
const isAdmin = user && user.status === 'admin'; // Проверка статуса пользователя
|
const isAdmin = user && user.status === 'admin'; // Проверка статуса пользователя
|
||||||
const handleLogout = () => {
|
|
||||||
localStorage.removeItem('user');
|
|
||||||
dispatch(logoutUser()); // Действие для выхода пользователя
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<Navbar expand='md' className='my-navbar'>
|
<Navbar expand='md' className='my-navbar'>
|
||||||
@ -50,8 +46,8 @@ const Navigation = ({ routes }) => {
|
|||||||
{/* Приветствие и кнопка выхода */}
|
{/* Приветствие и кнопка выхода */}
|
||||||
{user ? (
|
{user ? (
|
||||||
<div>
|
<div>
|
||||||
<span>Добро пожаловать, {user.username}</span>
|
<span>Добро пожаловать, {user.username} </span>
|
||||||
<button onClick={handleLogout}>Выход</button>
|
<Button className="btn btn-primary" onClick={handleLogout}>Выход</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@ -66,4 +62,4 @@ Navigation.propTypes = {
|
|||||||
routes: PropTypes.array,
|
routes: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Navigation;
|
export default Navigation;
|
@ -1,7 +0,0 @@
|
|||||||
// userActions.js
|
|
||||||
export const logoutUser = () => {
|
|
||||||
return {
|
|
||||||
type: 'LOGOUT',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
17
Lab5/src/components/navigation/utils/useHandleLogout.js
Normal file
17
Lab5/src/components/navigation/utils/useHandleLogout.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// useHandleLogout.js
|
||||||
|
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { UserContext } from '../../../Context/UserContext.jsx';
|
||||||
|
|
||||||
|
const useHandleLogout = () => {
|
||||||
|
const { logout } = useContext(UserContext);
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
logout(); // Вызов функции logout из контекста для выхода пользователя
|
||||||
|
};
|
||||||
|
|
||||||
|
return handleLogout;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useHandleLogout;
|
@ -1,49 +1,9 @@
|
|||||||
// NewsPage.jsx
|
import Cards from './utils/cards.jsx';
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { getAllLines } from '../components/api/cards_api';
|
|
||||||
|
|
||||||
|
|
||||||
const NewsPage = () => {
|
const NewsPage = () => {
|
||||||
const [news, setNews] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Получаем все новости при загрузке компонента
|
|
||||||
getAllNews();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getAllNews = async () => {
|
|
||||||
try {
|
|
||||||
// Получаем данные с сервера
|
|
||||||
const data = await getAllLines();
|
|
||||||
setNews(data); // Обновляем состояние новостей
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка получения новостей:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Функция для создания карточки на основе данных новости
|
|
||||||
const createNewsCard = (newsData) => {
|
|
||||||
return (
|
|
||||||
<div className="col-md-4" key={newsData.id}>
|
|
||||||
<div className="card news-card">
|
|
||||||
<img src={newsData.image || 'https://via.placeholder.com/200'} className="card-img-top" alt="Изображение новости" />
|
|
||||||
<div className="card-body">
|
|
||||||
<h5 className="card-title">{newsData.price}</h5>
|
|
||||||
<p className="card-text">{newsData.count}</p>
|
|
||||||
<p className="card-text">{newsData.sum}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<Cards />
|
||||||
<div className="row" id="news-cards-container">
|
|
||||||
{news.map((newsItem) => createNewsCard(newsItem))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,51 +1,12 @@
|
|||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { useState,useEffect } from 'react';
|
import { useForm } from 'react-hook-form'; // Импорт хука useForm
|
||||||
import axios from 'axios';
|
import useSubmitForm from './hooks/useSubmitForm';
|
||||||
import { useDispatch } from 'react-redux';
|
import './styles/buttonpurp.css';
|
||||||
import './button.css';
|
|
||||||
|
|
||||||
const Page5 = () => {
|
const Page5 = () => {
|
||||||
const { register, handleSubmit } = useForm();
|
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
|
||||||
const baseURL = 'http://localhost:8081/';
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const onSubmit = async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${baseURL}users`, {
|
|
||||||
params: {
|
|
||||||
login: data.login,
|
|
||||||
password: data.password,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data.length > 0) {
|
|
||||||
console.log('Успешный вход');
|
|
||||||
const setUserData = (userData) => {
|
|
||||||
return {
|
|
||||||
type: 'SET_USER',
|
|
||||||
payload: userData,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const userData = {
|
|
||||||
username: data.login,
|
|
||||||
status: response.data[0].status,
|
|
||||||
};
|
|
||||||
dispatch(setUserData(userData));
|
|
||||||
localStorage.setItem('user', JSON.stringify(userData));
|
|
||||||
redirectToHomePage(); // Замените на вашу функцию перехода
|
|
||||||
} else {
|
|
||||||
console.error('Неверный логин или пароль');
|
|
||||||
setErrorMessage('Неверный логин или пароль');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Произошла ошибка:', error);
|
|
||||||
setErrorMessage('Произошла ошибка. Пожалуйста, попробуйте снова.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const redirectToHomePage = () => {
|
const { register, handleSubmit } = useForm(); // Используем хук useForm
|
||||||
window.location.href = 'http://localhost:5173/';
|
const { handleSubmit: onSubmit, errorMessage } = useSubmitForm();
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useSelector, useDispatch} from 'react-redux';
|
|
||||||
import { Navigate } from 'react-router-dom';
|
|
||||||
import Lines from '../components/lines/table/Lines.jsx';
|
import Lines from '../components/lines/table/Lines.jsx';
|
||||||
|
|
||||||
const Page6 = () => {
|
const Page6 = () => {
|
||||||
const user = useSelector((state) => state.user);
|
|
||||||
const isAdmin = user && user.status === 'admin';
|
|
||||||
if (!isAdmin) {
|
|
||||||
return <Navigate to='/' replace />;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Lines />
|
<Lines />
|
||||||
);
|
);
|
||||||
|
41
Lab5/src/pages/hooks/useNews.js
Normal file
41
Lab5/src/pages/hooks/useNews.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// hooks/useNews.js
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { getAllLines } from '../../components/api/cards_api';
|
||||||
|
|
||||||
|
const useNews = () => {
|
||||||
|
const [news, setNews] = useState([]);
|
||||||
|
const [filteredNews, setFilteredNews] = useState([]);
|
||||||
|
const [selectedType, setSelectedType] = useState('');
|
||||||
|
|
||||||
|
const getAllNews = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getAllLines();
|
||||||
|
setNews(data);
|
||||||
|
setFilteredNews(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка получения новостей:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAllNews();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedType) {
|
||||||
|
const filtered = news.filter((item) => item.type === selectedType);
|
||||||
|
setFilteredNews(filtered);
|
||||||
|
} else {
|
||||||
|
setFilteredNews(news);
|
||||||
|
}
|
||||||
|
}, [selectedType, news]);
|
||||||
|
|
||||||
|
const handleTypeChange = (type) => {
|
||||||
|
setSelectedType(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { filteredNews, handleTypeChange };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useNews;
|
45
Lab5/src/pages/hooks/useSubmitForm.js
Normal file
45
Lab5/src/pages/hooks/useSubmitForm.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
const useSubmitForm = () => {
|
||||||
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
|
const baseURL = 'http://localhost:8081/';
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleSubmit = async (data) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${baseURL}users`, {
|
||||||
|
params: {
|
||||||
|
login: data.login,
|
||||||
|
password: data.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.length > 0) {
|
||||||
|
console.log('Успешный вход');
|
||||||
|
const userData = {
|
||||||
|
username: data.login,
|
||||||
|
status: response.data[0].status,
|
||||||
|
};
|
||||||
|
// Обработка успешного входа, например, обновление контекста пользователя
|
||||||
|
localStorage.setItem('user', JSON.stringify(userData));
|
||||||
|
redirectToHomePage();
|
||||||
|
} else {
|
||||||
|
console.error('Неверный логин или пароль');
|
||||||
|
setErrorMessage('Неверный логин или пароль');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Произошла ошибка:', error);
|
||||||
|
setErrorMessage('Произошла ошибка. Пожалуйста, попробуйте снова.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const redirectToHomePage = () => {
|
||||||
|
window.location.href = 'http://localhost:5173/';
|
||||||
|
};
|
||||||
|
|
||||||
|
return { handleSubmit, errorMessage };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSubmitForm;
|
27
Lab5/src/pages/utils/cards.jsx
Normal file
27
Lab5/src/pages/utils/cards.jsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Select from '../../components/input/Select.jsx';
|
||||||
|
import CreateNewsCard from './createNewsCard.jsx';
|
||||||
|
import useTypeFilter from '../../components/lines/hooks/LinesFilterHook';
|
||||||
|
import useLines from '../../components/lines/hooks/LinesHook';
|
||||||
|
|
||||||
|
const Cards = () => {
|
||||||
|
const { types, currentFilter, handleFilterChange } = useTypeFilter();
|
||||||
|
const { lines, handleLinesChange } = useLines(currentFilter);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<Select className='mt-2' values={types} label='Фильтр'
|
||||||
|
value={currentFilter} onChange={handleFilterChange} />
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
|
{lines.map((line) => (
|
||||||
|
<div key={line.id} className="col-md-4">
|
||||||
|
<CreateNewsCard line={line} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Cards;
|
28
Lab5/src/pages/utils/createNewsCard.jsx
Normal file
28
Lab5/src/pages/utils/createNewsCard.jsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
|
||||||
|
const CreateNewsCard = ({ line }) => {
|
||||||
|
const handleAnchorClick = (event, action) => {
|
||||||
|
event.preventDefault();
|
||||||
|
action();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card news-card">
|
||||||
|
<img src={line.image || 'https://via.placeholder.com/200'} className="card-img-top" alt="Изображение новости" />
|
||||||
|
<div className="card-body">
|
||||||
|
<p className="card-text">{line.type.name}</p>
|
||||||
|
<h5 className="card-title">{line.price}</h5>
|
||||||
|
<p className="card-text">{line.count}</p>
|
||||||
|
<p className="card-text">{line.sum}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CreateNewsCard.propTypes = {
|
||||||
|
line: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateNewsCard;
|
BIN
Lab5/Отчет по ип 5 (1).docx
Normal file
BIN
Lab5/Отчет по ип 5 (1).docx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user