[ToyProject-Todomate] ํˆฌ๋‘๋ฉ”์ดํŠธ ํด๋ก  ํ”„๋กœ์ ํŠธ 35

2022. 2. 8. 15:44ใ†WEB Dev/ToyProject

728x90

728x90

๐Ÿ”ท CloneTodo โ˜‘ - Todomate Clone Project  |  Team CloneMate

 

CloneTodo : ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ์‚ฌ๋ž‘ํ•˜๋Š” ํˆฌ๋‘๋ฉ”์ดํŠธ๋ฅผ ํด๋ก ํ•˜์—ฌ ์›น ์„œ๋น„์Šค๋ฅผ ๋ฐฐํฌํ•ด๋ณด๋Š” ํ”„๋กœ์ ํŠธ

 


 

 

ํ•  ์ผ(to-do) ๋งŒ๋“ค๊ธฐ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋‹ค.

์ €๋ฒˆ์ฃผ ํ›„๋ฐ˜๋ถ€ํ„ฐ ์ง‘์ค‘ํ•˜๊ณ  ์žˆ๋Š”๋ฐ ๋ชฉํ‘œ์— ๋งž๊ฒŒ ํ•  ์ผ ๊ฐ’์„ ๋ฟŒ๋ ค ์ฃผ๋Š” ๊ฒƒ์ด ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ค์› ๋‹ค. 

์—ฌ๋Ÿฌ๋ชจ๋กœ ์ค‘์‹ฌ์ด ๋˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค๋ณด๋‹ˆ state ๋‹ค๋ฃจ๋Š” ๊ฒƒ๋„ ๋ญ”๊ฐ€ ๋” ๊ณ ๋„ํ™” ๋œ ๊ธฐ๋ถ„์ด๋‹ค.

 

 

 

1. Feed์— ๋ชฉํ‘œ ๋ฟŒ๋ฆฌ๊ธฐ

 

atom์œผ๋กœ ์„ค์ •ํ•ด ๋‘” goal ๋”๋ฏธ ๋ฐ์ดํ„ฐ๋ฅผ Main - Feed ์—์„œ๋„ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

 

 

export const goalsData = atom({ // ๋กœ๋”ฉ ์‹œ ๋ชจ๋“  ๋ชฉํ‘œ ๋ฟŒ๋ฆฌ๊ธฐ
  key: "goalsData",
  default: [
    {
      "goal_id": 0,
      "next_goal_id": 1,
      "title": "์ฒซ ๋ฒˆ์งธ ๋ชฉํ‘œ",
      "privacy": "PUB",
      "box_color": "",
      "title_color": "#3CB371"
    },
    {
      "goal_id": 1,
      "next_goal_id": 2,
      "title": "๋‘ ๋ฒˆ์งธ ๋ชฉํ‘œ",
      "privacy": "PRI",
      "box_color": "",
      "title_color": "#C71585"
    }
  ]
})

 

 

import { useRecoilState, useRecoilValue } from "recoil";

import { goalsData } from "../atoms/todoData";

export default function Feed() {


/* Hook ์„ ์–ธ ์‹œ์ž‘ */

/* atom ์‹œ์ž‘ */
    let goal = useRecoilValue(goalsData);
    
    
    return ();
    
 }

 

 

useRecoilState๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  useRecoilValue๋ฅผ ์“ด ์ด์œ ๋Š” ๋ชฉํ‘œ์˜ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ๋Š” ๋ณ„๋„์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ง„ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. 

useRecoilValue๋กœ ๊ฐ’๋งŒ ๋ฐ›์•„์™€ ๋ฟŒ๋ ค์ค€ ๋‹ค์Œ ํŽ˜์ด์ง€์— ๋งˆํฌ์—… ๋  ์ˆ˜ ์žˆ๋„๋ก goal.map์œผ๋กœ ํ™”๋ฉด์— ๋ฟŒ๋ ค์ค€๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํด๋ฆญํ•˜๋ฉด ๊ธฐ๋Šฅ๋“ค์ด ๋™์ž‘ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— <Button> UI ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ์‹ธ์ค€๋‹ค.

 

