From faac402aa6fb68afffaff1f91281404d120b38b0 Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 19 Mar 2025 18:05:29 +0300 Subject: [PATCH] integrate `LinkedItems` into `/sight` pages --- src/components/LinkedItems.tsx | 56 +++++++++-- src/pages/sight/edit.tsx | 5 + src/pages/sight/show.tsx | 167 +-------------------------------- src/pages/sight/types.ts | 17 ++++ 4 files changed, 72 insertions(+), 173 deletions(-) create mode 100644 src/pages/sight/types.ts diff --git a/src/components/LinkedItems.tsx b/src/components/LinkedItems.tsx index 8d0b5c3..001374d 100644 --- a/src/components/LinkedItems.tsx +++ b/src/components/LinkedItems.tsx @@ -1,5 +1,5 @@ import {useState, useEffect} from 'react' -import {Stack, Typography, Button, MenuItem, Select, FormControl, InputLabel, Grid, Box, Accordion, AccordionSummary, AccordionDetails, useTheme} from '@mui/material' +import {Stack, Typography, Button, MenuItem, Select, FormControl, InputLabel, Grid, Box, Accordion, AccordionSummary, AccordionDetails, useTheme, TextField} from '@mui/material' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import axios from 'axios' import {BACKEND_URL} from '../lib/constants' @@ -22,6 +22,7 @@ export const LinkedItems = ({parentI const [items, setItems] = useState([]) const [linkedItems, setLinkedItems] = useState([]) const [selectedItemId, setSelectedItemId] = useState('') + const [pageNum, setPageNum] = useState(1) const [isLoading, setIsLoading] = useState(true) const theme = useTheme() @@ -55,18 +56,35 @@ export const LinkedItems = ({parentI } }, [childResource, type]) + useEffect(() => { + if (childResource === 'article' && parentResource === 'sight') { + setPageNum(linkedItems.length + 1) + } + }, [linkedItems, childResource, parentResource]) + const availableItems = items.filter((item) => !linkedItems.some((linked) => linked.id === item.id)) const linkItem = () => { if (selectedItemId) { + const requestData = + childResource === 'article' + ? { + [`${childResource}_id`]: selectedItemId, + page_num: pageNum, + } + : { + [`${childResource}_id`]: selectedItemId, + } + axios - .post(`${BACKEND_URL}/${parentResource}/${parentId}/${childResource}`, { - [`${childResource}_id`]: selectedItemId, - }) + .post(`${BACKEND_URL}/${parentResource}/${parentId}/${childResource}`, requestData) .then(() => { axios.get(`${BACKEND_URL}/${parentResource}/${parentId}/${childResource}`).then((response) => { setLinkedItems(response?.data || []) setSelectedItemId('') + if (childResource === 'article') { + setPageNum(pageNum + 1) + } }) }) .catch((error) => { @@ -98,7 +116,7 @@ export const LinkedItems = ({parentI }} > - {type === 'show' ? `Привязанные ${title}` : title} + Привязанные {title} @@ -138,11 +156,11 @@ export const LinkedItems = ({parentI {type === 'edit' && ( - <> + + Добавить {title} - {title} - - setSelectedItemId(Number(e.target.value))} label={`Выберите ${title}`} fullWidth> {availableItems.map((item) => ( {item[fields[0].data]} @@ -151,10 +169,28 @@ export const LinkedItems = ({parentI + {childResource === 'article' && ( + + { + const newValue = Number(e.target.value) + const minValue = linkedItems.length + 1 // page number on articles lenght + setPageNum(newValue < minValue ? minValue : newValue) + }} + fullWidth + InputLabelProps={{shrink: true}} + /> + + )} + - + )} diff --git a/src/pages/sight/edit.tsx b/src/pages/sight/edit.tsx index c2a21c4..fd8405b 100644 --- a/src/pages/sight/edit.tsx +++ b/src/pages/sight/edit.tsx @@ -2,6 +2,9 @@ import {Autocomplete, Box, TextField} from '@mui/material' import {Edit, useAutocomplete} from '@refinedev/mui' import {useForm} from '@refinedev/react-hook-form' import {Controller} from 'react-hook-form' +import {useParams} from 'react-router' +import {LinkedItems} from '../../components/LinkedItems' +import {ArticleItem, articleFields} from './types' export const SightEdit = () => { const { @@ -11,6 +14,7 @@ export const SightEdit = () => { formState: {errors}, } = useForm({}) + const {id: sightId} = useParams<{id: string}>() const {autocompleteProps: cityAutocompleteProps} = useAutocomplete({ resource: 'city', }) @@ -83,6 +87,7 @@ export const SightEdit = () => { )} /> + {sightId && type="edit" parentId={sightId} parentResource="sight" childResource="article" fields={articleFields} title="статьи" />} ) } diff --git a/src/pages/sight/show.tsx b/src/pages/sight/show.tsx index 4867ff8..6ff6953 100644 --- a/src/pages/sight/show.tsx +++ b/src/pages/sight/show.tsx @@ -1,101 +1,14 @@ -import {Stack, Typography, Box, Grid2 as Grid, Button, MenuItem, Select, FormControl, InputLabel, TextField} from '@mui/material' +import {Stack, Typography} from '@mui/material' import {useShow} from '@refinedev/core' import {Show, TextFieldComponent} from '@refinedev/mui' - -import {useEffect, useState} from 'react' -import axios from 'axios' - -import {BACKEND_URL} from '../../lib/constants' - -type ArticleItem = { - id: number - heading: string - body: string -} +import {LinkedItems} from '../../components/LinkedItems' +import {ArticleItem, articleFields} from './types' export const SightShow = () => { const {query} = useShow({}) const {data, isLoading} = query const record = data?.data - const [articles, setArticles] = useState([]) - const [linkedArticles, setLinkedArticles] = useState([]) - const [selectedArticleId, setSelectedArticleId] = useState('') - const [pageNum, setPageNum] = useState(1) - const [articlesLoading, setArticlesLoading] = useState(true) - - useEffect(() => { - if (record?.id) { - axios - .get(`${BACKEND_URL}/sight/${record.id}/article`) - .then((response) => { - setLinkedArticles(response?.data || []) - }) - .catch(() => { - setLinkedArticles([]) - }) - } - }, [record?.id]) - - useEffect(() => { - axios - .get(`${BACKEND_URL}/article/`) // without "/" throws CORS error - .then((response) => { - setArticles(response?.data || []) - setArticlesLoading(false) - }) - .catch(() => { - setArticles([]) - setArticlesLoading(false) - }) - }, []) - - const availableArticles = articles.filter((article) => !linkedArticles.some((linked) => linked.id === article.id)) - - const linkArticle = () => { - if (selectedArticleId) { - const requestData = { - article_id: selectedArticleId, - page_num: pageNum, - } - - axios - .post(`${BACKEND_URL}/sight/${record?.id}/article`, requestData, { - headers: { - accept: 'application/json', - 'Content-Type': 'application/json', - }, - }) - .then(() => { - axios - .get(`${BACKEND_URL}/sight/${record?.id}/article`) - .then((response) => { - setLinkedArticles(response?.data || []) - setPageNum(pageNum + 1) - }) - .catch(() => { - setLinkedArticles([]) - }) - }) - .catch((error) => { - console.error('Error linking article:', error) - }) - } - } - - const deleteArticle = (articleId: number) => { - axios - .delete(`${BACKEND_URL}/sight/${record?.id}/article`, { - data: {article_id: articleId}, - }) - .then(() => { - setLinkedArticles((prev) => prev.filter((item) => item.id !== articleId)) - }) - .catch((error) => { - console.error('Error unlinking article:', error) - }) - } - const fields = [ // {label: 'ID', data: 'id'}, {label: 'Название', data: 'name'}, @@ -117,79 +30,7 @@ export const SightShow = () => { ))} - - - Привязанные статьи - - - - {articlesLoading ? ( - Загрузка статей... - ) : linkedArticles.length > 0 ? ( - linkedArticles.map((article) => ( - `2px solid ${theme.palette.divider}`, - }} - > - - - {article.heading} - - - - {article.body} - - - - - - )) - ) : ( - Статьи не найдены - )} - - - - - Привязать статью - - - - - Статья - - - - - setPageNum(Number(e.target.value))} fullWidth InputLabelProps={{shrink: true}} /> - - - - - - + {record?.id && type="show" parentId={record.id} parentResource="sight" childResource="article" fields={articleFields} title="статьи" />} ) diff --git a/src/pages/sight/types.ts b/src/pages/sight/types.ts new file mode 100644 index 0000000..51ed3f1 --- /dev/null +++ b/src/pages/sight/types.ts @@ -0,0 +1,17 @@ +export type ArticleItem = { + id: number + heading: string + body: string + [key: string]: string | number +} + +export type FieldType = { + label: string + data: keyof T + render?: (value: any) => React.ReactNode +} + +export const articleFields: Array> = [ + {label: 'Заголовок', data: 'heading'}, + {label: 'Текст', data: 'body'}, +] \ No newline at end of file