React - 린캔버스 수정 1

김명원·2025년 1월 12일
0

learnReact

목록 보기
20/26

린캔버스 수정 1 🖋️✨

이번 포스트에서는 린캔버스 애플리케이션의 데이터 관리와 사용자 인터페이스 향상을 위해 다양한 수정 사항을 구현했습니다. db.json 데이터베이스 파일의 구조 변경부터 시작하여, React 컴포넌트의 세부 기능 추가 및 개선에 이르기까지, 각 단계별로 코드 변경 사항을 상세히 설명하며 이러한 변경이 애플리케이션에 미치는 영향을 분석하겠습니다.


📂 db.json 파일 수정

db.json 파일은 린캔버스 애플리케이션의 핵심 데이터를 저장합니다. 이번 수정에서는 기존의 단순한 캔버스 데이터 구조를 확장하여, 린캔버스의 다양한 섹션을 세부적으로 관리할 수 있도록 변경했습니다.

data/db.json 파일

{
  "canvases": [
    {
      "id": 1,
      "title": "친환경 도시 농업",
      "lastModified": "2023-06-15",
      "category": "농업"
    },
    {
      "id": 2,
      "title": "AI 기반 건강 관리 앱",
      "lastModified": "2023-06-10",
      "category": "헬스케어"
    },
    {
      "id": 3,
      "title": "온디맨드 물류 서비스",
      "lastModified": "2023-06-05",
      "category": "물류"
    },
    {
      "id": 4,
      "title": "VR 가상 여행 서비스",
      "lastModified": "2023-06-01",
      "category": "여행"
    },
    {
      "id": 5,
      "title": "혁신적인 스마트홈 솔루션!!",
      "lastModified": "2023-08-22",
      "category": "신규",
      "problem": {
        "notes": [
          {
            "id": "p1",
            "content": "기존 스마트홈 시스템의 복잡성",
            "color": "bg-yellow-300"
          },
          {
            "id": "p2",
            "content": "높은 초기 설치 비용",
            "color": "bg-pink-300"
          },
          {
            "id": "p3",
            "content": "다양한 기기 간 호환성 문제",
            "color": "bg-blue-300"
          }
        ]
      },
      "customerSegments": {
        "notes": [
          {
            "id": "cs1",
            "content": "30-50대 중산층 가정",
            "color": "bg-blue-300"
          },
          {
            "id": "cs2",
            "content": "기술에 관심 있는 밀레니얼 세대",
            "color": "bg-yellow-300"
          },
          {
            "id": "cs3",
            "content": "에너지 절약에 관심 있는 환경 의식적 소비자",
            "color": "bg-pink-300"
          }
        ]
      },
      "valueProposition": {
        "notes": [
          {
            "id": "vp1",
            "content": "직관적이고 사용하기 쉬운 인터페이스",
            "color": "bg-pink-300"
          },
          {
            "id": "vp2",
            "content": "저렴한 초기 비용과 월 구독 모델",
            "color": "bg-yellow-300"
          },
          {
            "id": "vp3",
            "content": "모든 주요 스마트홈 기기와 호환",
            "color": "bg-blue-300"
          }
        ]
      },
      "solution": {
        "notes": [
          {
            "id": "s1",
            "content": "AI 기반 자동화 시스템",
            "color": "bg-yellow-300"
          },
          {
            "id": "s2",
            "content": "클라우드 기반 중앙 제어 시스템",
            "color": "bg-blue-300"
          },
          {
            "id": "s3",
            "content": "모듈식 설계로 쉬운 확장성",
            "color": "bg-pink-300"
          }
        ]
      },
      "unfairAdvantage": {
        "notes": [
          {
            "id": "ua1",
            "content": "특허받은 AI 알고리즘",
            "color": "bg-pink-300"
          },
          {
            "id": "ua2",
            "content": "주요 스마트홈 기기 제조업체와의 독점 파트너십",
            "color": "bg-yellow-300"
          }
        ]
      },
      "channels": {
        "notes": [
          {
            "id": "ch1",
            "content": "온라인 직접 판매",
            "color": "bg-yellow-300"
          },
          {
            "id": "ch2",
            "content": "전문 설치 업체 네트워크",
            "color": "bg-blue-300"
          },
          {
            "id": "ch3",
            "content": "대형 전자제품 소매점",
            "color": "bg-pink-300"
          }
        ]
      },
      "keyMetrics": {
        "notes": [
          {
            "id": "km1",
            "content": "월간 활성 사용자 수",
            "color": "bg-blue-300"
          },
          {
            "id": "km2",
            "content": "고객 유지율",
            "color": "bg-yellow-300"
          },
          {
            "id": "km3",
            "content": "평균 에너지 절감률",
            "color": "bg-pink-300"
          }
        ]
      },
      "costStructure": {
        "notes": [
          {
            "id": "cs1",
            "content": "하드웨어 개발 및 생산 비용",
            "color": "bg-pink-300"
          },
          {
            "id": "cs2",
            "content": "소프트웨어 개발 및 유지보수",
            "color": "bg-blue-300"
          },
          {
            "id": "cs3",
            "content": "마케팅 및 고객 지원",
            "color": "bg-yellow-300"
          }
        ]
      },
      "revenueStreams": {
        "notes": [
          {
            "id": "rs1",
            "content": "하드웨어 판매",
            "color": "bg-yellow-300"
          },
          {
            "id": "rs2",
            "content": "월간 구독료",
            "color": "bg-pink-300"
          },
          {
            "id": "rs3",
            "content": "프리미엄 기능 업그레이드",
            "color": "bg-blue-300"
          }
        ]
      },
      "existingAlternatives": {
        "notes": [
          {
            "id": "ea1",
            "content": "기존 홈 오토메이션 시스템",
            "color": "bg-yellow-300"
          },
          {
            "id": "ea2",
            "content": "개별 스마트 기기 사용",
            "color": "bg-pink-300"
          }
        ]
      },
      "highLevelConcept": {
        "notes": [
          {
            "id": "hlc1",
            "content": "모든 가정을 위한 AI 기반 스마트홈",
            "color": "bg-blue-300"
          }
        ]
      },
      "earlyAdopters": {
        "notes": [
          {
            "id": "ea1",
            "content": "테크 얼리어답터",
            "color": "bg-blue-300"
          },
          {
            "id": "ea2",
            "content": "에너지 절약 열성 지지자",
            "color": "bg-pink-300"
          }
        ]
      }
    }
  ]
}

