この勤務時間管理は、時間設定に基づいて拘束時間や残業時間、深夜加給時間などを計算して各賃金を算出する簡易型のシステムです。また、この仕様はメモリ管理するローカルストレージやサーバーを介していません。端末上のブラウザで動作する環境なので、追加の構築などはご自身でアレンジして下さい。

仕様
1.氏名入力計算結果
2.日給入力1.拘束時間
3.時間給入力2.残業時間
4.深夜加給入力3.残業代
5.基本時間 開始~終了時間4.深夜加給額
6.就業時間 開始~終了時間5.消費税額
7.消費税入力6.合計支払額
8.データクリア7.各種総合計額
9.印刷
ファイル
index.html main.js styles.css
ご利用について
コードについては、修正・変更・削除・追加・デザイン変更等など、使用の際に特別な条件は一切ありません。また、動作確認は、ある程度確認していますがコピー&ペーストなどで不具合が有りましても、作者は責任を負う事は致しません。
※そのままご使用される場合は、どこか片隅に小さくMOMOPLANと書いて頂くと嬉しいです。
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>勤務時間管理</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <h1><FONT color="#000033">【勤務時間管理】</FONT></h1>
    <form id="workForm">
        <label>氏 名: <input type="text" id="name" required></label>
        <label>日 給: <input type="number" id="dailyPay"></label><br>
        <label>時間給: <input type="number" id="hourlyPay"></label>
        <label>深夜給: 
            <select id="nightOption">
                <option value="none">なし</option>
                <option value="yes">あり</option>
            </select>
        </label><br>
        <div id="nightInputs" style="display: none;">
            <label>深夜加給時間 開始: <input type="time" id="nightStartTime"></label>
            <label>終了: <input type="time" id="nightEndTime"></label><br>
            <label>深夜加給額 (¥): <input type="number" id="nightPayRate"></label><br>
        </div>
        <label>基本時間 開始: <input type="time" id="basicStartTime" required></label>
        <label>終了: <input type="time" id="basicEndTime" required></label>
        <label>就業時間 開始: <input type="time" id="workStartTime" required></label>
        <label>終了: <input type="time" id="workEndTime" required></label><br>
        
        <label>消費税: 
            <select id="taxRate"></select>
        </label>
        <button type="button" id="cal" onclick="calculateAndDisplay()">計算</button>
    </form>

    <table id="resultTable">
        <thead>
            <tr>
                <th>氏名</th>
                <th>基本開始時間</th>
                <th>基本終了時間</th>
                <th>就業開始時間</th>
                <th>就業終了時間</th>
                <th>拘束時間</th>
                <th>残業時間</th>
                <th>残業代</th>
                <th>深夜加給額</th>
                <th>消費税額</th>
                <th>合計支払額</th>
            </tr>
        </thead>
        <tfoot>
            <tr>
                <td colspan="5">合計</td>
                <td id="totalWorkHours">0時間0分</td>
                <td id="totalOvertimeHours">0時間0分</td>
                <td id="totalOvertimePay">¥0</td>
                <td id="totalNightPay">¥0</td>
                <td id="totalTaxAmount">¥0</td>
                <td id="finalTotalPay">¥0</td>
            </tr>
        </tfoot>
        <button id="clearButton">データをクリア</button>
        <tbody id="recordTableBody">
        <tbody>
        </tbody>
    </table>
    <button id="printButton">印刷</button>
    <script src="main.js"></script>
</body>
</html>
JS
// DOMの初期設定
document.addEventListener("DOMContentLoaded", () => {
    document.getElementById('nightOption')?.addEventListener('change', (event) => {
        const nightInputs = document.getElementById('nightInputs');
        if (nightInputs) {
            nightInputs.style.display = event.target.value === 'yes' ? 'block' : 'none';
        }
    });
});

window.addEventListener('beforeunload', (event) => {
    event.preventDefault();
});

document.getElementById('clearButton').addEventListener('click', () => {
    const tableBody = document.querySelector('#recordTableBody');
    if (tableBody) {
        tableBody.innerHTML = ''; // テーブル内容をクリア
        alert('データがクリアされました!');
    } else {
        console.error('#recordTableBodyが見つかりません');
    }
});

// 時間フォーマット関数
function formatTime(minutes) {
    const hours = Math.floor(minutes / 60);
    const mins = minutes % 60;
    return `${hours}時間${mins}分`;
}