return (
        <Box className='feed-box'>
        <h2 className="feed-title">Feed</h2>
            <Box className="feed-goals-list-box">
                <List className="goals-list-wrap" >
                    {
                    goal.map((goal, index) => {
                    return ( <ListItem className="goals-listItem" id={goal.goal_id} key={goal.goal_id} > 
                        <Button className="goals-listItem-text-wrap" isselected={isGoalSelected[index]} id={goal.goal_id} onClick={(e)=>{(clickTodoHandler(e))}} data-index={index} >
                        <LibraryBooksIcon className="goals-listItem-icon" />
                                <ListItemText className="goals-listItem-text" id={goal.goal_id} name={goal.goal_id} sx={{ color:goal.title_color }}  >{goal.title}</ListItemText>
                            <ListItemText className="goals-listItem-add-icon" ><span>+</span></ListItemText>
                        </Button>
                        )
                     }   
                    )
                   }
                   
                </List>
            </Box>
        </Box>
        
        </>
   )

 

 

 

 

 

 

2. ๊ฐœ๋ณ„ ๋ชฉํ‘œ ํด๋ฆญํ–ˆ์„ ๋•Œ ํ•  ์ผ ์ž…๋ ฅ

 

ํ™”๋ฉด์— ๋ฟŒ๋ ค์ง„ ๋ชฉํ‘œ๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ํ•ด๋‹น ํด๋ฆญ ๋ถ€๋ถ„ ํ•˜๋‹จ์— textfield๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ณ 

ํ•  ์ผ์„ ์ž…๋ ฅํ–ˆ์„ ๋•Œ ์ƒˆ๋กœ์šด todo state๊ฐ€ ๋งŒ๋“ค์–ด ์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” InputBase๋ฅผ ์‚ฌ์šฉํ•  ๊ฑด๋ฐ ๋ชฉํ‘œ ๋งŒ๋“ค ๋•Œ์ฒ˜๋Ÿผ ๊ฒฝํ—˜ํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ readonly์†์„ฑ์— ๋”ฐ๋ผ true์ผ ๋•Œ๋Š” ์ˆ˜์ •์ด ์•ˆ๋˜๊ณ  false์ผ ๋•Œ๋Š” ์ˆ˜์ •์ด ๊ฐ€๋Šฅํ•˜๋„๋กํ•˜๊ธฐ ์œ„ํ•ด์„œ์ด๋‹ค. 

 

๋ชฉํ‘œ๋ฅผ ํด๋ฆญํ•˜๋ฉด CreateInput์ด๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ  ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ชฉํ‘œ ์•„๋ž˜ ๋ถ™์„ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ๋ชฉํ‘œ์— onClick ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ–ˆ๊ณ  CreateInput ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•˜๋‹จ์— ๋ณ„๋„๋กœ ๋งŒ๋“ค์—ˆ๋‹ค.

์ด CreateInput ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•  ์ผ์„ ์ž…๋ ฅํ•˜๊ณ  ์—”ํ„ฐ๋ฅผ ์น˜๋ฉด (submit ๋ฒ„ํŠผ ๋…ธ์ถœํ•˜์ง€ ์•Š์Œ)

์•„๋ž˜ createTodoState์— ๋‹ด๊ธฐ๊ณ  react form hook์„ ํ†ตํ•ด ์ œ์ถœ๋˜๊ฒŒ ๋œ๋‹ค.

 