코드 설명:

  • 캔버스 데이터 구조 확장: 기존의 단순한 캔버스 데이터에 다양한 섹션(problem, customerSegments, valueProposition, 등)을 추가하여 린캔버스의 각 요소를 세부적으로 관리할 수 있도록 했습니다. 이는 린캔버스의 각 구성 요소를 더 명확하게 구분하고, 각 섹션별로 관련된 노트를 체계적으로 관리할 수 있게 합니다.
  • 노트 배열: 각 섹션은 notes 배열을 포함하며, 각 노트는 id, content, color 속성을 가집니다. id는 노트를 고유하게 식별하는 데 사용되며, content는 노트의 내용을, color는 노트의 배경색을 지정하여 시각적인 구분을 가능하게 합니다. 이는 사용자 인터페이스에서 노트의 시각적 구분과 관리의 용이성을 높입니다.
  • 색상 코드: color 속성을 통해 각 노트의 배경색을 지정하여 UI에서 시각적으로 구분할 수 있게 했습니다. 이는 사용자 경험을 향상시키며, 정보의 종류나 중요도에 따라 색상을 다르게 설정함으로써 노트의 가독성과 인지도를 높입니다.

추가 설명:

데이터 구조의 확장은 린캔버스 애플리케이션의 유연성과 확장성을 크게 향상시킵니다. 각 섹션별로 세부적인 노트를 추가함으로써, 사용자들이 보다 구체적이고 체계적으로 아이디어를 정리할 수 있게 되었습니다. 또한, 색상 코드를 활용하여 정보의 구분과 시각적 효과를 높여 사용자 경험을 향상시켰습니다. 이러한 구조 변경은 향후 기능 확장이나 데이터 관리 측면에서 큰 도움이 될 것입니다.


src/api/canvas.js 수정

React 애플리케이션에서 API 요청을 처리하는 canvas.js 파일에 새로운 함수를 추가하여 캔버스 데이터를 업데이트할 수 있도록 했습니다.

src/api/canvas.js

import { canvases } from './http';
import { v4 as uuidv4 } from 'uuid';
import dayjs from 'dayjs';

export function getCanvases(params) {
  const payload = Object.assign(
    {
      _sort: 'lastModified',
      _order: 'desc',
    },
    params,
  );
  return canvases.get('/', { params: payload });
}

export function createCanvas() {
  const newCanvas = {
    title: uuidv4().substring(0, 4) + '_새로운 린 캔버스',
    lastModified: dayjs().format('YYYY-MM-DD HH:mm:ss'),
    category: '신규',
  };
  return canvases.post('/', newCanvas);
}

export async function deleteCanvas(id) {
  await canvases.delete(`/${id}`);
}

export async function getCanvasById(id) {
  const { data } = await canvases.get(`/${id}`);
  return data;
}

export async function updateTitle(id, title) {
  /**
   * POST - 새로운 자원 생성
   * PUT - 기존 자원 전체 업데이트 또는 새 자원 생성
   * PATCH - 일부 수정
   */
  await canvases.patch(`/${id}`, { title });
}

export async function updateCanvas(id, canvas) {
  await canvases.put(`/${id}`, canvas);
}

