0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Redmine.JS

Last updated at Posted at 2025-03-13
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>タスク管理プロトタイプ</title>
  <style>
    body { font-family: sans-serif; margin: 20px; }
    table { border-collapse: collapse; width: 100%; margin-top: 20px; }
    th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
    button { margin: 5px; }
  </style>
  <!-- Excelインポート用:SheetJS -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
</head>
<body>
  <h1>タスク管理プロトタイプ</h1>
  
  <!-- タスクの登録エリア -->
  <h2>タスク登録</h2>
  <form id="taskForm">
    <label>タイトル:<input type="text" id="title" required></label><br>
    <label>説明:<input type="text" id="description"></label><br>
    <label>ステータス:
      <select id="status">
        <option value="未着手">未着手</option>
        <option value="進行中">進行中</option>
        <option value="完了">完了</option>
      </select>
    </label>
    <button type="submit">タスク追加</button>
  </form>
  
  <!-- Excelファイルからインポート -->
  <h2>Excelインポート</h2>
  <input type="file" id="excelFile" accept=".xlsx, .xls" />
  <button id="importExcelBtn">Excelからインポート</button>
  
  <!-- エクスポート -->
  <h2>エクスポート</h2>
  <button id="exportJsonBtn">JSON出力</button>
  <button id="exportCsvBtn">CSV出力</button>
  
  <!-- タスク一覧表示 -->
  <h2>タスク一覧</h2>
  <table id="taskTable">
    <thead>
      <tr>
        <th>ID</th>
        <th>タイトル</th>
        <th>説明</th>
        <th>ステータス</th>
      </tr>
    </thead>
    <tbody>
      <!-- タスクデータが動的に追加される -->
    </tbody>
  </table>
  
  <script>
    // 内部状態のタスクデータは配列で管理(シンプルな自動インクリメントID)
    let tasks = [];
    let nextTaskId = 1;
    
    // タスクの追加関数
    function addTask(title, description, status) {
      const task = { id: nextTaskId++, title, description, status };
      tasks.push(task);
      renderTasks();
    }
    
    // タスク一覧をテーブルに描画
    function renderTasks() {
      const tbody = document.querySelector("#taskTable tbody");
      tbody.innerHTML = "";
      tasks.forEach(task => {
        const tr = document.createElement("tr");
        tr.innerHTML = `<td>${task.id}</td>
                        <td>${task.title}</td>
                        <td>${task.description}</td>
                        <td>${task.status}</td>`;
        tbody.appendChild(tr);
      });
    }
    
    // フォーム送信イベント:タスク追加
    document.getElementById("taskForm").addEventListener("submit", function(event){
      event.preventDefault();
      const title = document.getElementById("title").value;
      const description = document.getElementById("description").value;
      const status = document.getElementById("status").value;
      addTask(title, description, status);
      this.reset();
    });
    
    // Excelからタスクデータのインポート(前提:1列目にタイトル、2列目に説明、3列目にステータス)
    document.getElementById("importExcelBtn").addEventListener("click", function(){
      const fileInput = document.getElementById("excelFile");
      if (!fileInput.files.length) {
        alert("Excelファイルを選択してください");
        return;
      }
      const file = fileInput.files[0];
      const reader = new FileReader();
      reader.onload = function(e) {
        const data = e.target.result;
        // 拡張オプションを追加して文字化け対策
        const workbook = XLSX.read(data, {
          type: "array",
          codepage: 65001,  // UTF-8エンコーディングを指定
          cellStyles: true,
          cellNF: true,
          cellDates: true
        });
        // シート1を選択してデータを取得
        const firstSheetName = workbook.SheetNames[0];
        const worksheet = workbook.Sheets[firstSheetName];
        // オプション:raw: trueでセルの生の値を取得、header: 1で全体を配列として取得
        const rows = XLSX.utils.sheet_to_json(worksheet, {
          header: 1,
          raw: false, // 文字列として値を取得
          defval: ""  // 未定義セルの代替値
        });
        
        console.log("インポートデータ:", rows); // デバッグ用
        
        // ヘッダーがある場合は行番号を調整する
        rows.forEach((row, index) => {
          // 例:row[0]:タイトル, row[1]:説明, row[2]:ステータス
          if (index === 0) return; // ヘッダー行をスキップ
          if(row[0]) {
            // データが文字化けしていないか確認
            const title = String(row[0] || "");
            const desc = String(row[1] || "");
            const status = String(row[2] || "未着手");
            
            console.log(`インポート行 ${index}:`, title, desc, status); // デバッグ用
            addTask(title, desc, status);
          }
        });
      };
      reader.readAsArrayBuffer(file); // バイナリではなくArrayBufferとして読み込む
    });
    
    // JSON出力
    document.getElementById("exportJsonBtn").addEventListener("click", function(){
      if (tasks.length === 0) {
        alert("出力するタスクがありません");
        return;
      }
      const jsonStr = JSON.stringify(tasks, null, 2);
      const blob = new Blob([jsonStr], { type: "application/json" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = "tasks_" + new Date().toISOString().replace(/[:.-]/g, "") + ".json";
      a.click();
      URL.revokeObjectURL(url);
    });
    
    // CSV出力(日本語対応のカンマ区切り)
    document.getElementById("exportCsvBtn").addEventListener("click", function(){
      if (tasks.length === 0) {
        alert("出力するタスクがありません");
        return;
      }
      
      // ヘッダー行
      let csv = "ID,タイトル,説明,ステータス\n";
      
      // データ行(ダブルクォートでエスケープを確実に行う)
      tasks.forEach(task => {
        // ダブルクォート内のダブルクォートを二重にエスケープ
        const title = task.title.replace(/"/g, '""');
        const description = task.description.replace(/"/g, '""');
        const status = task.status.replace(/"/g, '""');
        
        csv += `${task.id},"${title}","${description}","${status}"\n`;
      });
      
      // 重要: CSVの先頭にUTF-8 BOMを追加
      // これによりExcelが日本語を正しく認識できる
      const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
      
      // 文字列をUTF-8エンコードのUint8Arrayに変換
      const encoder = new TextEncoder();
      const csvData = encoder.encode(csv);
      
      // BOMとCSVデータを結合
      const combinedArray = new Uint8Array(bom.length + csvData.length);
      combinedArray.set(bom, 0);
      combinedArray.set(csvData, bom.length);
      
      // 適切なMIMEタイプを指定
      const blob = new Blob([combinedArray], { type: "text/csv;charset=utf-8" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = "tasks_" + new Date().toISOString().replace(/[:.-]/g, "") + ".csv";
      a.click();
      URL.revokeObjectURL(url);
    });
    
    // 初期描画
    renderTasks();
  </script>
</body>
</html>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?