오늘의 목표

수행 내역

import { Link } from 'react-router-dom';
import { useLogBook } from '../../context/LogBookContext';

const GridItemTop = ({ item, type, handleClickDelete }) => {
    const { setElements } = useLogBook();

    return (
        <div className='grid-item-top'>
            <div className='grid-item-text'>
                <img src={`/img/icon-${type}.png`} alt='' />
            </div>
            <button
                className='grid-item-delete'
                onClick={(e) => {
                    e.stopPropagation();
                    setElements((prev) => prev.filter((element) => element.i !== item.i));
                    handleClickDelete(item.i);
                }}
            >
                {/* <svg xmlns='<http://www.w3.org/2000/svg>' viewBox='0 0 640 640'>
                    <path d='M262.2 48C248.9 48 236.9 56.3 232.2 68.8L216 112L120 112C106.7 112 96 122.7 96 136C96 149.3 106.7 160 120 160L520 160C533.3 160 544 149.3 544 136C544 122.7 533.3 112 520 112L424 112L407.8 68.8C403.1 56.3 391.2 48 377.8 48L262.2 48zM128 208L128 512C128 547.3 156.7 576 192 576L448 576C483.3 576 512 547.3 512 512L512 208L464 208L464 512C464 520.8 456.8 528 448 528L192 528C183.2 528 176 520.8 176 512L176 208L128 208zM288 280C288 266.7 277.3 256 264 256C250.7 256 240 266.7 240 280L240 456C240 469.3 250.7 480 264 480C277.3 480 288 469.3 288 456L288 280zM400 280C400 266.7 389.3 256 376 256C362.7 256 352 266.7 352 280L352 456C352 469.3 362.7 480 376 480C389.3 480 400 469.3 400 456L400 280z' />
                </svg> */}
            </button>
        </div>
    );
};

const GridContent = ({ type, content }) => {
    switch (type) {
        case 'image':
            return content ? (
                <img src={`https://${content.replace(/https?:\\/\\//, '')}`} alt='' />
            ) : (
                <p className='default-text'>사진을 첨부하기 위해 클릭</p>
            );
        case 'link':
            return content ? (
                <Link to={`https://${content.replace(/https?:\\/\\//, '')}`}>{content}</Link>
            ) : (
                <p className='default-text'>내용을 입력하기 위해 클릭</p>
            );
        default:
            return content ? content : <p className='default-text'>내용을 입력하기 위해 클릭</p>;
    }
};

const BlogLayoutItem = ({ item, handleClickDelete, enableModal }) => {
    const { setClickedItem, elements } = useLogBook();
    const itemType = item.i.split('-')[0];
    const itemContent = elements.find((element) => element.i === item.i)?.content;

    return (
        <div
            className={item.i}
            onClick={() => {
                setClickedItem(item);
                enableModal();
            }}
        >
            <GridItemTop item={item} type={itemType} handleClickDelete={handleClickDelete} />
            <div className={`grid-${itemType}-content`}>
                <GridContent type={itemType} content={itemContent} />
            </div>
        </div>
    );
};

export default BlogLayoutItem;

import { useState } from 'react';
import ReactGridLayout from 'react-grid-layout';
import { useLogBook } from '../../context/LogBookContext';
import BlogLayoutItem from './BlogLayoutItem';

const BlogGridLayout = ({ enableModal }) => {
    const [layout, setLayout] = useState([]);
    const [newItemCounter, setNewItemCounter] = useState(0);
    const { draggingItem, setElements } = useLogBook();

    const handleClickDelete = (i) => {
        setLayout((prev) => prev.filter((item) => item.i !== i));
    };

    const onLayoutChange = (newLayout) => {
        setLayout(newLayout);
    };

    const onDrop = (currentLayout, droppedItemProps, e) => {
        if (!draggingItem) return;

        const { x, y } = droppedItemProps;
        const newId = `${draggingItem.className}-${newItemCounter}`;
        const w = draggingItem.w;
        const h = draggingItem.h;

        const isOverlap = layout.some(
            (item) => item.x < x + w && item.x + item.w > x && item.y < y + h && item.y + item.h > y
        );

        const newItem = {
            i: newId,
            x: x,
            y: y,
            w: w,
            h: h,
        };

        let finalLayout;
        if (isOverlap) {
            const newLayout = layout.map((item) => {
                // 드롭된 아이템의 x, y와 겹치는 아이템들을 아래로 이동
                if (item.x < x + w && item.x + item.w > x && item.y >= y) {
                    return { ...item, y: item.y + h };
                }
                return item;
            });
            finalLayout = [...newLayout, newItem];
        } else {
            finalLayout = [...layout, newItem];
        }

        setLayout(finalLayout);
        setElements((prev) => [...prev, { i: newId, content: null }]);
        setNewItemCounter((prevCounter) => prevCounter + 1);
    };

    const renderGridItems = () => {
        return layout.map((item) => {
            if (item.i !== '__dropping-elem__') {
                return (
                    <div key={item.i}>
                        <BlogLayoutItem
                            item={item}
                            handleClickDelete={handleClickDelete}
                            enableModal={enableModal}
                        />
                    </div>
                );
            }
        });
    };

    return (
        <div className='blog-area'>
            <ReactGridLayout
                layout={layout}
                cols={5}
                rowHeight={80}
                width={900}
                onDrop={onDrop}
                isDroppable={true}
                isDraggable={false}
                droppingItem={{
                    i: '__dropping-elem__',
                    w: draggingItem ? draggingItem.w : 1,
                    h: draggingItem ? draggingItem.h : 1,
                }}
                compactType={'vertical'}
                onLayoutChange={onLayoutChange}
            >
                {renderGridItems()}
            </ReactGridLayout>
        </div>
    );
};

export default BlogGridLayout;

문제 및 이슈

참고자료

지원요청

내일 계획