코드 설명:

  • 기존 함수 유지 및 추가: 기존의 getCanvases, createCanvas, deleteCanvas, getCanvasById, updateTitle 함수는 기존 기능을 유지하면서 새로운 updateCanvas 함수를 추가하였습니다.
  • updateCanvas 함수: 이 함수는 특정 id를 가진 캔버스의 전체 데이터를 업데이트하는 역할을 합니다. PUT 메소드를 사용하여 서버에 전체 캔버스 데이터를 전송합니다. 이는 특정 캔버스의 모든 필드를 한 번에 수정하거나 새로운 캔버스를 생성할 때 유용합니다. 예를 들어, 사용자가 린캔버스의 여러 섹션을 동시에 업데이트할 경우, 이 함수를 통해 한 번의 요청으로 모든 변경 사항을 서버에 반영할 수 있습니다.
  • RESTful API 메소드 주석 추가: 각 HTTP 메소드의 역할을 주석으로 명시하여 코드의 이해도를 높였습니다. 이는 다른 개발자들이 코드를 빠르게 이해하고 유지보수할 때 큰 도움이 됩니다.
  • 모듈화 및 재사용성: API 요청 함수를 모듈화하여 필요한 곳에서 쉽게 불러와 사용할 수 있도록 설계하였습니다. 이는 코드의 재사용성을 높이고, API 요청 로직을 중앙에서 관리할 수 있게 합니다.

추가 설명:

updateCanvas 함수의 도입은 린캔버스 애플리케이션에서 데이터의 일관성을 유지하고, 다양한 섹션의 정보를 효율적으로 관리할 수 있게 해줍니다. PUT 메소드를 사용함으로써 서버에 전체 데이터를 전송하여 기존 데이터를 완전히 대체할 수 있습니다. 반면, updateTitle 함수는 PATCH 메소드를 사용하여 캔버스의 특정 필드만 수정할 때 사용됩니다. 이러한 분리는 데이터 수정의 목적에 따라 적절한 메소드를 선택할 수 있게 해주며, API 요청의 효율성을 높입니다.


✏️ src/pages/CanvasDetail.jsx 수정

CanvasDetail 페이지에서 캔버스의 상세 정보를 표시하고 수정할 수 있도록 로직을 추가했습니다. 또한, 전체 캔버스 데이터를 업데이트할 수 있는 기능을 구현했습니다.

src/pages/CanvasDetail.jsx

import { useParams } from 'react-router-dom';
import CanvasTitle from '../components/CanvasTitle';
import LeanCanvas from '../components/LeanCanvas';
import { useEffect, useState } from 'react';
import { getCanvasById, updateCanvas, updateTitle } from '../api/canvas';

function CanvasDetail() {
  const { id } = useParams();
  const [canvas, setCanvas] = useState();

  useEffect(() => {
    const fetchCanvas = async () => {
      const data = await getCanvasById(id);
      setCanvas(data);
    };
    fetchCanvas();
  }, [id]);

  const handleTitleChange = async title => {
    try {
      await updateTitle(id, title);
      setCanvas(prev => ({ ...prev, title }));
    } catch (err) {
      alert(err.message);
    }
  };

  const handleCanvasChange = async updatedCanvas => {
    try {
      await updateCanvas(id, updatedCanvas);
      setCanvas(updatedCanvas);
    } catch (err) {
      alert(err.message);
    }
  };

  return (
    <div>
      <CanvasTitle value={canvas?.title} onChange={handleTitleChange} />
      {canvas && (
        <LeanCanvas canvas={canvas} onCanvasChange={handleCanvasChange} />
      )}
    </div>
  );
}

export default CanvasDetail;

코드 설명:

  • useParams 활용: useParams 훅을 사용하여 URL에서 id 파라미터를 추출합니다. 이는 특정 캔버스의 상세 정보를 가져오는 데 필요합니다.
  • 상태 관리 (useState): canvas 상태를 관리하여 현재 페이지에서 표시할 캔버스 데이터를 저장합니다. 초기 상태는 undefined로 설정되어 있으며, useEffect를 통해 데이터를 불러온 후 업데이트됩니다.
  • 데이터 가져오기 (useEffect): 컴포넌트가 마운트될 때 getCanvasById 함수를 호출하여 해당 id를 가진 캔버스의 데이터를 서버에서 가져옵니다. 비동기 함수인 fetchCanvas를 정의하고 즉시 호출하여 데이터를 가져옵니다. 이는 컴포넌트의 라이프사이클 동안 한 번만 실행되도록 id 의존성을 설정하였습니다.
  • handleTitleChange 함수: 사용자가 캔버스의 제목을 변경할 때 호출됩니다. 이 함수는 updateTitle 함수를 사용하여 서버에 제목 변경을 요청하고, 성공 시 로컬 상태(canvas)의 title을 업데이트합니다. 오류가 발생할 경우 사용자에게 알림을 표시합니다.
  • handleCanvasChange 함수: 린캔버스의 전체 데이터를 변경할 때 호출됩니다. 이 함수는 updateCanvas 함수를 사용하여 서버에 전체 캔버스 데이터를 업데이트하고, 성공 시 로컬 상태를 갱신합니다. 이는 LeanCanvas 컴포넌트에서 발생하는 모든 변경 사항을 반영하는 데 사용됩니다.
  • 조건부 렌더링: canvas 데이터가 존재할 때만 LeanCanvas 컴포넌트를 렌더링하여, 데이터가 없을 경우 발생할 수 있는 오류를 방지합니다. 이는 비동기 데이터 로딩 중에 컴포넌트가 정상적으로 작동하도록 돕습니다.