// 金額フォーマット関数
function formatCurrency(amount) {
    return amount > 0 
        ? amount.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' }).replace("¥", "¥")
        : "¥0";
}

// 合計オブジェクト初期化
let totals = {
    workMinutes: 0,
    overtimeMinutes: 0,
    overtimePay: 0,
    nightPay: 0,
    taxAmount: 0,
    totalPay: 0,
};

// 合計計算関数
function updateTotals(workMinutes, overtimeMinutes, overtimePay, nightPay, taxAmount, finalTotal) {
    // 各項目を累積
    totals.workMinutes += workMinutes;
    totals.overtimeMinutes += overtimeMinutes;
    totals.overtimePay += overtimePay;
    totals.nightPay += nightPay;
    totals.taxAmount += taxAmount;
    totals.totalPay += finalTotal;

    // 合計欄を更新
    document.getElementById('totalWorkHours').textContent = formatTime(totals.workMinutes);
    document.getElementById('totalOvertimeHours').textContent = formatTime(totals.overtimeMinutes);
    document.getElementById('totalOvertimePay').textContent = formatCurrency(totals.overtimePay);
    document.getElementById('totalNightPay').textContent = formatCurrency(totals.nightPay);
    document.getElementById('totalTaxAmount').textContent = formatCurrency(totals.taxAmount);
    document.getElementById('finalTotalPay').textContent = formatCurrency(totals.totalPay);
}

const taxRateElement = document.getElementById('taxRate');
const taxRate = Number(taxRateElement?.value) || 0; // 消費税率を取得
const taxRateSelect = document.getElementById('taxRate');
if (taxRateSelect) {
    for (let i = 0; i <= 30; i++) {
        const option = document.createElement('option');
        option.value = i; // 各値を設定
        option.textContent = i === 0 ? "なし" : `${i}%`;
        taxRateSelect.appendChild(option);
    }
}

// メイン処理関数
function calculateAndDisplay() {
    // 必須要素の取得
    const table = document.getElementById('resultTable').querySelector('tbody');
    const name = document.getElementById('name').value;
    const dailyPay = Number(document.getElementById('dailyPay').value) || 0;
    const hourlyPay = Number(document.getElementById('hourlyPay').value) || 0;
    const taxRate = Number(document.getElementById('taxRate').value) || 0;
    const basicStartTime = document.getElementById('basicStartTime').value.trim();
    const basicEndTime = document.getElementById('basicEndTime').value.trim();
    const workStartTime = document.getElementById('workStartTime').value.trim();
    const workEndTime = document.getElementById('workEndTime').value.trim();

    if (!name || !basicStartTime || !basicEndTime || !workStartTime || !workEndTime) {
        alert("すべての入力フィールドを正しく入力してください。");
        return;
    }

// 基本勤務時間(例: 8時間)
const basicStartDate = new Date(`2025-01-01T${basicStartTime}:00`);
const basicEndDate = new Date(`2025-01-01T${basicEndTime}:00`);
const workStartDate = new Date(`2025-01-01T${workStartTime}:00`);
let workEndDate = new Date(`2025-01-01T${workEndTime}:00`);

// 終了時間が開始時間より前なら翌日扱い
if (workEndDate < workStartDate) {
    workEndDate.setDate(workEndDate.getDate() + 1);
}

// 基本勤務時間と実際の勤務時間を計算
const basicMinutes = (basicEndDate - basicStartDate) / (1000 * 60); // 基本勤務時間(分)
const workMinutes = (workEndDate - workStartDate) / (1000 * 60); // 実際の勤務時間(分)
const overtimeMinutes = Math.max(0, workMinutes - basicMinutes); // 残業時間(分)


    // 深夜加給
    let nightPay = 0;
    const nightOption = document.getElementById('nightOption').value;
    if (nightOption === "yes") {
        const nightStart = document.getElementById('nightStartTime').value;
        const nightEnd = document.getElementById('nightEndTime').value;
        const nightRate = Number(document.getElementById('nightPayRate').value) || 0;

        if (nightStart && nightEnd) {
            const nightStartTime = new Date(`2025-01-01T${nightStart}`);
            const nightEndTime = new Date(`2025-01-01T${nightEnd}`);
            if (nightEndTime < nightStartTime) nightEndTime.setDate(nightEndTime.getDate() + 1);
            const nightMinutes = Math.max(0, (nightEndTime - nightStartTime) / (1000 * 60));
            nightPay = Math.ceil(nightMinutes / 60 * nightRate);
        }
    }

    // 金額計算
    const overtimePay = Math.ceil(overtimeMinutes / 5) * 5 * (hourlyPay / 60);
    const totalPay = dailyPay + overtimePay + nightPay;
    const taxAmount = taxRate > 0 ? Math.ceil(totalPay * (taxRate / 100)) : 0;
    const finalTotal = totalPay + taxAmount;

 
if (!table) {
    console.error("テーブルの <tbody> が見つかりません。HTMLを確認してください。");
    return;
}

// 新しい行を末尾に追加
const rows = table.rows.length; // 行数を取得
const row = table.insertRow(rows); // 新しい行を正しく追加
row.innerHTML = `
    <td>${name}</td>
    <td>${basicStartTime}</td>
    <td>${basicEndTime}</td>
    <td>${workStartTime}</td>
    <td>${workEndTime}</td>
    <td>${formatTime(workMinutes)}</td>
    <td>${formatTime(overtimeMinutes)}</td>
    <td>${formatCurrency(overtimePay)}</td>
    <td>${formatCurrency(nightPay)}</td>
    <td>${formatCurrency(taxAmount)}</td>
    <td>${formatCurrency(finalTotal)}</td>
`;

let tempData = []; // 一時的にデータを格納する配列

// データを追加する例
function addRecord(record) {
    tempData.push(record); // データを一時配列に追加
    displayRecords(); // テーブルを再描画
}

// 表示関数の修正例
function displayRecords() {
    const tableBody = document.querySelector('#recordTableBody');
    if (!tableBody) {
        console.error('#recordTableBodyが見つかりません');
        return;
    }
    tableBody.innerHTML = ''; // 表示をクリア

    tempData.forEach((record, index) => {
        const row = document.createElement('tr');
        row.innerHTML = `
            <td>${record.name || '---'}</td>
            <td>${record.basicStartTime || '---'}</td>
            <td>${record.basicEndTime || '---'}</td>
            <td>${record.workStartTime || '---'}</td>
            <td>${record.workEndTime || '---'}</td>
            <td>${record.workHours || '---'}</td>
            <td>${record.overtimeHours || '---'}</td>
            <td>${record.overtimePay || '¥0'}</td>
            <td>${record.nightPay || '¥0'}</td>
            <td>${record.taxAmount || '¥0'}</td>
            <td>${record.totalPay || '¥0'}</td>
        `;
        tableBody.appendChild(row);
    });
}
    updateTotals(workMinutes, overtimeMinutes, overtimePay, nightPay, taxAmount, finalTotal);
}

