Skip to content

Commit

Permalink
Merge pull request #48 from KNU-AEYE/46-태그-검색-기능
Browse files Browse the repository at this point in the history
46-태그-검색-기능
  • Loading branch information
ilp-sys authored May 23, 2024
2 parents 8d020ec + 5a5e0f5 commit 3c83264
Show file tree
Hide file tree
Showing 22 changed files with 616 additions and 266 deletions.
61 changes: 51 additions & 10 deletions aeye/app/(nav)/cams/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,57 @@
"use client";

import { useEffect } from "react";
import { useEffect, useState } from "react";
import fetchWithInterception from "@/app/fetchWrapper";
import { Grid } from "@mui/material";

export default function Cams() {
const [videos, setVideos] = useState<VideoResponseDto[]>([]);
const [page, setPage] = useState(0);

useEffect(() => {
fetchWithInterception("https://api.a-eye.live/video", {
method: "GET",
})
.then((response) => response.json())
.then((jsonData) => console.log(jsonData.data))
.catch((error) => console.error(error));
}, []);
return <div></div>;
const fetchVideos = async () => {
try {
const response = await fetchWithInterception(
`https://api.a-eye.live/video?page=${page}&size=12`,
{
method: "GET",
}
);
const jsonData = await response.json();
setVideos((prevVideos) => [...prevVideos, ...jsonData.data.videos]);
} catch (error) {
console.error(error);
}
};

fetchVideos();
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [page]);

const handleScroll = () => {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage((prevPage) => prevPage + 1);
}
};

return (
<Grid container spacing={2}>
{videos.map((video, index) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
<video
id={`videoPlayer_${index}`}
autoPlay
muted
src={video.videoUri}
style={{ width: "100%", height: "100%" }}
/>
</Grid>
))}
</Grid>
);
}
54 changes: 7 additions & 47 deletions aeye/app/(nav)/home/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,17 @@
import { styled, alpha } from "@mui/material/styles";
import SearchIcon from "@mui/icons-material/Search";
import InputBase from "@mui/material/InputBase";
import { useRecoilState } from "recoil";
import { searchQueryState } from "@/app/recoil-states";

const Search = styled("div")(({ theme }) => ({
position: "relative",
borderRadius: theme.shape.borderRadius,
backgroundColor: "#efefef",
"&:hover": {
backgroundColor: alpha(theme.palette.common.white, 0.25),
},
marginLeft: 0,
width: "100%",
marginBottom: theme.spacing(2),
[theme.breakpoints.up("sm")]: {
marginLeft: theme.spacing(1),
width: "auto",
},
}));

const SearchIconWrapper = styled("div")(({ theme }) => ({
padding: theme.spacing(0, 2),
height: "100%",
position: "absolute",
pointerEvents: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
}));

const StyledInputBase = styled(InputBase)(({ theme }) => ({
color: "inherit",
width: "100%",
"& .MuiInputBase-input": {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
transition: theme.transitions.create("width"),
[theme.breakpoints.up("sm")]: {
width: "12ch",
"&:focus": {
width: "20ch",
},
},
},
}));
import {
Search,
SearchIconWrapper,
StyledInputBase,
} from "@/app/(nav)/home/styleSearch";