추가 설명:

CanvasDetail 컴포넌트는 특정 캔버스의 상세 정보를 관리합니다. useEffect 훅을 사용하여 컴포넌트가 마운트될 때 해당 캔버스의 데이터를 불러옵니다. 이는 사용자가 특정 린캔버스를 선택했을 때, 해당 캔버스의 모든 정보를 로드하여 화면에 표시할 수 있게 합니다.

handleCanvasChange 함수는 LeanCanvas 컴포넌트에서 발생하는 모든 데이터 변경 사항을 처리합니다. 이를 통해 사용자가 린캔버스의 각 섹션을 수정할 때마다, 변경된 데이터를 서버에 업데이트하고 로컬 상태를 동기화합니다. 이는 데이터의 일관성을 유지하고, 사용자에게 실시간으로 업데이트된 정보를 제공하는 데 중요한 역할을 합니다.


🎨 src/components/LeanCanvas.jsx 구현

LeanCanvas 컴포넌트는 린캔버스의 각 섹션을 표시하고 수정할 수 있는 기능을 제공합니다. 이 컴포넌트는 캔버스 데이터를 받아 각 섹션별로 CanvasCard 컴포넌트를 렌더링합니다.

src/components/LeanCanvas.jsx

import CanvasCard from './CanvasCard';

function LeanCanvas({ canvas, onCanvasChange }) {
  const handleNotesChange = (section, updatedNotes) => {
    const updatedCanvas = {
      ...canvas,
      [section]: { notes: updatedNotes },
    };
    onCanvasChange(updatedCanvas);
  };

  return (
    <div className="border-4 border-black p-4">
      <div className="grid grid-cols-2 gap-4">
        <CanvasCard
          title="1. 문제"
          notes={canvas.problem.notes}
          onNotesChange={notes => handleNotesChange('problem', notes)}
        />
        <CanvasCard
          title="2. 고객 세그먼트"
          notes={canvas.customerSegments.notes}
          onNotesChange={notes => handleNotesChange('customerSegments', notes)}
        />
        <CanvasCard
          title="3. 가치제안"
          notes={canvas.valueProposition.notes}
          onNotesChange={notes => handleNotesChange('valueProposition', notes)}
        />
        <CanvasCard
          title="4. 해결안"
          notes={canvas.solution.notes}
          onNotesChange={notes => handleNotesChange('solution', notes)}
        />
        <CanvasCard
          title="5. 공정한 우위"
          notes={canvas.unfairAdvantage.notes}
          onNotesChange={notes => handleNotesChange('unfairAdvantage', notes)}
        />
        <CanvasCard
          title="6. 채널"
          notes={canvas.channels.notes}
          onNotesChange={notes => handleNotesChange('channels', notes)}
        />
        <CanvasCard
          title="7. 핵심 지표"
          notes={canvas.keyMetrics.notes}
          onNotesChange={notes => handleNotesChange('keyMetrics', notes)}
        />
        <CanvasCard
          title="8. 비용 구조"
          notes={canvas.costStructure.notes}
          onNotesChange={notes => handleNotesChange('costStructure', notes)}
        />
        <CanvasCard
          title="9. 수익 흐름"
          notes={canvas.revenueStreams.notes}
          onNotesChange={notes => handleNotesChange('revenueStreams', notes)}
        />
        <CanvasCard
          title="10. 기존 대안"
          notes={canvas.existingAlternatives.notes}
          onNotesChange={notes => handleNotesChange('existingAlternatives', notes)}
        />
        <CanvasCard
          title="11. 고수준 개념"
          notes={canvas.highLevelConcept.notes}
          onNotesChange={notes => handleNotesChange('highLevelConcept', notes)}
        />
        <CanvasCard
          title="12. 초기 사용자"
          notes={canvas.earlyAdopters.notes}
          onNotesChange={notes => handleNotesChange('earlyAdopters', notes)}
        />
      </div>
    </div>
  );
}

export default LeanCanvas;

코드 설명:

  • 컴포넌트 구조: LeanCanvas 컴포넌트는 린캔버스의 각 섹션을 CanvasCard 컴포넌트를 사용하여 렌더링합니다. 각 CanvasCard는 특정 섹션의 제목과 노트 데이터를 받아 표시 및 수정 기능을 제공합니다.
  • handleNotesChange 함수: 이 함수는 특정 섹션(problem, customerSegments, valueProposition, 등)의 노트가 변경될 때 호출됩니다. 섹션 이름과 업데이트된 노트를 인자로 받아 전체 캔버스 데이터를 업데이트하고, onCanvasChange 콜백을 통해 상위 컴포넌트(CanvasDetail)에 변경된 캔버스 데이터를 전달합니다. 이를 통해 데이터의 일관성을 유지하며, 모든 변경 사항이 서버와 동기화됩니다.
  • 그리드 레이아웃: grid-cols-2gap-4 클래스를 사용하여 섹션들이 두 열로 정렬되고, 섹션 간에 일정한 간격을 유지하도록 했습니다. 이는 UI의 깔끔함과 가독성을 높여줍니다.
  • 섹션 추가 용이성: 각 섹션을 CanvasCard 컴포넌트로 분리함으로써, 새로운 섹션을 추가하거나 기존 섹션을 수정할 때 코드의 중복을 줄이고 유지보수를 용이하게 했습니다. 예를 들어, 새로운 섹션을 추가하려면 단순히 새로운 CanvasCard 컴포넌트를 추가하면 됩니다.

추가 설명:

LeanCanvas 컴포넌트는 린캔버스의 각 섹션을 독립적으로 관리할 수 있게 설계되었습니다. 각 섹션은 CanvasCard 컴포넌트를 통해 표시되며, 사용자가 각 섹션의 노트를 추가, 수정, 삭제할 수 있는 기능을 제공합니다. handleNotesChange 함수를 통해 섹션별로 노트가 변경될 때마다 전체 캔버스 데이터가 업데이트되고, 이는 CanvasDetail 컴포넌트에서 서버와 동기화됩니다. 이러한 구조는 데이터 관리의 효율성을 높이고, 사용자 경험을 향상시키는 데 큰 도움이 됩니다.


🗂️ src/components/CanvasCard.jsx 수정

CanvasCard 컴포넌트는 린캔버스의 각 섹션을 표시하고, 노트의 수정 기능을 제공합니다. 이번 수정에서는 노트의 내용을 업데이트할 수 있는 기능을 추가했습니다.

src/components/CanvasCard.jsx

import { FaPlus } from 'react-icons/fa';
import Note from './Note';

function CanvasCard({ title, isSubtitle = false, notes = [], onNotesChange }) {
  const handleAddNote = () => {
    const newNote = {
      id: `n${Date.now()}`,
      content: '',
      color: 'bg-yellow-300',
    };
    onNotesChange([...notes, newNote]);
  };

  const handleRemoveNote = id => {
    const updatedNotes = notes.filter(note => note.id !== id);
    onNotesChange(updatedNotes);
  };

  const handleUpdateNote = (id, content, color) => {
    onNotesChange(
      notes.map(note => (note.id === id ? { ...note, content, color } : note)),
    );
  };

  return (
    <div className="row-span-1 bg-white min-h-48 border border-gray-300 p-4 rounded shadow">
      <h2 className={`text-xl font-semibold mb-4 ${isSubtitle ? 'text-gray-700' : 'text-black'}`}>
        {title}
      </h2>
      <div className="space-y-2">
        {notes.map(note => (
          <Note
            key={note.id}
            id={note.id}
            content={note.content}
            color={note.color}
            onRemoveNote={handleRemoveNote}
            onUpdateNote={handleUpdateNote}
          />
        ))}
        <button
          className="flex items-center justify-center w-full p-2 bg-green-500 text-white rounded hover:bg-green-600 transition"
          onClick={handleAddNote}
        >
          <FaPlus /> 추가
        </button>
      </div>
    </div>
  );
}

export default CanvasCard;

코드 설명:

  • 노트 추가 (handleAddNote): 사용자가 "추가" 버튼을 클릭하면 새로운 노트가 생성됩니다. id는 현재 시간을 기반으로 고유하게 생성되며, 기본 content는 빈 문자열, 기본 colorbg-yellow-300으로 설정됩니다. 새로운 노트는 기존 노트 배열에 추가되어 onNotesChange 함수를 통해 상위 컴포넌트에 전달됩니다.
  • 노트 제거 (handleRemoveNote): 특정 id를 가진 노트를 삭제할 때 호출됩니다. 해당 id를 가진 노트를 필터링하여 제외한 새로운 노트 배열을 생성하고, onNotesChange 함수를 통해 상위 컴포넌트에 전달합니다.
  • 노트 업데이트 (handleUpdateNote): 특정 노트의 contentcolor를 업데이트할 때 호출됩니다. 노트 배열을 순회하면서 해당 id를 가진 노트를 찾아 새로운 contentcolor로 교체한 후, 업데이트된 노트 배열을 onNotesChange 함수를 통해 상위 컴포넌트에 전달합니다.
  • 스타일링 및 레이아웃: row-span-1, bg-white, min-h-48, border, border-gray-300, p-4, rounded, shadow 등의 Tailwind CSS 클래스를 사용하여 카드의 시각적 스타일을 지정하고, 사용자에게 깔끔하고 직관적인 UI를 제공합니다.
  • 노트 렌더링: notes 배열을 순회하며 각 노트를 Note 컴포넌트로 렌더링합니다. 각 Note 컴포넌트는 노트의 내용을 표시하고, 수정 및 삭제 기능을 제공합니다.
  • 추가 버튼: "추가" 버튼을 클릭하면 새로운 노트가 추가되도록 구현되었습니다. 이는 사용자가 자유롭게 노트를 추가할 수 있는 기능을 제공합니다.

추가 설명:

CanvasCard 컴포넌트는 린캔버스의 각 섹션을 관리하는 핵심 요소입니다. 사용자가 각 섹션별로 노트를 추가하거나 수정할 수 있도록 하여, 린캔버스의 유연성을 높였습니다. 노트의 추가, 제거, 수정 기능을 통해 사용자는 린캔버스의 각 섹션을 체계적으로 관리할 수 있으며, 이는 아이디어를 보다 명확하게 정리하는 데 큰 도움이 됩니다. 또한, Tailwind CSS를 활용한 스타일링은 컴포넌트의 일관된 디자인을 유지하면서도 사용자에게 친숙한 인터페이스를 제공합니다.


🖌️ src/components/Note.jsx 수정

Note 컴포넌트는 개별 노트를 표시하고 수정할 수 있는 기능을 제공합니다. 이번 수정에서는 노트의 색상을 변경하고, 텍스트 영역의 높이를 자동으로 조절하는 기능을 추가했습니다.

src/components/Note.jsx

import { useEffect, useRef, useState } from 'react';
import { AiOutlineClose, AiOutlineCheck } from 'react-icons/ai';

const Note = ({
  id,
  content,
  color: initialColor,
  onUpdateNote,
  onRemoveNote,
}) => {
  const [color, setColor] = useState(initialColor);
  const [isEditing, setIsEditing] = useState(false);
  const textareaRef = useRef(null);

  useEffect(() => {
    setColor(initialColor);
  }, [initialColor]);

  useEffect(() => {
    if (textareaRef.current) {
      textareaRef.current.style.height = 'auto';
      textareaRef.current.style.height =
        textareaRef.current.scrollHeight + 'px';
    }
  }, [content]);

  const colorOptions = [
    'bg-yellow-300',
    'bg-pink-300',
    'bg-blue-300',
    'bg-green-300',
  ];

  const handleContentChange = e => {
    onUpdateNote(id, e.target.value, color);
  };

  const handleColorChange = newColor => {
    setColor(newColor);
    onUpdateNote(id, content, newColor);
  };

  const handleSave = () => {
    setIsEditing(false);
  };

  const handleCancel = () => {
    setIsEditing(false);
    // Optionally, reset content if needed
  };

  return (
    <div className={`p-4 ${color} relative max-h-[32rem] overflow-hidden rounded shadow`}>
      {isEditing ? (
        <>
          <textarea
            ref={textareaRef}
            value={content}
            onChange={handleContentChange}
            className="w-full h-full bg-transparent resize-none border-none focus:outline-none text-gray-900 overflow-hidden"
            aria-label="Edit Note"
            placeholder="메모를 작성하세요."
          />
          <div className="flex justify-end mt-2 space-x-2">
            <button
              className="p-1 bg-red-500 text-white rounded hover:bg-red-600 transition"
              onClick={handleCancel}
              aria-label="Cancel edit"
            >
              <AiOutlineClose />
            </button>
            <button
              className="p-1 bg-green-500 text-white rounded hover:bg-green-600 transition"
              onClick={handleSave}
              aria-label="Save edit"
            >
              <AiOutlineCheck />
            </button>
          </div>
        </>
      ) : (
        <>
          <p className="whitespace-pre-wrap">{content}</p>
          <div className="absolute top-2 right-2 flex space-x-1">
            {colorOptions.map((option, index) => (
              <button
                key={index}
                className={`w-6 h-6 rounded-full cursor-pointer outline outline-gray-50 ${option}`}
                onClick={() => handleColorChange(option)}
                aria-label={`Change color to ${option}`}
              />
            ))}
            <button
              className="ml-2 p-1 bg-gray-300 text-gray-700 rounded hover:bg-gray-400 transition"
              onClick={() => onRemoveNote(id)}
              aria-label="Remove note"
            >
              <AiOutlineClose />
            </button>
          </div>
        </>
      )}
    </div>
  );
};

export default Note;