document.getElementById('printButton').addEventListener('click', () => {
    const table = document.getElementById('resultTable');

    if (!table) {
        console.error('#resultTable が見つかりません。HTMLを確認してください。');
        return;
    }

    // 印刷専用の新しいウィンドウを開く
    const printWindow = window.open('', '', 'width=800,height=600');
    printWindow.document.write('<html><head><title>印刷プレビュー</title>');
    printWindow.document.write('<style>table { width: 100%; border-collapse: collapse; text-align: center; } th, td { border: 1px solid #000; padding: 8px; }</style>');
    printWindow.document.write('</head><body>');
    printWindow.document.write(table.outerHTML); // テーブルをウィンドウに書き込む
    printWindow.document.write('</body></html>');
    printWindow.document.close();

    // 印刷を実行
    printWindow.print();

    // 印刷後ウィンドウを閉じる
    printWindow.close();
});
CSS
body {
    font-family: Arial, sans-serif;
    text-align: center; 
    background-image: url("s-brick008.gif");
  }

/* ヘッダーのスタイル */
header {
    background-color: #333; /* 背景色 */
    color: white; /* 文字色 */
    padding: 15px 20px; /* パディング */
    font-size: 24px; /* フォントサイズ */
    text-align: center; /* 中央揃え */
}

header:focus {
    outline: none; /* フォーカス時の枠線を無効化 */
    background-color: inherit; /* 元の背景色を維持 */
    color: inherit; /* 元の文字色を維持 */
}

header h1 {
    margin: 0; /* タイトルの余白をリセット */
    font-weight: normal; /* フォントの太さを通常に設定 */
}

#cal{
    background-color: #5b39f1; /* 背景色 */
    color: white; /* 文字色 */
    border: none; /* ボーダーを削除 */
    padding: 6px 20px; /* パディング */
    text-align: center; /* テキストの中央揃え */
    font-size: 16px; /* フォントサイズ */
    border-radius: 5px; /* 角を丸める */
    cursor: pointer; /* ポインター変更 */
    transition: background-color 0.3s, box-shadow 0.3s; /* ホバー時のトランジション */
}