// input ์ƒ์„ฑ
export function CreateInput(props) {


    let id = props.id;
    let defaultDate = new Date().toJSON().substring(0,10);
    const isGoalSelected = props.isGoalSelected;
    const setIsGoalSelected = props.setIsGoalSelected;
    console.log("isGoalSelected" , isGoalSelected)
    /* Hook ์„ ์–ธ ์‹œ์ž‘ */

    /* atom ์‹œ์ž‘ */
    const { register, handleSubmit, errors, watch } = useForm({ mode: "onChange" });
    let [todo, setTodo] = useRecoilState(todoData);
    let [createTodoState, setCreactTodoState] = useState({
        "todo_id": "",
        "goal_id": "", //๋ฌถ์—ฌ์žˆ๋Š” goal id
        "next_todo_id": "", //๋‹ค์Œ todo id (์ˆœ์„œ์ง€์ •์šฉ)
        "title": "",
        "date": "",
        "end_repeat_date": "", //๋ฐ˜๋ณต ์ข…๋ฃŒ ์ผ์ž. ๋ฐ˜๋ณต ์—†์œผ๋ฉด date ์™€ ๊ฐ’์ด ๊ฐ™๊ฑฐ๋‚˜ ์—†์Œ
        "repeat_days": {
            "sun": "N", //y ๋ฉด ์ผ์š”์ผ ๋ฐ˜๋ณต, n ์ด๋ฉด ๋ฐ˜๋ณต x
            "mon": "N",
            "tue": "N",
            "wed": "N",
            "thu": "N",
            "fri": "N",
            "sat": "N",
        },
        "check_yn" : "N" //๋‹ฌ์„ฑ์—ฌ๋ถ€
      });

    /* Hook ์„ ์–ธ ๋ */

    /* ํ•จ์ˆ˜ ์„ ์–ธ ์‹œ์ž‘ */

    //input ํ•จ์ˆ˜
    const onInputChange = (e) => {
        setCreactTodoState({...createTodoState, title: e.target.value})
        // console.log(createTodoState)
    }
    
    //todo ์ถ”๊ฐ€ ํ•จ์ˆ˜
    const addTodo = (data, id) => {
        const copy_todo_state = [...todo];
        //์ถ”๊ฐ€๋˜๋Š” state 'todo_id', 'goal_id', 'next_todo_id', 'date', 'end_repeat_date'
        // 'repeat_days'์™€ 'check_yn'์€ default ๊ฐ’ "N" , 
        data.todo_id = copy_todo_state.length;
        data.goal_id = parseInt(id); //key๋ฅผ ์œ„ํ•œ id ์ถ”๊ฐ€
        data.next_todo_id = copy_todo_state.length+1;
        data.date = defaultDate;
        data.end_repeat_date = defaultDate;
        copy_todo_state.push(data);
        setTodo(copy_todo_state, console.log(copy_todo_state))
    }
    
    // console.log(goal)//react-form-hook submit ํ•จ์ˆ˜
    const onSubmit =  (data) => { 
        console.log(data.goal_id)
        const index = data.goal_id
        const newArr = [...isGoalSelected];
        setIsGoalSelected(newArr[index] = false)
         setCreactTodoState(JSON.stringify(createTodoState));
         addTodo(createTodoState, index);
    }

    const onError = (error) => {
        console.log(error);
        };



    /* ํ•จ์ˆ˜ ์„ ์–ธ ๋ */

    return  (
            <Box className="goals-todo-input-create-Box">
                <form onSubmit={handleSubmit(onSubmit,onError)}>
                    <div className="goals-todo-input-create-wrap">
                    <CheckBoxOutlineBlankIcon className="goals-todo-input-create-check-icon"/>
                    <InputBase {...register("title")} id="todo-input" className="goals-todo-input-create-field"  placeholder="ํ•  ์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”." onChange={onInputChange} /> 
                    <InputBase {...register("goal_id")} id="todo-goal-id-input" className="todo-goal-create-id" type="hidden" value={id} /> 
                    <Button type="submit" className="goals-todo-input-btn"><MoreHorizIcon className="goals-todo-list-input-btn-icon" /></Button>
                    </div>
                </form>
            </Box>
            )
}

 

 

 

์—ฌ๊ธฐ์„œ ๋งต์œผ๋กœ ๋ฟŒ๋ ค์ง€๋Š” ํƒœ๊ทธ๋ฅผ ๊ฐœ๋ณ„ ์„ ํƒํ•˜๋Š” ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š”๋ฐ ๊ต‰์žฅํžˆ ์• ๋ฅผ ๋จน์—ˆ๋‹ค. 

๊ทธ๋Ÿฌ๋‹ค ์•„๋ž˜ ๋ธ”๋กœ๊ทธ์˜ ๋„์›€์„ ๊ต‰์žฅํžˆ ๋งŽ์ด ๋ฐ›์•˜๋‹ค.

 

 

 

 

react) mapํ•จ์ˆ˜ ํด๋ฆญ ์ด๋ฒคํŠธ์—์„œ ํ•œ๊ฐœ์˜ ๊ฐ’๋งŒ ์Šคํƒ€์ผ ๋ณ€๊ฒฝํ•˜๊ธฐ

์ด์ „์—๋Š” ํด๋ฆญ์‹œ ์Šคํƒ€์ผ๋งŒ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ž‘์„ฑํ•˜์˜€๋‹ค. ํ•˜์ง€๋งŒ ํ™ˆํŽ˜์ด์ง€๋ฅผ ๋ณด๋ฉด ํ•˜๋‚˜๋งŒ ํด๋ฆญํ–ˆ์„์‹œ ํ•˜๋‚˜๋งŒ ๋ถˆ์ด ๋“ค์–ด์˜จ๋‹ค๊ฑฐ๋‚˜ ํ•˜๋‚˜๋งŒ ์Šคํƒ€์ผ์ด ๋ณ€๊ฒฝ๋˜์–ด์•ผ ํ•œ๋‹ค.