export default function SearchBar() {
const [searchQuery, setSearchQuery] = useRecoilState(searchQueryState); // Using Recoil state
const [searchQuery, setSearchQuery] = useRecoilState(searchQueryState);

const handleInputChange = (event: any) => {
setSearchQuery(event.target.value); // Update Recoil state with user input
setSearchQuery(event.target.value);
};

return (
Expand Down
48 changes: 27 additions & 21 deletions aeye/app/(nav)/home/SearchResult.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import React, { useEffect, useState } from "react";
import { useRecoilValue } from "recoil";
import { searchQueryState } from "@/app/recoil-states";
import { searchQueryState, selectedTagsState } from "@/app/recoil-states";
import fetchWithInterception from "@/app/fetchWrapper";
import Vidpane from "@/app/components/Vidpane";
import { Grid, Paper, Typography, Pagination } from "@mui/material";
import { Grid, Typography, Pagination, Container } from "@mui/material";

const FETCH_TIMEOUT = 200;
const PAGE_SIZE = 9;
const PAGE_SIZE = 12;

function Vidgroup({ videos }: { videos: VideoDocument[] }) {
return (
<Grid container spacing={2}>
{videos.map((video, index) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
<Paper style={{ padding: 10, background: "#f0f0f0" }}>
<Vidpane video={video} />
</Paper>
<Vidpane video={video} />
</Grid>
))}
</Grid>
Expand All @@ -38,14 +36,19 @@ function NoResultTypography() {
const SearchResult: React.FC = () => {
const searchQuery = useRecoilValue(searchQueryState);
const [results, setResults] = useState<Vidarr | null>(null);
const [currentPage, setCurrentPage] = useState<number>(1);
const [currentPage, setCurrentPage] = useState<number>(0);
const selectedTags = useRecoilValue(selectedTagsState);
const cityPlaceholder = "";
const districtPlaceholder = "";

useEffect(() => {
let searchTimeout: NodeJS.Timeout;

const fetchResults = () => {
fetchWithInterception(
`https://api.a-eye.live/video/search?keyword=${searchQuery}&page=${currentPage}&size=${PAGE_SIZE}`,
`https://api.a-eye.live/video/search/v2?keyword=${searchQuery}&city=${cityPlaceholder}&district=${districtPlaceholder}&tag=${
selectedTags === undefined ? "" : selectedTags
}&page=${currentPage}&size=${PAGE_SIZE}`,
{ method: "GET" }
)
.then((response) => response.json())
Expand All @@ -57,30 +60,33 @@ const SearchResult: React.FC = () => {
searchTimeout = setTimeout(fetchResults, FETCH_TIMEOUT);
}
return () => clearTimeout(searchTimeout);
}, [searchQuery, currentPage]);
}, [searchQuery, currentPage, selectedTags]);

const handlePaginationChange = (
event: React.ChangeEvent<unknown>,
page: number
) => {
setCurrentPage(page);
setCurrentPage(page - 1);
};

return (
<>
{results && results.videoDocuments.length > 0 ? (
<>
<Container
sx={{
display: "grid",
placeItems: "center",
gap: "20px",
}}
>
<Vidgroup videos={results.videoDocuments} />
<div
style={{ display: "flex", justifyContent: "center", marginTop: 20 }}
>
<Pagination
count={results.totalPage}
page={currentPage}
onChange={handlePaginationChange}
/>
</div>
</>
<Pagination
count={results.totalPage}
page={currentPage + 1}
onChange={handlePaginationChange}
size="large"
/>
</Container>
) : (
<NoResultTypography />
)}
Expand Down
33 changes: 33 additions & 0 deletions aeye/app/(nav)/home/SearchTagList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Chip, Stack } from "@mui/material";
import { selectedTagsState } from "@/app/recoil-states";
import { useRecoilState } from "recoil";

const tags = ["새 객체", "넘어짐", "화재", "자동차", "도로"];

export default function SearchTagList() {
const [selectedTags, setSelectedTags] = useRecoilState(selectedTagsState);

const handleChipClick = (tag: string) => {
setSelectedTags((prevSelectedTags) => {
if (prevSelectedTags.includes(tag)) {
return prevSelectedTags.filter((t) => t !== tag);
} else {
return [...prevSelectedTags, tag];
}
});
};
return (
<Stack flexDirection="row" mb="15px">
{tags.map((tag, index) => (
<Chip
label={tag}
key={index}
variant={selectedTags.includes(tag) ? "filled" : "outlined"}
sx={{ margin: "0.5vh" }}
color={selectedTags.includes(tag) ? "primary" : "default"}
onClick={() => handleChipClick(tag)}
/>
))}
</Stack>
);
}
2 changes: 2 additions & 0 deletions aeye/app/(nav)/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import SearchBar from "./SearchBar";
import SearchResult from "./SearchResult";
import SearchTagList from "./SearchTagList";

export default function Home() {
return (
<>
<SearchBar />
<SearchTagList />
<SearchResult />
</>
);
Expand Down
54 changes: 54 additions & 0 deletions aeye/app/(nav)/home/styleSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import InputBase from "@mui/material/InputBase";
import { styled, alpha } from "@mui/material/styles";

const Search = styled("div")(({ theme }) => ({
position: "relative",
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.info.main, // Use theme.palette.info.main
marginLeft: 0,
width: "100%",
marginBottom: theme.spacing(2),
transition: theme.transitions.create([
"background-color",
"box-shadow",
"border",
]),
border: `1px solid transparent`, // Default border
"&:hover, &:focus-within": {
backgroundColor: theme.palette.info.light,
boxShadow: `0 0 0 1px ${alpha(theme.palette.info.contrastText, 0.5)}`,
border: `1px solid ${theme.palette.info.main}`,
},
[theme.breakpoints.up("sm")]: {
marginLeft: theme.spacing(1),
width: "auto",
},
}));

const SearchIconWrapper = styled("div")(({ theme }) => ({
padding: theme.spacing(0, 2),
height: "100%",
position: "absolute",
pointerEvents: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
}));

const StyledInputBase = styled(InputBase)(({ theme }) => ({
color: theme.palette.info.contrastText, // Use theme.palette.info.contrastText for text color
width: "100%",
"& .MuiInputBase-input": {
padding: theme.spacing(1, 1, 1, 0),
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
transition: theme.transitions.create("width"),
[theme.breakpoints.up("sm")]: {
width: "12ch",
"&:focus": {
width: "20ch",
},
},
},
}));

export { Search, SearchIconWrapper, StyledInputBase };
9 changes: 7 additions & 2 deletions aeye/app/(nav)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import Navbar from "@/app/components/Navbar";
import { Container } from "@mui/material";
import OnlineUserSpeedDial from "@/app/onlineUserSpeedDial";

export default function NavLayout({ children }: { children: React.ReactNode }) {
return (
<>
<Navbar />
<div>{children}</div>
<Container>
<Navbar />
<div>{children}</div>
</Container>
<OnlineUserSpeedDial />
</>
);
}
Loading

0 comments on commit 3c83264

Please sign in to comment.