#printButton{
    background-color: #3fd6fc; /* 背景色 */
    color: rgb(43, 40, 40); /* 文字色 */
    border: none; /* ボーダーを削除 */
    padding: 6px 20px; /* パディング */
    text-align: center; /* テキストの中央揃え */
    font-size: 16px; /* フォントサイズ */
    border-radius: 5px; /* 角を丸める */
    cursor: pointer; /* ポインター変更 */
    transition: background-color 0.3s, box-shadow 0.3s; /* ホバー時のトランジション */
}

#clearButton{
    background-color: #f13995; /* 背景色 */
    color: white; /* 文字色 */
    border: none; /* ボーダーを削除 */
    padding: 6px 20px; /* パディング */
    text-align: center; /* テキストの中央揃え */
    font-size: 16px; /* フォントサイズ */
    border-radius: 5px; /* 角を丸める */
    cursor: pointer; /* ポインター変更 */
    transition: background-color 0.3s, box-shadow 0.3s; /* ホバー時のトランジション */
}

/* ボタンのスタイル */
button {
    background-color: #4CAF50; /* 背景色 */
    color: white; /* 文字色 */
    border: none; /* ボーダーを削除 */
    padding: 10px 20px; /* パディング */
    text-align: center; /* テキストの中央揃え */
    font-size: 16px; /* フォントサイズ */
    border-radius: 5px; /* 角を丸める */
    cursor: pointer; /* ポインター変更 */
    transition: background-color 0.3s, box-shadow 0.3s; /* ホバー時のトランジション */
}

button:hover {
    background-color: #45a049; /* ホバー時の背景色 */
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* ホバー時のシャドウ */
}

button:active {
    background-color: #3e8e41; /* クリック時の背景色 */
}

/* 深夜加給入力欄のスタイル */
#nightInputs {
    display: none; /* 初期状態は非表示 */
    margin: 15px 0; /* 上下の余白 */
    padding: 10px; /* 内部余白 */
    border: 1px solid #ccc; /* 枠線 */
    border-radius: 5px; /* 角を丸める */
    background-color: #f9f9f9; /* 背景色 */
}

/* テーブルスタイル */
table {
    width: 100%; /* テーブル幅を全体に拡張 */
    border-collapse: collapse; /* 枠線の重なりを防ぐ */
    margin-top: 20px; /* 上余白 */
}

table th, table td {
    border: 2px solid #2460ad; /* セルの枠線 */
    text-align: left; /* 左揃え */
    padding: 8px; /* セル内の余白 */
}

table th {
    background-color: #2af7c3; /* ヘッダー背景色 */
    color: rgb(0, 0, 0); /* ヘッダー文字色 */
}

/* ホバー時の行ハイライト */
table tr:hover {
    background-color: #f1f1f1; /* 行ホバー時の背景色 */
}

/* 入力欄の基本スタイル */
input[type="text"], input[type="number"], select {
    width: 250px; /* 固定幅を指定 */
    max-width: 100%; /* 最大幅を親要素に合わせる */
    padding: 8px; /* 内側余白 */
    margin: 10px 0; /* 上下の余白 */
    border: 1px solid #ccc; /* 枠線 */
    border-radius: 5px; /* 角を丸める */
    box-sizing: border-box; /* ボックスサイズを調整 */
    font-size: 16px; /* フォントサイズ */
}

/* フォーカス時のスタイル */
input:focus, select:focus {
    outline: none; /* フォーカス枠線を非表示 */
    border-color: #4CAF50; /* フォーカス時の枠線色 */
    box-shadow: 0 0 5px rgba(76, 175, 80, 0.5); /* フォーカス時の影 */
}

#finalTotalPay{
    background-color: #b6c7f7; /* 背景色 */
    color: rgb(20, 1, 37); /* 文字色 */
    padding: 6px 20px; /* パディング */
    text-align: center; /* テキストの中央揃え */
    font-size: 16px; /* フォントサイズ */
    border-radius: 5px; /* 角を丸める */
    cursor: pointer; /* ポインター変更 */
    transition: background-color 0.3s, box-shadow 0.3s; /* ホバー時のトランジション */
}

以上です・・・・・

勤務時間管理デモサイト