코드 설명:

  • 상태 관리 (useState):

    • color: 현재 노트의 배경색을 관리합니다. 초기 값은 initialColor로 설정되며, useEffect를 통해 외부에서 전달된 색상 값이 변경될 때 업데이트됩니다.
    • isEditing: 노트가 편집 모드인지 여부를 관리합니다. true일 때는 편집 가능한 텍스트 영역과 저장/취소 버튼이 표시되고, false일 때는 노트 내용과 색상 변경 버튼이 표시됩니다.
  • 참조 관리 (useRef):

    • textareaRef: 텍스트 영역의 DOM 요소에 접근하기 위해 useRef를 사용합니다. 이를 통해 텍스트 영역의 높이를 동적으로 조절할 수 있습니다.
  • 자동 높이 조절 (useEffect):

    • useEffect 훅을 사용하여 content가 변경될 때마다 텍스트 영역의 높이를 자동으로 조절합니다. 먼저 높이를 'auto'로 설정하여 콘텐츠의 크기에 맞게 초기화한 후, scrollHeight 값을 통해 실제 콘텐츠 높이로 설정합니다. 이는 사용자가 노트 내용을 입력할 때마다 텍스트 영역이 자동으로 확장되어 스크롤 없이 모든 내용을 볼 수 있게 합니다.
  • 색상 변경 (handleColorChange):

    • 사용자가 색상 버튼을 클릭하면 handleColorChange 함수가 호출되어 노트의 color 상태를 업데이트하고, onUpdateNote 함수를 통해 상위 컴포넌트에 변경된 색상을 전달합니다. 이는 노트의 시각적 구분을 용이하게 하여 사용자 경험을 향상시킵니다.
  • 내용 변경 (handleContentChange):

    • 사용자가 텍스트 영역에 입력할 때마다 handleContentChange 함수가 호출되어 노트의 content를 업데이트합니다. 이는 실시간으로 노트의 내용을 수정할 수 있게 해주며, 변경 사항은 즉시 상위 컴포넌트에 반영됩니다.
  • 편집 모드 전환:

    • 편집 시작: 사용자가 노트 내용을 클릭하거나 특정 버튼을 클릭하여 편집 모드로 전환할 수 있습니다.
    • 편집 완료 (handleSave): 사용자가 저장 버튼을 클릭하면 isEditing 상태가 false로 변경되어 편집 모드가 종료되고, 변경된 내용이 반영됩니다.
    • 편집 취소 (handleCancel): 사용자가 취소 버튼을 클릭하면 isEditing 상태가 false로 변경되어 편집 모드가 종료되고, 이전 상태로 되돌릴 수 있습니다.
  • 노트 삭제 (onRemoveNote):

    • 사용자가 삭제 버튼을 클릭하면 onRemoveNote 함수가 호출되어 해당 노트가 삭제됩니다. 이는 노트를 더 이상 필요로 하지 않을 때 쉽게 제거할 수 있게 해줍니다.
  • 접근성 (aria-label):

    • 각 버튼에 aria-label을 추가하여 스크린 리더 사용자도 버튼의 기능을 이해할 수 있게 했습니다. 이는 웹 접근성을 향상시키는 중요한 요소입니다.

추가 설명:

Note 컴포넌트는 린캔버스의 각 노트를 직관적으로 관리할 수 있는 기능을 제공합니다. 사용자는 노트의 내용을 자유롭게 수정할 수 있으며, 색상 변경 기능을 통해 노트를 시각적으로 구분할 수 있습니다. 텍스트 영역의 자동 높이 조절 기능은 사용자 경험을 크게 향상시키며, 노트의 편집 모드 전환은 인터랙티브한 사용자 인터페이스를 구현하는 데 중요한 역할을 합니다.

또한, 노트 삭제 기능은 사용자가 필요하지 않은 노트를 손쉽게 제거할 수 있게 해주어 데이터의 정리와 관리에 용이함을 제공합니다. 접근성을 고려한 aria-label 추가는 모든 사용자가 애플리케이션을 편리하게 사용할 수 있도록 돕습니다.


🖋️ src/components/Note.jsx 추가 수정 - Textarea height auto 초기화

노트의 텍스트 영역이 내용에 따라 자동으로 높이가 조절되도록 수정했습니다.

src/components/Note.jsx

useEffect(() => {
  if (textareaRef.current) {
    textareaRef.current.style.height = 'auto';
    textareaRef.current.style.height =
      textareaRef.current.scrollHeight + 'px';
  }
}, [content]);

코드 설명:

  • 자동 높이 조절:
    • useEffect 훅을 사용하여 content가 변경될 때마다 텍스트 영역의 높이를 재설정합니다.
    • 먼저 textareaRef.current.style.height = 'auto'로 설정하여 텍스트 영역의 높이를 초기화합니다. 이는 콘텐츠의 현재 높이를 정확하게 측정하기 위함입니다.
    • 그 다음, textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'로 설정하여 텍스트 영역의 높이를 콘텐츠의 실제 높이에 맞게 조절합니다. scrollHeight는 텍스트 영역 내의 전체 콘텐츠 높이를 반환하며, 이를 통해 텍스트 영역이 콘텐츠에 맞게 자동으로 확장됩니다.

추가 설명:

이 기능은 사용자가 노트의 내용을 입력할 때마다 텍스트 영역의 크기가 자동으로 조절되어, 스크롤 없이 모든 내용을 확인할 수 있게 합니다. 이는 사용자 경험을 크게 향상시키며, 노트 작성 시 편리함을 제공합니다. 예를 들어, 긴 내용을 입력할 때 텍스트 영역이 자동으로 확장되므로, 사용자는 내용을 한눈에 확인하고 수정할 수 있습니다.

