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?

【kintone】年齢と勤続年数を表示するカスタマイズビュー

Last updated at Posted at 2025-08-12

サンプルアプリの人事労務パックを例に、一覧画面で動的な値を表示する方法を示す。

タイトルなし.png


従業員名簿アプリに、一覧名が 年齢と勤続年数 のカスタマイズビューを追加して
「ページネーションを表示する」のチェックを外す。また、以下のHTMLを記述。

<div id="age-tenure-custom-view">
  <div class="loading">データを読み込み中...</div>
</div>

<style>
#age-tenure-custom-view {
  padding: 20px;
  background: #fff;
}

.loading, .error {
  text-align: center;
  padding: 40px;
  font-size: 16px;
}

.loading {
  color: #7f8c8d;
}

#employee-table th {
  background: #f8f9fa;
  font-weight: bold;
  text-align: center;
}
</style>

JavaScript / CSSファイル は下図のように配置。

タイトルqvfrewなし.png

ライブラリは以下のサイトで入手できる。

sample.js は以下のコードを使う。

sample.js
(() => {
  'use strict';

  const client = new KintoneRestAPIClient();

  // 一覧画面が表示されるときに実行される処理
  kintone.events.on('app.record.index.show', async (event) => {
    // 一覧名が「年齢と勤続年数」でない場合は何もしない
    if (event.viewName !== '年齢と勤続年数') return event;

    // 集計ボタンの右に「yyyy年MM月dd日現在」の形式で今日の日付を表示
    const headerSpace = kintone.app.getHeaderMenuSpaceElement();
    if (headerSpace) {
      const now = luxon.DateTime.now();
      const formatted = now.toFormat('yyyy年MM月dd日');
      const infoText = `${formatted}現在`;

      let infoEl = headerSpace.querySelector('.current-date-info');
      if (!infoEl) {
        infoEl = document.createElement('div');
        infoEl.className = 'current-date-info';
        infoEl.style.cssText = 'font-weight: bold; font-size: 26px;';
        headerSpace.appendChild(infoEl);
      }

      infoEl.textContent = infoText;
    }

    // カスタマイズビューのコンテナ要素を取得
    const container = document.getElementById('age-tenure-custom-view');
    // ローディング表示を設定
    container.innerHTML = '<div class="loading">データを読み込み中...</div>';

    // 在籍中の従業員のレコードを全て取得
    const records = await client.record.getAllRecordsWithCursor({
      app: kintone.app.getId(),
      fields: ['$id', '従業員番号', '氏名', '入社日', '生年月日'],
      query: '在籍状況 in ("在籍中")'
    });

    // 取得したレコードをDataTables用に変換
    const tableData = records.map(record => {
      // 年齢の計算
      let age;
      // 生年月日が入力されてない場合はハイフンを年齢に表示
      if (!record.生年月日?.value) {
        age = '-';
      // 生年月日が入力されている場合は 今日 - 生年月日 の差を年齢に表示
      } else {
        const birth = luxon.DateTime.fromISO(record.生年月日.value);
        const today = luxon.DateTime.now();
        const ageNum = Math.floor(today.diff(birth, 'years').years);
        age = `${ageNum}歳`;
      }

      // 勤続年数の計算
      let tenureHTML;
      // 入社日が入力されてない場合はハイフンを勤続年数に表示
      if (!record.入社日?.value) {
        tenureHTML = '-';
      // 入社日が入力されている場合は 今日 - 入社日 の差を勤続年数に表示
      } else {
        const hire = luxon.DateTime.fromISO(record.入社日.value);
        const today = luxon.DateTime.now();
        const diff = today.diff(hire, ['years', 'months']).toObject();
        const years = Math.floor(diff.years);
        const months = Math.floor(diff.months % 12);

        // 勤続年数を「○年○ヶ月」の形式に整形
        let yearHTML = '';
        if (years > 0) {
          yearHTML = (years < 10 ? '&nbsp;'.repeat(1) : '') + years + '';
        }
        const monthPad = months < 10 ? '&nbsp;'.repeat(1) : '';
        const monthHTML = monthPad + months + 'ヶ月';
        tenureHTML = yearHTML + monthHTML;
      }
      
      // 値がない場合はハイフンを表示し、従業員番号に詳細画面へのリンクを追加
      return {
        employeeNumberLink: `<a href="/k/${kintone.app.getId()}/show#record=${record.$id.value}" target="_blank">${record.従業員番号?.value || '-'}</a>`,
        name: record.氏名?.value || '-',
        birthDate: record.生年月日?.value || '-',
        hireDate: record.入社日?.value || '-',
        age: age,
        tenureHTML: tenureHTML
      };
    });

    // カスタマイズビュー用のstyle要素が存在しない場合は作成
    if (!document.getElementById('custom-datatable-css')) {
      const style = document.createElement('style');
      style.id = 'custom-datatable-css';
      style.textContent = `
        #employee-table {
          margin: 0 auto;
          border-collapse: collapse;
        }
        #employee-table thead th {
          text-align: center !important;
          white-space: nowrap;
        }
        #employee-table td {
          text-align: center !important;
          padding: 8px;
          font-family: monospace;
        }
        #employee-table td .cell {
          display: inline-block;
          white-space: nowrap;
          font-family: monospace;
        }

        #employee-table td.name-cell {
          text-align: center !important;
        }
        #employee-table td.name-cell .cell {
          text-align: left !important;
        }
        #employee-table td.name-cell .cell.dash-value {
          text-align: center !important;
        }

        #employee-table td.tenure-cell {
          text-align: center !important;
        }
        #employee-table td.tenure-cell .cell {
          text-align: right !important;
        }
        #employee-table td.tenure-cell .cell.dash-value {
          text-align: center !important;
        }

        #employee-table thead th.sorting_asc::after { content: ' ↑'; color: #007bff; }
        #employee-table thead th.sorting_desc::after { content: ' ↓'; color: #007bff; }
        #employee-table thead th.sorting::after { content: ' ↕'; color: #ccc; }
      `;
      document.head.appendChild(style);
    }

    // カスタマイズビューのHTMLを作成
    const tableHtml = `
      <table id="employee-table" class="display" style="width:100%">
        <thead>
          <tr>
            <th>従業員番号</th>
            <th>氏名</th>
            <th>入社日</th>
            <th>勤続年数</th>
            <th>生年月日</th>
            <th>年齢</th>
          </tr>
        </thead>
        <tbody>
          ${tableData.map(row => `
            <tr>
              <td><div class="cell">${row.employeeNumberLink}</div></td>
              <td class="name-cell"><div class="cell ${row.name === '-' ? 'dash-value' : ''}">${row.name}</div></td>
              <td><div class="cell">${row.hireDate || '-'}</div></td>
              <td class="tenure-cell"><div class="cell ${row.tenureHTML === '-' ? 'dash-value' : ''}">${row.tenureHTML}</div></td>
              <td><div class="cell">${row.birthDate || '-'}</div></td>
              <td><div class="cell">${row.age}</div></td>
            </tr>
          `).join('')}
        </tbody>
      </table>
    `;
    container.innerHTML = tableHtml;

    // 既にDataTablesのインスタンスが存在する場合は破棄
    if ($.fn.DataTable.isDataTable('#employee-table')) {
      $('#employee-table').DataTable().destroy();
    }

    // DataTablesの設定
    $('#employee-table').DataTable({
      // 日本語化ファイルのURL
      language: {
        url: 'https://cdn.datatables.net/plug-ins/1.13.7/i18n/ja.json'
      },
      pageLength: 25, // 1ページあたりの表示件数
      order: [[0, 'asc']], // 従業員番号の昇順でソート
      columnDefs: [
        { targets: [3], type: 'num' } // 勤続年数列を数値型として扱う
      ],
      responsive: true // レスポンシブウェブデザインを有効にする
    });

    // 最も幅が広いセルに合わせて各列の最小幅を設定 
    const table = document.querySelector('#employee-table');
    if (!table) return;
    const rows = Array.from(table.rows);
    if (rows.length === 0) return;
    const columnCount = rows[0].cells.length;
    const maxWidths = new Array(columnCount).fill(0);
    rows.forEach(row => {
      Array.from(row.cells).forEach((cell, i) => {
        const div = cell.querySelector('.cell');
        if (!div) return;
        const width = div.getBoundingClientRect().width;
        maxWidths[i] = Math.max(maxWidths[i], width);
      });
    });
    rows.forEach(row => {
      Array.from(row.cells).forEach((cell, i) => {
        const div = cell.querySelector('.cell');
        if (!div) return;
        div.style.minWidth = `${maxWidths[i]}px`;
      });
    });

    return event;
  });
})();
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?