velog.io

 

 

1. ๋ชฉํ‘œ ๋ฐฐ์—ด๋งŒํผ boolean ๊ฐ’์œผ๋กœ ์ฑ„์›Œ์ง„ ๋ฐฐ์—ด ์ƒ์„ฑํ•˜๊ธฐ

let [isGoalSelected, setIsGoalSelected] = useState(Array(goal.length).fill(false) );

๋กœ ๋ณ„๋„์˜ ๋ฐฐ์—ด์„ ํ•˜๋‚˜ ์ƒ์„ฑํ•ด์ค€๋‹ค. 

 

 

2. ๊ฐœ๋ณ„ ๋ชฉํ‘œ ๋ฒ„ํŠผ์— ํ• ๋‹นํ•  ์ด๋ฒคํŠธ ์ƒ์„ฑ

<Button className="goals-listItem-text-wrap" isselected={isGoalSelected[index]} id={goal.goal_id} onClick={(e)=>{(clickCreateTodoHandler(e))}} data-index={index} ></Button>
//๋ชฉํ‘œ ํด๋ฆญ ์‹œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
const clickCreateTodoHandler = (e) => {
    const index = e.currentTarget.dataset.index;
    const newArr = Array(goal.length).fill(false) ;
    newArr[index] = true;
    setIsGoalSelected(newArr)
}

์œ„์™€ ๊ฐ™์ด ๊ฐœ๋ณ„ ๋ชฉํ‘œ ๋ฒ„ํŠผ์— onClick ์ด๋ฒคํŠธ๋ฅผ ๋„ฃ์–ด์ค€ ๋‹ค์Œ ํด๋ฆญ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

๊ฐœ๋ณ„ Button์— dataset์œผ๋กœ ์„ค์ •๋œ index ๊ฐ’์„ ๋ฐ›์•„์˜ค๊ณ  (goal ๋ฐฐ์—ด์˜ ์ธ๋ฑ์Šค์™€ ๊ฐ™์Œ)

