Commit 308d4f7f authored by Jiwon Yoon's avatar Jiwon Yoon
Browse files

Merge branch 'kimpen'

parents fa2af12b 1241f3e0
/node_modules
.env.development
.env.development.local
.env.local
.env
\ No newline at end of file
......@@ -17,8 +17,8 @@ import SearchPage from "./pages/SearchPage";
function App() {
return (
<div className="" style={{ backgroundColor: "black" }}>
<Router style={{ backgroundColor: "black" }}>
<div style={{ backgroundColor: "black" }}>
<Router>
<SubNav />
<Header />
<MainNav />
......
import axios from "axios";
import baseUrl from "../utils/baseUrl.js";
const getInfo = async () => {
const { data } = await axios.get(`${baseUrl}/api/cinema`)
return data
}
const edit = async (cinemaInfo) => {
const { data } = await axios.put(`${baseUrl}/api/cinema`, cinemaInfo)
return data
}
const cinemaApi = {
getInfo,
edit
}
export default cinemaApi
\ No newline at end of file
import axios from "axios";
import { baseUrl, TMDBUrl } from "../utils/baseUrl.js";
const getUpcomingfromTM = async () => {
const { data } = await axios.get(`${TMDBUrl}/upcoming?api_key=${process.env.REACT_APP_TMDB_API_KEY}&language=ko-KR`)
return data.results
const getAllfromTM = async () => {
const payload = {
params: {
pageNum: 1
}
}
const { data } = await axios.get(`${baseUrl}/api/movie/all`, payload)
return data
}
const getMoviesfromTM = async (cate) => {
const category = cate
const getMoviesfromTM = async (category) => {
const response = await axios.get(`${baseUrl}/api/movie/showmovies/${category}`)
console.log(response.data)
// console.log(response.data)
return response.data
}
const getMovieInfofromTM = async (id) => {
......@@ -39,14 +44,31 @@ const submit = async (movieId) => {
console.log("data==", data)
}
const remove = async (movieId) => {
const { data } = await axios.delete(`${baseUrl}/api/movie/${movieId}`)
return data
}
const search = async ({ type, keyword }) => {
const payload = {
params: {
keyword
}
}
const { data } = await axios.get(`${baseUrl}/api/movie/search/${type}`, payload)
return data
}
const movieApi = {
getUpcomingfromTM,
getAllfromTM,
getMoviesfromTM,
getMovieInfofromTM,
getImagesfromTM,
getCreditsfromTM,
getVideosfromTM,
submit
submit,
remove,
search,
}
export default movieApi
\ No newline at end of file
import { useState } from "react";
import KakaoMap from "../KakaoMap";
import { useState, useEffect } from "react";
import cinemaApi from "../../apis/cinema.api.js";
import catchErrors from "../../utils/catchErrors.js";
import styles from "./admin.module.scss";
const INIT_CINEMAINFO = {
cinemaName: "",
transportation: "",
parking: "",
address: ""
}
const CinemaEdit = () => {
const [cinemaInfo, setCinemaInfo] = useState({ cinema: "", transportation: "", parking: "", keyword: "", address: "" })
const [search, setSearch] = useState("")
const [cinemaInfo, setCinemaInfo] = useState(INIT_CINEMAINFO)
const [error, setError] = useState("")
useEffect(() => {
getInfo()
}, [])
function handleChange(e) {
const { name, value } = e.target
setCinemaInfo({ ...cinemaInfo, [name]: value })
}
async function getInfo() {
try {
setError("")
const info = await cinemaApi.getInfo()
if (info) setCinemaInfo(info)
else setCinemaInfo(INIT_CINEMAINFO)
} catch (error) {
catchErrors(error, setError)
}
}
async function handleSubmit() {
try {
setError("")
await cinemaApi.edit(cinemaInfo)
window.location.reload()
} catch (error) {
catchErrors(error, setError)
}
}
return (
<>
<h2 className="border-bottom border-2 text-center pb-2 me-2">현재 영화관 정보</h2>
<input type="text" className={`form-control mb-2 ${styles.shadowNone}`} id="cinema" name="cinema" onChange={handleChange} />
<div className="mb-3">
<label for="cinemaName" className="form-label">영화관 이름</label>
<input type="text" className={`form-control mb-2 ${styles.shadowNone}`} id="cinemaName" name="cinemaName" value={cinemaInfo.cinemaName} onChange={handleChange} />
<p> 상영관 : 8개관 | 좌석 : 1,282</p>
</div>
<div className="mb-3">
<label for="transportation" className="form-label">대중교통 안내</label>
<textarea className={`form-control ${styles.shadowNone} ${styles.textarea}`} rows="7" id="transportation" name="transportation" onChange={handleChange}></textarea>
<textarea className={`form-control ${styles.shadowNone} ${styles.textarea}`} rows="7" id="transportation" name="transportation" value={cinemaInfo.transportation} onChange={handleChange}></textarea>
</div>
<div className="mb-3">
<label for="parking" className="form-label">자가용/주차안내</label>
<textarea className={`form-control ${styles.shadowNone} ${styles.textarea}`} rows="7" id="parking" name="parking" onChange={handleChange}></textarea>
<textarea className={`form-control ${styles.shadowNone} ${styles.textarea}`} rows="7" id="parking" name="parking" value={cinemaInfo.parking} onChange={handleChange}></textarea>
</div>
<label for="keyword" className="form-label">지도보기</label>
<div className="input-group mb-3">
<span className="input-group-text" id="address"><i className="bi bi-geo-alt-fill"></i></span>
<input type="text" className={`form-control ${styles.shadowNone}`} id="address" name="address" aria-label="map" aria-describedby="address" onChange={handleChange} value={cinemaInfo.address} />
</div>
<div className="input-group mb-3">
<input type="text" className={`form-control ${styles.shadowNone}`} id="keyword" name="keyword" aria-label="map" aria-describedby="currentMap" onChange={handleChange} />
<button className="btn btn-dark" type="button" id="currentMap" onClick={() => setSearch(cinemaInfo.keyword)}><i className="bi bi-search"></i></button>
<input type="text" className={`form-control ${styles.shadowNone}`} id="address" name="address" value={cinemaInfo.address} onChange={handleChange} value={cinemaInfo.address} />
</div>
<div className="d-flex justify-content-center mb-5">
<KakaoMap keyword={search} cinemaInfo={cinemaInfo} setCinemaInfo={setCinemaInfo} />
<div className="d-grid gap-2 mb-5">
<button type="submit" className={`btn btn-dark shadow-none ${styles.customBtn}`} onClick={handleSubmit}>수정</button>
</div>
</>
)
......
......@@ -4,10 +4,9 @@ import MovieTable from "../MovieTable";
import Pagination from "../Pagination";
import movieApi from "../../apis/movie.api.js";
import catchErrors from "../../utils/catchErrors.js";
import styles from "./admin.module.scss";
const MovieEdit = () => {
const [search, setSearch] = useState({ kind: "", keyword: "" })
const [search, setSearch] = useState({ type: "admin", keyword: "" })
const [movieList, setMovieList] = useState([])
const [error, setError] = useState("")
......@@ -18,7 +17,7 @@ const MovieEdit = () => {
async function getMovieList() {
try {
setError("")
const getMovieList = await movieApi.getUpcomingfromTM()
const getMovieList = await movieApi.getAllfromTM()
setMovieList(getMovieList)
} catch (error) {
catchErrors(error, setError)
......@@ -28,6 +27,8 @@ const MovieEdit = () => {
async function searchMovie() {
try {
setError("")
const findMovie = await movieApi.search(search)
setMovieList(findMovie)
} catch (error) {
catchErrors(error, setError)
}
......@@ -35,17 +36,11 @@ const MovieEdit = () => {
return (
<>
{console.log("search==",search)}
<div className="d-flex justify-content-md-end justify-content-center mb-3">
<Search type="admin" search={search} setSearch={setSearch} handleClick={searchMovie} />
<Search search={search} setSearch={setSearch} handleClick={searchMovie} />
</div>
<MovieTable movieList={movieList} />
<div className="d-flex flex-wrap">
<Pagination />
<div className="d-flex justify-content-end col-12 col-md-4 my-2">
<button type="button" className={`btn btn-dark ${styles.customBtn}`}>등록</button>
</div>
</div>
</>
)
}
......
......@@ -29,8 +29,6 @@
}
.customBtn {
width: 5em;
&:hover {
border-color: #FEDC00;
background-color: #FEDC00;
......
import { useState, useEffect, useRef } from "react";
import styles from "./kakao-map.module.scss";
const { kakao } = window;
const KakaoMap = ({ keyword, cinemaInfo, setCinemaInfo }) => {
const kakaoMapDiv = useRef(null)
const menu = useRef(null)
const searchList = useRef(null)
const page = useRef(null)
const [places, setPlaces] = useState([])
let markers = []
useEffect(() => {
const container = kakaoMapDiv.current
const options = {
center: new kakao.maps.LatLng(33.450701, 126.570667),
level: 3
};
const map = new kakao.maps.Map(container, options);
const ps = new kakao.maps.services.Places();
const infowindow = new kakao.maps.InfoWindow({ zIndex: 1 });
searchPlaces(keyword)
// 키워드 검색을 요청하는 함수입니다
function searchPlaces(keyword) {
if (!keyword.replace(/^\s+|\s+$/g, '')) {
alert('키워드를 입력해주세요.');
return false
}
// 장소검색 객체를 통해 키워드로 장소검색을 요청합니다
ps.keywordSearch(keyword, placesSearchCB);
}
// 장소검색이 완료됐을 때 호출되는 콜백함수 입니다
function placesSearchCB(data, status, pagination) {
if (status === kakao.maps.services.Status.OK) {
displayPlaces(data);
displayPagination(pagination); // 페이지 번호를 표출합니다
} else if (status === kakao.maps.services.Status.ZERO_RESULT) {
alert('검색 결과가 존재하지 않습니다.');
return
} else if (status === kakao.maps.services.Status.ERROR) {
alert('검색 결과 중 오류가 발생했습니다.');
return
}
}
// 검색 결과 목록과 마커를 표출하는 함수입니다
function displayPlaces(places) {
let listEl = searchList.current,
menuEl = menu.current,
fragment = document.createDocumentFragment(),
bounds = new kakao.maps.LatLngBounds();
for (let i = 0; i < places.length; i++) {
// 마커를 생성하고 지도에 표시합니다
let placePosition = new kakao.maps.LatLng(places[i].y, places[i].x),
itemEl = getListItem(i, places[i]); // 검색 결과 항목 Element를 생성합니다
displayMarker(places[i], itemEl)
// 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해 LatLngBounds 객체에 좌표를 추가합니다
bounds.extend(placePosition);
fragment.appendChild(itemEl);
}
// 검색결과 항목들을 검색결과 목록 Elemnet에 추가합니다
listEl.appendChild(fragment);
menuEl.scrollTop = 0;
// 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다
map.setBounds(bounds);
}
function displayMarker(place, itemEl) {
let marker = new kakao.maps.Marker({
map: map,
position: new kakao.maps.LatLng(place.y, place.x),
})
kakao.maps.event.addListener(marker, 'mouseover', function () {
infowindow.setContent('<div style="padding:5px;font-size:12px;">' + place.place_name + '</div>')
infowindow.open(map, marker)
})
kakao.maps.event.addListener(marker, 'mouseout', function () {
infowindow.close();
})
itemEl.onmouseover = function () {
itemEl.style.background = 'rgba(0, 0, 0, 0.075)';
itemEl.style.cursor = 'pointer';
infowindow.setContent('<div style="padding:5px;font-size:12px;">' + place.place_name + '</div>')
infowindow.open(map, marker)
};
itemEl.onmouseout = function () {
itemEl.style.background = '#fff';
infowindow.close();
};
itemEl.onclick = function () {
setCinemaInfo({ ...cinemaInfo, address: place.address_name })
}
}
}, [keyword])
// 검색결과 목록 하단에 페이지번호를 표시는 함수입니다
function displayPagination(pagination) {
let paginationEl = page.current,
fragment = document.createDocumentFragment(),
i;
for (i = 1; i <= pagination.last; i++) {
// let el = <a>{i}</a>
let el = document.createElement('a');
// el.href = "#";
el.innerHTML = i;
if (i === pagination.current) {
el.className = 'on';
} else {
el.onclick = (function (i) {
return function () {
pagination.gotoPage(i);
}
})(i);
}
fragment.appendChild(el);
}
paginationEl.appendChild(fragment);
}
// 검색결과 항목을 Element로 반환하는 함수입니다
function getListItem(index, places) {
let el = document.createElement('div'),
itemStr = '<div className="info">' +
' <h5>' + places.place_name + '</h5>';
if (places.road_address_name) {
itemStr += ' <span>' + places.road_address_name + '</span>' +
' <span className="jibun gray">' + places.address_name + '</span>';
} else {
itemStr += ' <span>' + places.address_name + '</span>';
}
itemStr += ' <span className="tel">' + places.phone + '</span>' +
'</div>';
el.innerHTML = itemStr;
el.className = 'item';
return el;
}
return (
<>
<div ref={kakaoMapDiv} style={{ width: "500px", height: "400px" }}></div>
<div ref={menu} className={`${styles.menu} bg-white`}>
<div ref={searchList}></div>
<div ref={page}></div>
</div>
</>
)
}
export default KakaoMap
\ No newline at end of file
export { default } from "./KakaoMap"
\ No newline at end of file
// .menu {
// position:absolute;
// top:0;
// left:0;
// bottom:0;
// width:250px;
// margin:10px 0 30px 10px;
// padding:5px;
// overflow-y:auto;
// background:rgba(255, 255, 255, 0.7);
// z-index: 1;
// font-size:12px;
// border-radius: 10px;
// }
\ No newline at end of file
import { useState, useEffect } from "react"
import { Link } from 'react-router-dom'
import styles from './movie-card.module.scss'
const MovieCard = ({ list }) => {
const [movieList, setMovieList] = useState(list)
useEffect(() => {
setMovieList(list)
}, [list])
return (
<>
{movieList.map(movie => (
<div className="card h-100" style={{ backgroundColor: "black" }}>
<Link to={{
pathname: `/movie/${movie.id}`,
state: {
...movie
}
}} className={`${styles.layer}`} >
<img src={`https://image.tmdb.org/t/p/original${movie.poster_path}`} className={`card-img-top rounded ${styles.poster}`} alt="Movie Poster" />
<div className={`${styles.description}`}>{movie.overview}</div>
</Link>
<div className="card-body text-light">
<marquee className={`h2 card-title text-center ${styles.title}`}>{movie.title}</marquee>
<p className={`card-text text-center ${styles.txt}`}>예매율: {movie.ticket_sales}0% | {movie.runtime}</p>
<p className="card-text text-center"><small className="text-muted">{movie.release_date} 개봉</small></p>
</div>
<Link to={{
pathname:`/ticket`,
state: {movieId:movie.id,}
}} className="text-center">
<button className="btn btn-warning">예매하기</button>
</Link>
</div>
))}
</>
)
}
export default MovieCard
\ No newline at end of file
export { default } from "./MovieCard.js"
\ No newline at end of file
import { useState, useEffect } from 'react'
import MovieCard from './MovieCard/index.js'
import movieApi from '../apis/movie.api.js'
import catchErrors from '../utils/catchErrors.js'
const MovieChart = () => {
const [TMDB_TopRated_Data, setTMDB_TopRated_Data] = useState([])
const [error, setError] = useState("")
const category = "popular"
useEffect(() => {
getTMDB_TopRated()
}, [])
async function getTMDB_TopRated() {
try {
setError("")
const data = await movieApi.getMoviesfromTM(category)
console.log("sdad==", data)
setTMDB_TopRated_Data([...data])
} catch (error) {
catchErrors(error, setError)
}
}
return (
<>
{TMDB_TopRated_Data.length !== 0 ?
<div className="row row-cols-1 row-cols-md-4 g-4">
<MovieCard list={TMDB_TopRated_Data} />
</div>
: <h2 className="text-white text-center my-5">영화정보를 로딩할 없습니다.</h2>
}
</>
)
}
export default MovieChart
\ No newline at end of file
import { useState, useEffect } from 'react'
import movieApi from '../../apis/movie.api.js'
import { Link } from 'react-router-dom';
import styles from "./movieChart.module.scss"
const MovieChart = () => {
const [TMDB_TopRated_Data, setTMDB_TopRated_Data] = useState([])
const category="popular"
useEffect(() => {
getTMDB_TopRated()
}, [])
async function getTMDB_TopRated() {
try {
const data = await movieApi.getMoviesfromTM(category)
console.log(data)
setTMDB_TopRated_Data([...data])
} catch (error) {
}
}
return (
<div class="row row-cols-1 row-cols-md-4 g-4">
{console.log(TMDB_TopRated_Data)}
{TMDB_TopRated_Data
?
TMDB_TopRated_Data.map(movie => (
<div className="card h-100 " style={{ backgroundColor: "black" }}>
<Link to={{
pathname: `/movie/${movie.id}`,
state: {...movie}
}} className={`${styles.layer}`} >
<img src={`https://image.tmdb.org/t/p/original${movie.poster_path}`} className={`card-img-top rounded ${styles.poster}`} alt="Movie Poster" />
<div className={`${styles.description}`}>{movie.overview}</div>
</Link>
<div className="card-body text-light">
<div onmouseover="this.stop()" className={`h2 card-title text-center ${styles.title} ${styles.txt}`}>{movie.title}</div>
<p className={`card-text text-center ${styles.txt}`}>예매율: {movie.ticket_sales}0% | {movie.runtime}</p>
<p className="card-text text-center"><small className="text-muted">{movie.release_date} 개봉</small></p>
</div>
<Link to={{
pathname:`/ticket`,
state: {movieId:movie.id,}
}} className="text-center">
<button className="btn btn-warning">예매하기</button>
</Link>
</div>
))
: <div>영화정보를 로딩할 없습니다.</div>
}
</div>
)
}
export default MovieChart
\ No newline at end of file
export { default } from './MovieChart'
\ No newline at end of file
import { useState, useEffect } from 'react'
import movieApi from "../apis/movie.api.js"
import MovieCard from "./MovieCard/index.js"
import catchErrors from '../utils/catchErrors.js'
const MovieComming = () => {
const [TMDB_UpComming_Data, setTMDB_UpComming_Data] = useState([])
const [error, setError] = useState("")
const category = "upcoming"
useEffect(() => {
getTMDB_UpComming()
}, [])
async function getTMDB_UpComming() {
try {
setError("")
const response = await movieApi.getfromTM(category)
setTMDB_UpComming_Data([...response])
} catch (error) {
catchErrors(error, setError)
}
}
return (
<>
{TMDB_UpComming_Data.length !== 0 ?
<div className="row row-cols-1 row-cols-md-4 g-4">
<MovieCard list={TMDB_UpComming_Data} />
</div>
: <h2 className="text-white text-center my-5">영화정보를 로딩할 없습니다.</h2>
}
</>
)
}
export default MovieComming
\ No newline at end of file
import { useState, useEffect } from 'react'
import movieApi from '../../apis/movie.api.js'
import { Link } from 'react-router-dom';
import styles from "./movieComming.module.scss"
const MovieComming = () => {
const [TMDB_TopRated_Data, setTMDB_TopRated_Data] = useState()
const category="upcoming"
useEffect(() => {
getTMDB_TopRated()
}, [])
async function getTMDB_TopRated() {
try {
const data = await movieApi.getfromTM(category)
console.log(data)
setTMDB_TopRated_Data(data)
} catch (error) {
}
}
return (
<div class="row row-cols-1 row-cols-md-4 g-4">
{console.log(TMDB_TopRated_Data)}
{TMDB_TopRated_Data
?
TMDB_TopRated_Data.map(movie => (
<div className="card h-100" style={{ backgroundColor: "black" }}>
<Link to={{
pathname: `/movie/${movie.id}`,
state: {...movie}
}} className={`${styles.layer}`} >
<img src={`https://image.tmdb.org/t/p/original${movie.poster_path}`} className={`card-img-top rounded ${styles.poster}`} alt="Movie Poster" />
<div className={`${styles.description}`}>{movie.overview}</div>
</Link>
<div className="card-body text-light">
<marquee onmouseover="this.stop()" className={`h2 card-title text-center ${styles.title}`}>{movie.title}</marquee>
<p className="card-text text-center">예매율: {movie.ticket_sales}0% | {movie.runtime}</p>
<p className="card-text text-center"><small className="text-muted">{movie.release_date} 개봉</small></p>
</div>
<Link to={{
pathname:`/ticket`,
state: {movieId:movie.id}
}}>
<button className="btn btn-warning">예매하기</button>
</Link>
</div>
))
: <div>영화정보를 로딩할 없습니다.</div>
}
</div>
)
}
export default MovieComming
\ No newline at end of file
export { default } from './MovieComming'
\ No newline at end of file
.layer{
position: relative;
}
// .poster{
// opacity: 1;
// &:hover{
// opacity: 0.3;
// }
// }
.description{
position: absolute;
top: 0%;
left: 0%;
width: 100%;
height: 100%;
opacity: 0;
background-color:rgba(0, 0, 0, 0.6);
text-align: center;
color:white;
//ellipsis
text-overflow: ellipsis;
white-space: pre-line;
display: -webkit-box;
-webkit-line-clamp: 9;
-webkit-box-orient: vertical;
overflow: hidden;
&:hover{
opacity: 1;
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment