This commit is contained in:
admin
2026-05-22 23:05:25 +09:00
parent cea3016fbe
commit e8690d130b
23 changed files with 581 additions and 187 deletions

View File

@@ -2,9 +2,11 @@ from dotenv import load_dotenv
load_dotenv() load_dotenv()
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, RedirectResponse from fastapi.responses import JSONResponse, RedirectResponse, Response
from pydantic import ValidationError from pydantic import ValidationError
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from starlette.datastructures import MutableHeaders
from starlette.types import ASGIApp, Receive, Scope, Send
# FastAPI アプリケーション作成 # FastAPI アプリケーション作成
app = FastAPI( app = FastAPI(
@@ -12,13 +14,37 @@ app = FastAPI(
) )
class SecurityHeadersMiddleware:
"""キャッシュ禁止・セキュリティヘッダーを全レスポンスに付与ASGIネイティブ実装"""
def __init__(self, app: ASGIApp) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http":
await self.app(scope, receive, send)
return
async def send_with_headers(message):
if message["type"] == "http.response.start":
headers = MutableHeaders(scope=message)
headers["Cache-Control"] = "no-store, no-cache, must-revalidate, private"
headers["Pragma"] = "no-cache"
headers["Expires"] = "0"
headers["X-Frame-Options"] = "DENY"
headers["X-Content-Type-Options"] = "nosniff"
headers["X-XSS-Protection"] = "1; mode=block"
await send(message)
await self.app(scope, receive, send_with_headers)
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], allow_origins=["*"],
allow_credentials=False, allow_credentials=False,
allow_methods=["*"], allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"], allow_headers=["Content-Type", "Authorization"],
) )
# ───────────────────────── # ─────────────────────────

View File

@@ -12,6 +12,12 @@ class LoginRequest(BaseModel):
password: str password: str
class ChangePasswordRequest(BaseModel):
username: str
current_password: str
new_password: str
class LoginResponse(BaseModel): class LoginResponse(BaseModel):
id: int id: int
username: str username: str
@@ -66,39 +72,48 @@ async def login(request: LoginRequest):
@router.post("/register") @router.post("/register")
async def register(request: LoginRequest): async def register(request: LoginRequest):
"""新規ユーザー登録""" """新規ユーザー登録(無効化済み)"""
raise HTTPException(status_code=403, detail="新規ユーザー登録は現在受け付けていません")
@router.post("/change-password")
async def change_password(request: ChangePasswordRequest):
"""パスワード変更"""
if len(request.new_password) < 8:
raise HTTPException(status_code=400, detail="新しいパスワードは8文字以上である必要があります")
try: try:
if len(request.password) < 6:
raise HTTPException(status_code=400, detail="パスワードは6文字以上である必要があります")
conn = get_connection() conn = get_connection()
cur = conn.cursor() cur = conn.cursor()
# ユーザーが既に存在するか確認 cur.execute(
cur.execute("SELECT id FROM users WHERE username = %s", (request.username,)) "SELECT id, password FROM users WHERE username = %s AND is_active = TRUE",
if cur.fetchone(): (request.username,)
)
user = cur.fetchone()
if not user:
cur.close() cur.close()
conn.close() conn.close()
raise HTTPException(status_code=400, detail="このユーザー名は既に使用されています") raise HTTPException(status_code=401, detail="ユーザーが見つかりません")
# ユーザーを登録 if user["password"] != hash_password(request.current_password):
hashed_password = hash_password(request.password) cur.close()
conn.close()
raise HTTPException(status_code=401, detail="現在のパスワードが正しくありません")
new_hashed = hash_password(request.new_password)
cur.execute( cur.execute(
"INSERT INTO users (username, password) VALUES (%s, %s) RETURNING id, username", "UPDATE users SET password = %s WHERE username = %s",
(request.username, hashed_password) (new_hashed, request.username)
) )
new_user = cur.fetchone()
conn.commit() conn.commit()
cur.close() cur.close()
conn.close() conn.close()
return { return {"message": "パスワードを変更しました"}
"id": new_user["id"],
"username": new_user["username"],
"message": "ユーザー登録が完了しました"
}
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"登録処理エラー: {str(e)}") raise HTTPException(status_code=500, detail=f"パスワード変更エラー: {str(e)}")