newArr๋กœ ๊ธฐ์กด ๋ฐฐ์—ด๊ณผ ๊ฐ™์€ ๋ฐฐ์—ด์„ ์ƒ์„ฑํ•œ ๋‹ค์Œ ํ•ด๋‹น ์ˆœ์„œ์˜ false ๊ฐ’์„ true๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค์Œ isGoalSelected ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด์ค€๋‹ค. (์ด ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ์“ธ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— recoil์— ๋„ฃ์ง€ ์•Š๊ณ  useState๋กœ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

3. ์กฐ๊ฑด๋ฌธ์œผ๋กœ CreateInput ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ

 

ํ•ด๋‹น ์ธ๋ฑ์Šค์˜ isGoalSelected๊ฐ€ true ์ธ ๊ฒฝ์šฐ CreateInput ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ๋„๋ก ์กฐ๊ฑด๋ฌธ์„ ์„ค์ •ํ•œ๋‹ค.

 

 {isGoalSelected[index] ? <CreateInput />: null}

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•ด๋‹น ์ธ๋ฑ์Šค ๋ถ€๋ถ„์˜ isGoalSelected์˜ ๊ฐ’์— ๋”ฐ๋ผ CreateInput ์ด ๋“ฑ์žฅํ•˜๊ฒŒ ๋œ๋‹ค.

 

 

 

 

3. todo state ๊ฐ ๋ชฉํ‘œ์— ๋งž๊ฒŒ ๋ฟŒ๋ฆฌ๊ธฐ

 

 

์ด์ œ ์ƒ์„ฑ๋œ ํ•  ์ผ ๋“ค์„ ๋ชฉํ‘œ ์•„๋ž˜์— ๋…ธ์ถœ๋˜๋„๋ก ํ•ด์ค€๋‹ค.

๋ชฉํ‘œ์˜ id์™€ ํ•  ์ผ์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” goal_id๊ฐ€ ๋งค์นญ์ด ๋˜์–ด์•ผ ํ•˜๊ณ  ํ•ด๋‹น ๋ชฉํ‘œ ์•„๋ž˜์— ํ•  ์ผ์ด ๋ฟŒ๋ ค์ ธ์•ผ ํ•œ๋‹ค.

 

๋ชฉํ‘œ์™€ ๋™์ผํ•˜๊ฒŒ recoil๋กœ ๋”๋ฏธ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค๊ณ  atom์œผ๋กœ ๊ฐ€์ง€๊ณ  ์˜จ๋‹ค.

 

import { todoData } from "../atoms/todoData";

export default function Feed() {


/* Hook ์„ ์–ธ ์‹œ์ž‘ */

/* atom ์‹œ์ž‘ */

    let todo = useRecoilValue(todoData);
    
    
    return (<>
        
        <Box className='feed-box'>
        <h2 className="feed-title">Feed</h2>
            <Box className="feed-goals-list-box">
                <List className="goals-list-wrap" >
                    {
                    goal.map((goal, index) => {
                    return ( <ListItem className="goals-listItem" id={goal.goal_id} key={goal.goal_id} > 
                        <Button className="goals-listItem-text-wrap" isselected={isGoalSelected[index]} id={goal.goal_id} onClick={(e)=>{(clickCreateTodoHandler(e))}} data-index={index} >
                        <LibraryBooksIcon className="goals-listItem-icon" />
                                <ListItemText className="goals-listItem-text" id={goal.goal_id} name={goal.goal_id} sx={{ color:goal.title_color }}  >{goal.title}</ListItemText>
                            <ListItemText className="goals-listItem-add-icon" ><span>+</span></ListItemText>
                        </Button>
                        {todo.map((todo,index)=>{
                          
                            return (
                                <Box className="goals-todo-input-list-Box" key={index} onClick={clickTodoModalHandler} data-key={index}>
                              
                                {goal.goal_id === parseInt(todo.goal_id) ? (<>
                                <div className="goals-todo-input-list-check-wrap">
                                    <CheckBoxOutlineBlankIcon className="goals-todo-list-input-check-icon"/>
                                    <InputBase key={`todo${index}`} inputProps={{readOnly: todoReadOnly[index].toString(),}} type="text" name={todo.title} id="todo-input" value={todo.title} onChange={todoEditEventHandler} className="goals-todo-list-input"></InputBase> 
                                    { 
                                
                                 }
                                </div>
                                <Button className="goals-todo-list-input-btn" ><MoreHorizIcon className="goals-todo-list-input-btn-icon" /></Button>
                                </>
                                ) : null} 
                                </Box>
                            )
                        })}
                        {isGoalSelected[index] ? <CreateInput id={goal.goal_id} isGoalSelected={isGoalSelected} setIsGoalSelected={setIsGoalSelected} />: null}
                    </ListItem>
                            )
                        })
                    }
                    

                </List>
            </Box>
        </Box>

        {/* ๋ชจ๋‹ฌ ์ƒ์„ฑ */}
         <TodoModal modalOpen={modalOpen} handleTodoModalClose={handleTodoModalClose}selectedTodo={selectedTodo} clickTodoEditHandler={clickTodoEditHandler} clickTodoDeleteHandler={clickTodoDeleteHandler} selectedInputIndex={selectedInputIndex}  />
        </>
    );
}

 

 

๊ทธ๋ฆฌ๊ณ  goal.map ์•ˆ์— todo๋ฅผ ๋ฟŒ๋ ค์ฃผ๊ฒŒ ๋˜๋Š”๋ฐ goal์˜ goal_id์™€ todo ์˜ goal_id๊ฐ€ ๊ฐ™์€ ๊ฒฝ์šฐ๋งŒ ์ถ”๋ ค์ ธ map์œผ๋กœ ๋ฟŒ๋ ค์งˆ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜์˜€๋‹ค. 

 

 

์ด์ œ ๊ฐ ํ•  ์ผ์„ ํด๋ฆญํ•˜๋ฉด ๋ชจ๋‹ฌ์ด ์ƒ์„ฑ๋˜๊ณ  ํ•ด๋‹น ๋ชจ๋‹ฌ์—์„œ 5๊ฐ€์ง€ ๊ธฐ๋Šฅ (์ˆ˜์ •, ์‚ญ์ œ, ๋‚ด์ผ ํ•˜๊ธฐ, ๋‚ ์งœ ๋ฐ”๊พธ๊ธฐ, ์ˆœ์„œ๋ณ€๊ฒฝ) ๋‹ค์„ฏ ๊ฐœ์˜ ๊ธฐ๋Šฅ์ด ๋™์ž‘ํ•˜๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.

 

๊ทธ๋Ÿฐ๋ฐ ์ด๊ฒŒ ์ƒ์„ฑ๋ณด๋‹ค ๋” ์–ด๋ ค์šด ๊ฑฐ ๊ฐ™์•„....

728x90