본문 바로가기
AI/AI 활용 기초

메모만 붙여넣으면 업무보고 표 완성, 단일 HTML 도구 만들기

by 고돌한 AI 2026. 9. 19.
반응형
메모를 주간 업무보고 표로 바꾸는 단일 HTML 도구 대표 이미지

메모만 붙여넣으면 업무보고 표 완성, 단일 HTML 도구 만들기

금요일마다 흩어진 메모를 표로 다시 옮기고 있다면, 서버나 설치 프로그램 없이 여는 단일 HTML 도구로 줄일 수 있습니다. 아래 파일은 상태 | 업무 구분 | 한 일 | 결과 형식의 메모를 읽어 주간 업무보고 표로 바꾸고, 복사와 CSV 저장까지 처리합니다. 코드는 브라우저에서 직접 동작하며 메모를 외부 API로 보내지 않도록 구성했습니다.

30초 요약

  • 메모 한 줄을 업무보고 표 한 행으로 바꿉니다.
  • HTML 파일 하나라서 설치나 회원가입이 필요 없습니다.
  • 표를 엑셀에 붙여넣거나 CSV 파일로 저장할 수 있습니다.
  • 입력값은 textContent로 넣어 메모 속 HTML이 실행되지 않게 막았습니다.
  • 사내 기밀은 회사 보안 정책을 먼저 확인하고 예시 데이터로 시험하세요.

이걸 만들면 무엇이 달라질까요?

업무 중에는 다음처럼 한 줄만 남깁니다.

완료 | 고객 포털 | 로그인 오류 수정 | 운영 반영
진행 | 자동화 | 주간 보고서 템플릿 | 팀 검토 중
예정 | 데이터 정리 | 9월 지표 취합 | 다음 주 시작

보고할 때 같은 내용을 다시 표 칸마다 옮기지 않습니다. 메모 전체를 붙여넣고 표 만들기를 누르면 상태, 업무 구분, 한 일, 결과가 각각의 열로 나뉩니다.

예시 화면 · 실제 앱이 아닌 정적 미리보기

이번 주 업무보고

상태업무 구분한 일결과
완료고객 포털로그인 오류 수정운영 반영
진행자동화주간 보고서 템플릿팀 검토 중
예시 메모가 네 열의 주간 업무보고 표로 바뀐 완성 화면

업무 전에는 메모를 찾고, 셀을 만들고, 문장을 다시 나누는 일이 한 덩어리였습니다. 이 도구를 쓰면 기록 방식만 맞춘 뒤 표 생성과 파일 저장은 브라우저가 맡습니다. 다만 애매한 자연어를 AI가 해석하는 도구는 아닙니다. 구분자 |를 기준으로 정확히 나누는 작은 자동화입니다.

왜 단일 HTML로 만드는 게 맞을까요?

HTML은 웹페이지의 뼈대를 적는 파일입니다. 여기에 화면 모양을 맡는 CSS와 버튼 동작을 맡는 JavaScript를 한 파일에 함께 넣으면, 별도 설치 없이 브라우저로 열 수 있습니다.

이 주제에서는 서버가 오히려 짐이 될 수 있습니다. 메모를 표로 나누는 데 계정, 데이터베이스, 외부 API가 필요하지 않기 때문입니다. 완성 파일을 메신저로 배포하기 전에는 회사의 파일 공유 규정과 보안 정책을 확인하세요.

작동 과정은 단순합니다.

  1. 입력창의 메모를 줄바꿈 기준으로 나눕니다.
  2. 빈 줄과 앞쪽의 글머리표를 걷어냅니다.
  3. 각 줄을 | 기준으로 최대 네 칸으로 나눕니다.
  4. 새 표 행을 만들고 각 셀에 일반 텍스트로 값을 넣습니다.
  5. 같은 데이터를 탭 구분 복사나 CSV 저장에 재사용합니다.
여러 줄 메모가 표와 CSV로 이어지는 입력 결과 흐름

여기서 textContent는 메모를 HTML 명령이 아닌 글자로 넣는 속성입니다. MDN은 원시 HTML을 다루는 innerHTML로 텍스트를 넣으면 XSS 공격에 노출될 수 있다고 설명합니다. 그래서 표 셀은 innerHTML 대신 textContent를 씁니다.

어디에 무엇을 붙여넣으면 되나요?

먼저 메모장이나 코드 편집기를 엽니다. 새 파일을 만든 뒤, 아래 코드의 첫 줄 <!doctype html>부터 마지막 </html>까지 전부 복사해 붙여넣으세요.