자동 높이 조절 기능은 특히 모바일 기기나 터치 스크린에서의 사용성을 높이는 데 중요한 역할을 합니다. 사용자가 노트를 작성하면서 텍스트 영역이 자연스럽게 확장되기 때문에, 더욱 직관적이고 편리한 인터페이스를 제공할 수 있습니다.


전체 수정 사항 요약 📋

  1. db.json 파일 수정:

    • 린캔버스의 다양한 섹션(problem, customerSegments, valueProposition, 등)과 노트(notes)를 추가하여 데이터 구조를 확장했습니다.
    • 각 노트에 id, content, color 속성을 부여하여 데이터 관리의 유연성을 높였습니다.
  2. src/api/canvas.js 수정:

    • updateCanvas 함수를 추가하여 특정 캔버스의 전체 데이터를 업데이트할 수 있게 했습니다.
    • RESTful API 메소드에 대한 주석을 추가하여 코드의 이해도를 향상시켰습니다.
  3. src/pages/CanvasDetail.jsx 수정:

    • handleCanvasChange 함수를 추가하여 캔버스의 전체 데이터를 업데이트하고 로컬 상태를 갱신하도록 했습니다.
    • LeanCanvas 컴포넌트에 onCanvasChange prop을 전달하여 데이터 변경 시 상위 컴포넌트와 동기화할 수 있게 했습니다.
  4. src/components/LeanCanvas.jsx 구현:

    • handleNotesChange 함수를 통해 특정 섹션의 노트를 업데이트할 수 있게 했습니다.
    • 각 섹션을 CanvasCard 컴포넌트로 렌더링하여 재사용성을 높이고 코드의 중복을 줄였습니다.
  5. src/components/CanvasCard.jsx 수정:

    • handleUpdateNote 함수를 추가하여 노트의 내용을 업데이트할 수 있게 했습니다.
    • 노트 추가 및 삭제 기능을 강화하여 사용자 편의성을 높였습니다.
  6. src/components/Note.jsx 수정:

    • 노트의 색상 변경 기능을 추가하여 시각적 구분을 용이하게 했습니다.
    • 텍스트 영역의 자동 높이 조절 기능을 구현하여 사용자 경험을 향상시켰습니다.
    • 편집 모드 전환 기능을 통해 노트의 내용을 직관적으로 수정할 수 있게 했습니다.
  7. src/components/Note.jsx 추가 수정:

    • 텍스트 영역의 높이를 자동으로 조절하여 내용에 맞게 확장되도록 구현했습니다.

배운 내용 요약 📝

이번 린캔버스 수정 작업을 통해 다음과 같은 주요 내용을 학습하고 적용할 수 있었습니다:

  1. RESTful API 메소드 활용:

    • PUTPATCH 메소드를 적절히 사용하여 전체 데이터 업데이트와 부분 데이터 수정을 효율적으로 처리하는 방법을 배웠습니다.
    • API 요청 함수를 체계적으로 관리하여 코드의 가독성과 유지보수성을 향상시켰습니다.
  2. React 컴포넌트 구조화:

    • 컴포넌트를 재사용 가능한 단위로 분리하여 코드의 중복을 줄이고, 각 컴포넌트의 책임을 명확히 했습니다.
    • 상위 컴포넌트와 하위 컴포넌트 간의 데이터 흐름을 관리하여 상태의 일관성을 유지하는 방법을 익혔습니다.
  3. 사용자 경험(UX) 향상:

    • 노트의 색상 변경 기능과 자동 높이 조절 기능을 추가하여 사용자 인터페이스를 더욱 직관적이고 편리하게 만들었습니다.
    • 편집 모드 전환과 같은 인터랙티브한 기능을 통해 사용자 참여도를 높였습니다.
  4. 상태 관리 및 동기화:

    • React의 useStateuseEffect 훅을 활용하여 컴포넌트의 상태를 효과적으로 관리하고, 서버와의 데이터 동기화를 구현했습니다.
    • 실시간 데이터 업데이트를 통해 UI의 일관성을 유지하고, 사용자에게 최신 정보를 제공할 수 있게 했습니다.
  5. 코드 최적화 및 유지보수성 향상:

    • 각 기능을 독립적인 함수로 분리하여 코드의 모듈화를 실현했습니다.
    • 주석과 문서화를 통해 코드의 이해도를 높이고, 향후 유지보수를 용이하게 했습니다.

이러한 학습과정을 통해 린캔버스 애플리케이션의 기능을 확장하고, 사용자 경험을 크게 향상시킬 수 있었습니다. 앞으로도 지속적인 개선과 최적화를 통해 더욱 완성도 높은 애플리케이션을 개발할 계획입니다.

profile
개발자가 되고 싶은 정치학도생의 기술 블로그

0개의 댓글