View File

@@ -2,9 +2,11 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<title>科目マスタ</title> <title>科目マスタ</title>
<link rel="stylesheet" href="css/style.css" /> <link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
</head> </head>
<body> <body>
<h1>科目マスタ</h1> <h1>科目マスタ</h1>

324
frontend/css/mobile.css Normal file
View File

@@ -0,0 +1,324 @@
/* ==================================================================
mobile.css — iOS Safari・Android Chrome レスポンシブ対応
ブレークポイント: 767px 以下をモバイルとして扱う
PC のレイアウトは変更しない (screen media query で完全に隔離)
================================================================== */
/* ── iOS Safari フォントブースト無効化 ── */
html {
-webkit-text-size-adjust: 100%;
}
@media screen and (max-width: 767px) {
/* ── body 余白を縮小 ── */
body {
padding: 12px !important;
margin: 8px !important;
}
/* ── h1・h2 フォントサイズ ── */
h1 { font-size: 1.35rem !important; }
h2 { font-size: 1.1rem !important; }
/* ================================================================
テーブル: 横スクロール
会計・給与テーブルは列数が多く、横スクロールが最適解
================================================================ */
table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
max-width: 100%;
}
/* テーブルを包む overflow:hidden ラッパーも解放する */
.doc-table-wrap {
overflow-x: auto !important;
}
/* ================================================================
フォーム入力: 16px で iOS の自動ズームを防ぐ
================================================================ */
input[type="text"],
input[type="date"],
input[type="number"],
input[type="password"],
input[type="email"],
input[type="tel"],
select,
textarea {
font-size: 16px !important;
max-width: 100%;
box-sizing: border-box;
}
/* ================================================================
ボタン: タップ領域を 44px 以上に (iOS HIG 推奨)
================================================================ */
button,
input[type="submit"],
input[type="button"],
.btn,
.upload-btn,
.back-link,
.logout-btn,
.change-pw-btn {
min-height: 44px;
touch-action: manipulation;
}
/* ================================================================
index.html
================================================================ */
/* 2カラムカードグリッド → 1カラム */
.grid {
grid-template-columns: 1fr !important;
}
/* ヘッダー: タイトルとユーザー情報を縦積み */
header {
flex-direction: column !important;
align-items: flex-start !important;
gap: 10px !important;
}
header h1 {
font-size: 18px !important;
}
.user-info {
flex-wrap: wrap !important;
width: 100% !important;
gap: 8px !important;
font-size: 13px !important;
}
/* カード内ボタン群: 縦積み・margin-left をリセット */
.card p:last-child {
display: flex !important;
flex-direction: column !important;
gap: 8px !important;
}
.btn {
margin-left: 0 !important;
display: block !important;
text-align: center !important;
box-sizing: border-box !important;
}
/* パスワード変更モーダル: 画面幅に合わせる */
.modal-box {
width: 92vw !important;
max-width: 92vw !important;
padding: 20px !important;
}
/* ================================================================
payroll.html (給与システムメニュー)
================================================================ */
.menu-grid {
grid-template-columns: 1fr !important;
}
.menu-card {
padding: 20px !important;
}
/* ================================================================
journal-entry.html / journal-edit.html (仕訳入力)
================================================================ */
/* 検索フォーム: 2カラムグリッド → 1カラム */
.search-form-grid {
grid-template-columns: 1fr !important;
gap: 12px !important;
}
.search-form-row,
.search-form-row-full {
flex-direction: column !important;
align-items: flex-start !important;
gap: 4px !important;
}
.search-form-row label,
.search-form-row-full label {
min-width: unset !important;
width: 100%;
}
.search-form-row input,
.search-form-row select,
.search-form-row-full input,
.search-form-row-full select {
width: 100% !important;
box-sizing: border-box !important;
}
/* 検索ボタン群 */
.search-buttons {
flex-wrap: wrap !important;
gap: 6px !important;
}
.search-buttons button {
flex: 1 1 auto !important;
min-width: 100px !important;
}
/* .row: 日付・摘要・勘定科目などの入力行 */
.row {
flex-direction: column !important;
align-items: flex-start !important;
gap: 6px !important;
width: 100%;
}
.row label {
min-width: unset !important;
}
.row input,
.row select {
width: 100% !important;
box-sizing: border-box !important;
}
/* ================================================================
trial-balance.html (試算表)
テキスト中心の inline 検索フォームを縦積みにする
================================================================ */
.search-form {
text-align: left !important;
padding: 12px !important;
}
/* trial-balance の検索フォームは label/input が直接子要素 */
.search-form > label {
display: block !important;
margin: 6px 0 2px !important;
}
.search-form > input[type="text"],
.search-form > input[type="date"],
.search-form > input[type="number"] {
display: block !important;
width: 100% !important;
margin: 0 0 8px 15px !important;
box-sizing: border-box !important;
}
.search-form > button {
display: inline-block !important;
margin: 4px 4px 0 0 !important;
width: auto !important;
min-width: 80px;
}
/* ================================================================
nenkin-portal.html (社会保険電子文書)
================================================================ */
.page-header {
flex-direction: column !important;
align-items: flex-start !important;
gap: 10px !important;
}
.upload-zone {
padding: 20px 16px !important;
}
/* フィルターバー: wrapping は既存スタイルで対応済み、幅を補完 */
.filter-bar {
gap: 8px !important;
}
.filter-select {
width: 100% !important;
box-sizing: border-box !important;
}
/* ================================================================
payroll-employees.html / payroll-settings.html (タブ UI)
================================================================ */
.nav-tabs {
flex-wrap: wrap !important;
justify-content: flex-start !important;
gap: 6px !important;
border-bottom: none !important;
}
.nav-tab {
padding: 8px 14px !important;
font-size: 0.85rem !important;
border-radius: 6px !important;
}
/* container の余白縮小 */
.container {
margin: 10px !important;
padding: 16px 12px !important;
border-radius: 10px !important;
}
/* ================================================================
payroll-settings.html (2/3カラムフォームグリッド → 1カラム)
================================================================ */
.form-row {
display: flex !important;
flex-direction: column !important;
gap: 10px !important;
}
.form-row-3 {
display: flex !important;
flex-direction: column !important;
gap: 10px !important;
}
.form-group {
width: 100%;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100% !important;
box-sizing: border-box !important;
}
/* ================================================================
payroll-calculation.html (月次給与計算)
================================================================ */
/* フィルター行のラベルを縦積み */
.filters {
display: flex !important;
flex-direction: column !important;
gap: 8px !important;
padding: 12px !important;
}
.filters label {
display: flex !important;
flex-direction: column !important;
gap: 4px !important;
margin-right: 0 !important;
width: 100%;
}
.filters input,
.filters select {
width: 100% !important;
box-sizing: border-box !important;
}
}

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>e-Gov 電子送達 NJTS</title> <title>e-Gov 電子送達 NJTS</title>
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
* { * {
box-sizing: border-box; box-sizing: border-box;

View File

@@ -2,8 +2,10 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NJTS 会計システム - メイン</title> <title>NJTS 会計システム - メイン</title>
<link rel="stylesheet" href="css/style.css" /> <link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<style> <style>
body { body {
@@ -79,6 +81,50 @@
.logout-btn:hover { .logout-btn:hover {
background: #c82333; background: #c82333;
} }
.change-pw-btn {
padding: 8px 12px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
}
.change-pw-btn:hover { background: #545b62; }
/* パスワード変更モーダル */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-overlay.active { display: flex; }
.modal-box {
background: white;
border-radius: 10px;
padding: 32px;
width: 360px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.modal-box h3 { margin: 0 0 20px; }
.modal-box .form-group { margin-bottom: 14px; }
.modal-box label { display: block; margin-bottom: 5px; font-size: 13px; font-weight: 600; color: #333; }
.modal-box input[type="password"] {
width: 100%; padding: 10px; border: 1px solid #ddd;
border-radius: 5px; font-size: 14px; box-sizing: border-box;
}
.modal-box .btn-row { display: flex; gap: 10px; margin-top: 20px; }
.modal-box .btn-row button { flex: 1; padding: 10px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; }
.modal-box .btn-submit { background: #007bff; color: white; }
.modal-box .btn-submit:hover { background: #0056b3; }
.modal-box .btn-cancel { background: #e9ecef; color: #333; }
.modal-box .btn-cancel:hover { background: #d0d5db; }
.modal-msg { padding: 8px 12px; border-radius: 4px; margin-bottom: 12px; display: none; font-size: 13px; }
.modal-msg.error { background: #fee; color: #c33; border: 1px solid #fcc; }
.modal-msg.success { background: #efe; color: #3a3; border: 1px solid #cfc; }
</style> </style>
</head> </head>
<body> <body>
@@ -86,10 +132,35 @@
<h1>NJTS</h1> <h1>NJTS</h1>
<div class="user-info"> <div class="user-info">
<span>ユーザー: <strong id="username">読み込み中...</strong></span> <span>ユーザー: <strong id="username">読み込み中...</strong></span>
<button class="change-pw-btn" onclick="openChangePw()">パスワード変更</button>
<button class="logout-btn" onclick="logout()">ログアウト</button> <button class="logout-btn" onclick="logout()">ログアウト</button>
</div> </div>
</header> </header>
<!-- パスワード変更モーダル -->
<div class="modal-overlay" id="changePwModal">
<div class="modal-box">
<h3>🔒 パスワード変更</h3>
<div class="modal-msg" id="changePwMsg"></div>
<div class="form-group">
<label>現在のパスワード</label>
<input type="password" id="cpCurrent" placeholder="現在のパスワード" />
</div>
<div class="form-group">
<label>新しいパスワード8文字以上</label>
<input type="password" id="cpNew" placeholder="新しいパスワード" />
</div>
<div class="form-group">
<label>新しいパスワード(確認)</label>
<input type="password" id="cpConfirm" placeholder="もう一度入力" />
</div>
<div class="btn-row">
<button class="btn-submit" onclick="submitChangePw()">変更する</button>
<button class="btn-cancel" onclick="closeChangePw()">キャンセル</button>
</div>
</div>
</div>
<main class="grid"> <main class="grid">
<section class="card"> <section class="card">
<h2>会計システム</h2> <h2>会計システム</h2>
@@ -142,6 +213,56 @@
} }
}); });
// パスワード変更モーダル
function openChangePw() {
document.getElementById("cpCurrent").value = "";
document.getElementById("cpNew").value = "";
document.getElementById("cpConfirm").value = "";
const msg = document.getElementById("changePwMsg");
msg.style.display = "none";
document.getElementById("changePwModal").classList.add("active");
}
function closeChangePw() {
document.getElementById("changePwModal").classList.remove("active");
}
function showChangePwMsg(text, isError) {
const msg = document.getElementById("changePwMsg");
msg.textContent = text;
msg.className = "modal-msg " + (isError ? "error" : "success");
msg.style.display = "block";
}
async function submitChangePw() {
const current = document.getElementById("cpCurrent").value;
const newPw = document.getElementById("cpNew").value;
const confirm = document.getElementById("cpConfirm").value;
const user = getCurrentUser();
if (!current || !newPw || !confirm) {
showChangePwMsg("すべての項目を入力してください", true); return;
}
if (newPw.length < 8) {
showChangePwMsg("新しいパスワードは8文字以上にしてください", true); return;
}
if (newPw !== confirm) {
showChangePwMsg("新しいパスワードが一致しません", true); return;
}
try {
const res = await fetch("/api/users/change-password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: user.username, current_password: current, new_password: newPw })
});
const data = await res.json();
if (res.ok) {
showChangePwMsg("パスワードを変更しました。再ログインしてください", false);
setTimeout(() => { logout(); }, 2000);
} else {
showChangePwMsg(data.detail || "変更に失敗しました", true);
}
} catch (e) {
showChangePwMsg("エラー: " + e.message, true);
}
}
// dropdown toggle // dropdown toggle
const toggle = document.getElementById("accountingToggle"); const toggle = document.getElementById("accountingToggle");
const menu = document.getElementById("accountingMenu"); const menu = document.getElementById("accountingMenu");

View File

@@ -2,7 +2,9 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<link rel="stylesheet" href="/css/mobile.css" />
<title>修正仕訳入力</title> <title>修正仕訳入力</title>
<style> <style>
body { body {

View File

@@ -2,7 +2,9 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<link rel="stylesheet" href="/css/mobile.css" />
<title>仕訳入力</title> <title>仕訳入力</title>
<style> <style>
body { body {

View File

@@ -2,7 +2,9 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<link rel="stylesheet" href="/css/mobile.css" />
<title>仕訳一覧</title> <title>仕訳一覧</title>
<style> <style>
body { body {

View File

@@ -2,7 +2,9 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<link rel="stylesheet" href="/css/mobile.css" />
<title>仕訳詳細</title> <title>仕訳詳細</title>
<style> <style>
body { body {

View File

@@ -2,9 +2,11 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<title>仕訳入力</title> <title>仕訳入力</title>
<link rel="stylesheet" href="css/style.css" /> <link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
</head> </head>
<body> <body>
<h1>仕訳入力</h1> <h1>仕訳入力</h1>

View File

@@ -1,41 +1,59 @@
/** /**
* 认证和会话管理 * 認証・セッション管理
*/ */
// 检查用户是否已登录 // ──────────────────────────────────────
// ページ描画前に即座に認証確認(フラッシュ防止)
// ──────────────────────────────────────
(function () {
if (!window.location.pathname.includes("login.html")) {
if (!localStorage.getItem("currentUser")) {
// ページが見えてしまわないよう即座に非表示にしてからリダイレクト
document.documentElement.style.visibility = "hidden";
window.location.replace("/login.html");
}
}
})();
// bfcache戻る/進むボタン)対応
// ブラウザがキャッシュからページを復元した場合も認証を再確認する
window.addEventListener("pageshow", function (event) {
if (!window.location.pathname.includes("login.html")) {
if (!localStorage.getItem("currentUser")) {
// bfcacheから復元されたページを即座に非表示にしてリダイレクト
document.documentElement.style.visibility = "hidden";
window.location.replace("/login.html");
}
}
});
function checkAuth() { function checkAuth() {
const user = localStorage.getItem("currentUser"); const user = localStorage.getItem("currentUser");
if (!user) { if (!user) {
// 用户未登录,重定向到登录页面 window.location.replace("/login.html");
window.location.href = "/login.html";
return false; return false;
} }
return true; return true;
} }
// 获取当前登录的用户信息
function getCurrentUser() { function getCurrentUser() {
const user = localStorage.getItem("currentUser"); const user = localStorage.getItem("currentUser");
return user ? JSON.parse(user) : null; return user ? JSON.parse(user) : null;
} }
// 保存用户信息(登录时使用)
function setCurrentUser(userData) { function setCurrentUser(userData) {
localStorage.setItem("currentUser", JSON.stringify(userData)); localStorage.setItem("currentUser", JSON.stringify(userData));
} }
// 注销用户
function logout() { function logout() {
localStorage.removeItem("currentUser"); localStorage.removeItem("currentUser");
window.location.href = "/login.html"; sessionStorage.clear();
// replace() を使うことでログイン前の履歴に戻れなくなる
window.location.replace("/login.html");
} }
// 在页面加载时检查认证(仅在非登录页面使用)
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const currentPage = window.location.pathname; if (!window.location.pathname.includes("login.html")) {
// 排除登录页面的检查
if (!currentPage.includes("login.html")) {
checkAuth(); checkAuth();
} }
}); });

View File

@@ -2,9 +2,11 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<title>元帳</title> <title>元帳</title>
<link rel="stylesheet" href="css/style.css" /> <link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
</head> </head>
<body> <body>
<h1 id="account-title">元帳</h1> <h1 id="account-title">元帳</h1>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NJTS 会計システム - ログイン</title> <title>NJTS 会計システム - ログイン</title>
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -108,16 +109,6 @@
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
} }
.btn-register {
background: transparent;
color: #667eea;
border: 2px solid #667eea;
}
.btn-register:hover {
background: #f0f0ff;
}
.message { .message {
padding: 12px; padding: 12px;
border-radius: 5px; border-radius: 5px;
@@ -138,32 +129,6 @@
border: 1px solid #cfc; border: 1px solid #cfc;
} }
.toggle-form {
text-align: center;
margin-top: 20px;
font-size: 14px;
color: #666;
}
.toggle-form a {
color: #667eea;
text-decoration: none;
cursor: pointer;
font-weight: 500;
}
.toggle-form a:hover {
text-decoration: underline;
}
.form {
display: none;
}
.form.active {
display: block;
}
.loading { .loading {
display: none; display: none;
text-align: center; text-align: center;
@@ -181,7 +146,7 @@
</div> </div>
<!-- ログインフォーム --> <!-- ログインフォーム -->
<div id="loginForm" class="form active"> <div id="loginForm">
<div id="messageLogin" class="message"></div> <div id="messageLogin" class="message"></div>
<div class="form-group"> <div class="form-group">
@@ -208,47 +173,8 @@
<button class="btn-login" onclick="handleLogin()">ログイン</button> <button class="btn-login" onclick="handleLogin()">ログイン</button>
</div> </div>
<div class="toggle-form">
新規登録?
<a onclick="toggleForm()">アカウント作成</a>
</div>
<div class="loading" id="loginLoading">処理中...</div> <div class="loading" id="loginLoading">処理中...</div>
</div> </div>
<!-- 登録フォーム -->
<div id="registerForm" class="form">
<div id="messageRegister" class="message"></div>
<div class="form-group">
<label for="regUsername">ユーザー名</label>
<input
type="text"
id="regUsername"
placeholder="ユーザー名を入力"
required
/>
</div>
<div class="form-group">
<label for="regPassword">パスワード</label>
<input
type="password"
id="regPassword"
placeholder="6文字以上で設定"
required
/>
</div>
<div class="button-group">
<button class="btn-login" onclick="handleRegister()">登録</button>
<button class="btn-register" onclick="toggleForm()">
キャンセル
</button>
</div>
<div class="loading" id="registerLoading">処理中...</div>
</div>
</div> </div>
<script> <script>
@@ -330,82 +256,19 @@
} }
} }
async function handleRegister() { // EnterキーでログインできるようにEnterキーをバインド
const username = document.getElementById("regUsername").value;
const password = document.getElementById("regPassword").value;
if (!username || !password) {
showMessage(
"messageRegister",
"ユーザー名とパスワードを入力してください",
true,
);
return;
}
if (password.length < 6) {
showMessage(
"messageRegister",
"パスワードは6文字以上である必要があります",
true,
);
return;
}
const loadingEl = document.getElementById("registerLoading");
loadingEl.style.display = "block";
try {
const response = await fetch("/api/users/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
});
const data = await response.json();
if (response.ok) {
showMessage(
"messageRegister",
"登録が完了しました。ログインしてください",
false,
);
setTimeout(() => {
toggleForm();
document.getElementById("loginUsername").value = username;
document.getElementById("loginPassword").value = "";
}, 2000);
} else {
showMessage(
"messageRegister",
data.detail || "登録に失敗しました",
true,
);
}
} catch (error) {
showMessage(
"messageRegister",
"エラーが発生しました: " + error.message,
true,
);
} finally {
loadingEl.style.display = "none";
}
}
// Enterキーでログイン/登録
document.addEventListener("keypress", (e) => { document.addEventListener("keypress", (e) => {
if (e.key === "Enter") { if (e.key === "Enter") {
const loginForm = document.getElementById("loginForm"); handleLogin();
if (loginForm.classList.contains("active")) {
handleLogin();
} else {
handleRegister();
}
} }
}); });
// ログインページから後退ボタンで離脱できないようにする
// (ログアウト後に戻るボタンでメニューが見えてしまう問題の対策)
history.pushState(null, "", window.location.href);
window.addEventListener("popstate", function () {
history.pushState(null, "", window.location.href);
});
</script> </script>
</body> </body>
</html> </html>

View File

@@ -6,6 +6,7 @@
<title>社会保険電子文書ポータル</title> <title>社会保険電子文書ポータル</title>
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<link rel="stylesheet" href="css/style.css" /> <link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
body { body {
font-family: system-ui, -apple-system, "Yu Gothic UI", "Hiragino Kaku Gothic ProN", "メイリオ", sans-serif; font-family: system-ui, -apple-system, "Yu Gothic UI", "Hiragino Kaku Gothic ProN", "メイリオ", sans-serif;

View File

@@ -2,9 +2,11 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<title>期首残高入力</title> <title>期首残高入力</title>
<link rel="stylesheet" href="css/style.css" /> <link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
</head> </head>
<body> <body>
<h1>期首残高入力</h1> <h1>期首残高入力</h1>

View File

@@ -12,6 +12,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与管理 - 月次給与計算</title> <title>給与管理 - 月次給与計算</title>
<link rel="stylesheet" href="/css/style.css" /> <link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
.container { .container {
max-width: 1440px; max-width: 1440px;

View File

@@ -12,6 +12,7 @@
rel="stylesheet" rel="stylesheet"
/> />
<link rel="stylesheet" href="/css/style.css" /> <link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
body { body {
font-family: "Noto Sans JP", Arial, sans-serif; font-family: "Noto Sans JP", Arial, sans-serif;

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与管理 - 給与設定</title> <title>給与管理 - 給与設定</title>
<link rel="stylesheet" href="/css/style.css" /> <link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
.employee-select { .employee-select {
margin-bottom: 20px; margin-bottom: 20px;

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与管理 - 設定</title> <title>給与管理 - 設定</title>
<link rel="stylesheet" href="/css/style.css" /> <link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
.nav-tabs { .nav-tabs {
display: flex; display: flex;

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与管理システム</title> <title>給与管理システム</title>
<link rel="stylesheet" href="/css/style.css" /> <link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
.menu-grid { .menu-grid {
display: grid; display: grid;

View File

@@ -2,8 +2,11 @@
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<title>試算表</title> <title>試算表</title>
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
body { body {
font-family: "MS Gothic", "Meiryo", sans-serif; font-family: "MS Gothic", "Meiryo", sans-serif;

View File

@@ -9,6 +9,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与所得の源泉徴収票</title> <title>給与所得の源泉徴収票</title>
<link rel="stylesheet" href="/css/style.css" /> <link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<style> <style>
/* ===== 画面UI ===== */ /* ===== 画面UI ===== */
body { font-family: 'MS Gothic', 'Meiryo', sans-serif; } body { font-family: 'MS Gothic', 'Meiryo', sans-serif; }