파일 이름은 weekly-report.html로 저장합니다. 저장한 파일을 더블클릭했을 때 주간 업무보고 만들기 화면이 보이면 첫 단계는 성공입니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>주간 업무보고 만들기</title>
  <style>
    :root { color-scheme: light; --navy:#0f172a; --teal:#0f766e; --line:#cbd5e1; }
    * { box-sizing:border-box; }
    body { margin:0; font-family:system-ui,-apple-system,sans-serif; background:#f1f5f9; color:var(--navy); }
    main { width:min(960px,calc(100% - 32px)); margin:40px auto; }
    .card { background:white; border-radius:18px; padding:24px; box-shadow:0 12px 36px rgba(15,23,42,.08); }
    h1 { margin-top:0; font-size:clamp(24px,5vw,36px); }
    .hint { color:#475569; line-height:1.6; }
    textarea { width:100%; min-height:190px; padding:14px; border:1px solid var(--line); border-radius:12px; font:inherit; line-height:1.6; }
    .actions { display:flex; flex-wrap:wrap; gap:10px; margin:14px 0 22px; }
    button { border:0; border-radius:10px; padding:11px 15px; font-weight:700; cursor:pointer; }
    .primary { background:var(--teal); color:white; }
    .secondary { background:#e2e8f0; color:var(--navy); }
    .table-wrap { overflow-x:auto; }
    table { width:100%; min-width:680px; border-collapse:collapse; }
    th,td { padding:11px; border:1px solid var(--line); text-align:left; vertical-align:top; }
    th { background:var(--navy); color:white; }
    #message { min-height:24px; color:#b45309; font-weight:700; }
    @media (max-width:600px) { main { margin:20px auto; } .card { padding:16px; } button { flex:1 1 100%; } }
  </style>
</head>
<body>
<main>
  <section class="card">
    <h1>주간 업무보고 만들기</h1>
    <p class="hint">한 줄에 하나씩 `상태 | 업무 구분 | 한 일 | 결과` 순서로 적으세요.</p>
    <textarea id="notes" aria-label="주간 업무 메모">완료 | 고객 포털 | 로그인 오류 수정 | 운영 반영
진행 | 자동화 | 주간 보고서 템플릿 | 팀 검토 중
예정 | 데이터 정리 | 9월 지표 취합 | 다음 주 시작</textarea>
    <div class="actions">
      <button class="primary" id="build">표 만들기</button>
      <button class="secondary" id="copy">표 복사</button>
      <button class="secondary" id="download">CSV 저장</button>
      <button class="secondary" id="clear">비우기</button>
    </div>
    <p id="message" role="status" aria-live="polite"></p>
    <div class="table-wrap">
      <table>
        <thead><tr><th>상태</th><th>업무 구분</th><th>한 일</th><th>결과</th></tr></thead>
        <tbody id="reportBody"></tbody>
      </table>
    </div>
  </section>
</main>
<script>
  const notes = document.querySelector('#notes');
  const body = document.querySelector('#reportBody');
  const message = document.querySelector('#message');
  let rows = [];

  function parseNotes(text) {
    return text.split(/\r?\n/)
      .map(line => line.trim().replace(/^[-*•]\s*/, ''))
      .filter(Boolean)
      .map(line => {
        const cells = line.split('|').map(value => value.trim());
        return [cells[0] || '미분류', cells[1] || '', cells[2] || '', cells.slice(3).join(' | ') || ''];
      });
  }

  function render(data) {
    body.replaceChildren();
    data.forEach(values => {
      const tr = document.createElement('tr');
      values.forEach(value => {
        const td = document.createElement('td');
        td.textContent = value;
        tr.appendChild(td);
      });
      body.appendChild(tr);
    });
    message.textContent = data.length ? `${data.length}개 업무를 표로 만들었습니다.` : '메모를 한 줄 이상 입력하세요.';
  }

  function makeTable() {
    rows = parseNotes(notes.value);
    render(rows);
  }

  function toTsv(data) {
    return [['상태','업무 구분','한 일','결과'], ...data]
      .map(row => row.join('\t')).join('\n');
  }

  function csvCell(value) {
    return `"${String(value).replaceAll('"','""')}"`;
  }

  document.querySelector('#build').addEventListener('click', makeTable);

  document.querySelector('#copy').addEventListener('click', async () => {
    if (!rows.length) makeTable();
    if (!rows.length) return;
    try {
      await navigator.clipboard.writeText(toTsv(rows));
      message.textContent = '표를 복사했습니다. 엑셀 첫 셀에 붙여넣으세요.';
    } catch {
      message.textContent = '복사가 막혔습니다. CSV 저장을 이용하세요.';
    }
  });

  document.querySelector('#download').addEventListener('click', () => {
    if (!rows.length) makeTable();
    if (!rows.length) return;
    const csv = [['상태','업무 구분','한 일','결과'], ...rows]
      .map(row => row.map(csvCell).join(',')).join('\r\n');
    const blob = new Blob(['\uFEFF' + csv], { type:'text/csv;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = 'weekly-report.csv';
    link.click();
    URL.revokeObjectURL(url);
    message.textContent = 'CSV 파일을 저장했습니다.';
  });

  document.querySelector('#clear').addEventListener('click', () => {
    notes.value = '';
    rows = [];
    render(rows);
    notes.focus();
  });

  makeTable();
</script>
</body>
</html>

코드는 네 구역만 기억하면 됩니다. textarea가 메모 입력칸, parseNotes()가 줄과 칸을 나누는 분류 담당, render()가 화면 표를 만드는 담당, 마지막 버튼 이벤트들이 복사와 저장을 맡습니다. 사무실로 비유하면 접수함, 분류함, 보고서 서식, 발송 버튼을 한 책상에 놓은 셈입니다.

입력 형식이 맞는지 어떻게 확인하나요?

브라우저에서 파일을 연 뒤 예시 메모를 그대로 둔 채 표 만들기를 누릅니다. 표에 세 행이 나오고 안내 문구에 3개 업무를 표로 만들었습니다.가 표시되면 기본 흐름이 맞습니다.

그다음 첫 줄을 아래처럼 바꿔 보세요.

완료 | 홈페이지 | 모바일 메뉴 수정 | 배포 완료

첫 행이 완료 / 홈페이지 / 모바일 메뉴 수정 / 배포 완료로 바뀌어야 합니다. 표 복사 뒤 엑셀이나 스프레드시트의 첫 셀에 붙여넣었을 때 네 열로 나뉘는지도 확인합니다. 복사 권한이 막히면 화면에 CSV 저장을 쓰라는 문구가 나옵니다.

빈 줄은 무시하며 -, *, •로 시작한 글머리표도 걷어냅니다. 반면 구분자 없이 고객 문의 답변만 적으면 첫 칸에 그 문장이 들어가고 나머지는 비게 됩니다. 자동 추론을 하지 않는 대신, 엉뚱한 열로 자신 있게 분류하는 실수를 줄이는 선택입니다.

원하는 보고서 형식으로 바꾸려면 무엇을 수정하나요?

열 제목을 바꾸려면 HTML의 <th> 네 개와 JavaScript 안의 ['상태','업무 구분','한 일','결과'] 두 곳을 함께 고칩니다. 화면 표와 복사·CSV 파일의 머리글이 같은 순서를 써야 하기 때문입니다.

메모 규칙을 팀에 안내할 때는 거창한 매뉴얼보다 예시 한 줄이 낫습니다.

상태 | 프로젝트나 업무 구분 | 이번 주에 한 일 | 현재 결과나 다음 단계

AI로 문장을 알아서 분류하고 싶다면 외부 API와 별도 보안 설계가 필요합니다. 이름, 고객 정보, 계약 내용이 들어간 업무 메모라면 이 글의 로컬 규칙 기반 버전부터 쓰는 편이 단순합니다. 다만 로컬 파일도 화면 공유, 자동 백업, 공용 PC 같은 경로로 노출될 수 있으니 민감정보를 그대로 넣어도 안전하다고 단정하면 안 됩니다.

막혔을 때는 오류를 어떻게 읽으면 되나요?

화면이 코드처럼 보인다면 파일 확장자가 .html.txt로 저장됐을 가능성이 큽니다. 파일 이름을 확인하고 확장자를 .html로 바꾼 뒤 다시 여세요.

표 복사가 안 되면 코드 전체가 실패한 것이 아닙니다. 로컬 파일에서 클립보드 권한을 제한하는 브라우저가 있으므로 CSV 저장을 먼저 시험하면 됩니다. CSV 저장은 Blob으로 파일 내용을 만들고 URL.createObjectURL()로 임시 주소를 붙인 다음, 다운로드가 끝나면 URL.revokeObjectURL()로 주소를 해제합니다.

한글이 엑셀에서 깨지는 경우를 줄이려고 CSV 앞에 UTF-8 BOM(\uFEFF)을 붙였습니다. 회사 표준 프로그램의 가져오기 설정이 다르면 UTF-8 인코딩을 직접 선택해야 할 수도 있습니다.

이 도구가 잘 맞는 경우와 아닌 경우

매주 같은 네 칸으로 정리하고, 메모 작성 규칙을 직접 정할 수 있다면 잘 맞습니다. 팀원이 많지 않고 서버 운영 없이 파일 하나를 공유하고 싶을 때도 부담이 적습니다.

반대로 여러 사람이 동시에 편집하거나, 승인 이력을 남기거나, Jira·Notion 같은 시스템과 자동 동기화해야 한다면 단일 HTML의 범위를 벗어납니다. 자연어 문장을 문맥으로 분류하고 싶을 때도 규칙 기반 파서보다 별도 백엔드와 권한 관리가 필요합니다.

작은 도구의 장점은 모든 문제를 해결하는 데 있지 않습니다. 금요일마다 반복하던 ‘메모를 네 칸으로 옮기기’ 한 가지만 정확히 넘겨받는 데 있습니다.

핵심 정리: 다섯 가지 질문과 답

Q. 메모는 어떤 형식으로 써야 하나요?

한 줄에 한 업무를 적고 상태 | 업무 구분 | 한 일 | 결과 순서로 구분합니다. 빈 줄과 앞쪽 글머리표는 자동으로 무시합니다.

Q. 프로그램 설치가 필요한가요?

필요 없습니다. 코드를 .html 파일 하나로 저장한 뒤 브라우저로 열면 됩니다.

Q. 메모가 외부 서버로 전송되나요?

이 글의 제공 코드는 외부 API나 네트워크 요청을 넣지 않았습니다. 다만 회사의 브라우저 관리, 백업, 화면 공유 정책까지 보장하는 뜻은 아닙니다.

Q. 엑셀로 옮길 수 있나요?

표 복사는 탭으로 열을 나눠 클립보드에 넣고, CSV 저장은 UTF-8 BOM이 포함된 파일을 만듭니다. 클립보드 권한이 막히면 CSV 저장을 사용하세요.

Q. 언제 더 큰 시스템이 필요한가요?

동시 편집, 승인 이력, 계정별 권한, 외부 업무 시스템 연동이 필요하다면 단일 HTML 대신 서버가 있는 업무 도구를 검토해야 합니다.

작업 환경을 함께 정리한다면

이 도구 자체는 추가 장비 없이 동작합니다. 다만 원본 메모와 결과 표를 나란히 대조한다면 포터블 모니터가 화면 전환을 줄이는 데 직접 연결됩니다. 일렉싱크 S15A는 브랜드커넥트 검색에서 15.6인치 FHD 보조 모니터로 확인했지만, 구매 전 사용 중인 Mac의 영상 출력 방식과 필요한 케이블을 판매 페이지에서 다시 확인하세요.

추천 상품 이미지
본문 기반 추천 상품일렉싱크 넥서스 포터블 모니터 FHD 서브 보조 S15A (15.6인치) 39.62cm, 블랙평점 4.81 · 리뷰 863건 · 110,000원브랜드커넥트 공식 노출 1위 · 공개 지표 5%, 165,000원네이버 쇼핑 커넥트에서 상품 보기 →

정해진 형식으로 여러 줄을 입력하는 일이 잦다면 별도 키보드가 실용적인 선택지입니다. 아이노트 K608RB는 멀티페어링과 태블릿 거치 기능이 표시된 제품으로 확인했으며, Mac용 키 배치와 한글 각인 여부는 구매 페이지의 옵션·상세 정보에서 다시 확인해야 합니다.

추천 상품 이미지
본문 기반 추천 상품아이노트 K608RB 무선 블루투스 멀티페어링 키보드 태블릿 거치평점 4.84 · 리뷰 971건 · 29,800원브랜드커넥트 공식 노출 1위 · 공개 지표 5%, 34,800원네이버 쇼핑 커넥트에서 상품 보기 →

이 포스팅은 네이버 쇼핑 커넥트 활동의 일환으로, 판매 발생 시 수수료를 제공받습니다.

출처

  • MDN Web Docs, Node: textContent property: https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
  • MDN Web Docs, URL.createObjectURL(): https://developer.mozilla.org/ko/docs/Web/API/URL/createObjectURL_static
  • MDN Web Docs, Using the Web Storage API: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
반응형

댓글