|
<!DOCTYPE html> |
|
<html lang="fa" dir="rtl"> |
|
<head> |
|
<meta charset="utf-8" /> |
|
<meta name="viewport" content="width=device-width,initial-scale=1" /> |
|
<title>سامانه بازرسی و پلاک برداری از کارگاه ها — اجرا محلی با Fallback مختصات</title> |
|
<style> |
|
:root{ |
|
--p:#4361ee; |
|
--p2:#3f37c9; |
|
--p-light:#e0e7ff; |
|
--p-dark:#3a0ca3; |
|
--s:#2a9d8f; |
|
--w:#e63946; |
|
--y:#f77f00; |
|
--muted:#6c757d; |
|
--bg:#f8f9fa; |
|
--card:#ffffff; |
|
--text:#212529; |
|
--text-light:#6c757d; |
|
--border:#dee2e6; |
|
--shadow:0 4px 6px rgba(0,0,0,0.05), 0 1px 3px rgba(0,0,0,0.1), 0 0 0 1px rgba(255,255,255,0.05); |
|
--shadow-hover:0 10px 15px rgba(0,0,0,0.1), 0 4px 6px rgba(0,0,0,0.05); |
|
--shadow-card:0 10px 15px rgba(0,0,0,0.1), 0 4px 6px rgba(0,0,0,0.05); |
|
--shadow-modal:0 20px 25px rgba(0,0,0,0.15), 0 10px 10px rgba(0,0,0,0.04); |
|
--radius:16px; |
|
--transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|
--gradient-primary:linear-gradient(135deg, var(--p), var(--p2)); |
|
--gradient-secondary:linear-gradient(135deg, #667eea, #764ba2); |
|
--gradient-success:linear-gradient(135deg, #2a9d8f, #1d7a73); |
|
--gradient-danger:linear-gradient(135deg, #e63946, #d62828); |
|
--gradient-warning:linear-gradient(135deg, #f77f00, #fcbf49); |
|
--gradient-manager:linear-gradient(135deg, #1e3a8a, #1e40af, #2563eb); |
|
} |
|
|
|
*{box-sizing:border-box;margin:0;padding:0} |
|
|
|
body{ |
|
font-family:'Yekan Bakh', 'Tahoma', sans-serif; |
|
direction:rtl; |
|
margin:0; |
|
background:linear-gradient(165deg, #f5f7fa 0%, #e4efe9 100%); |
|
color:var(--text); |
|
line-height:1.6; |
|
overflow-x:hidden; |
|
min-height:100vh; |
|
} |
|
|
|
body.modal-open { overflow: hidden; } |
|
|
|
/* بهبود تایپوگرافی */ |
|
h1, h2, h3, h4, h5, h6 { |
|
font-weight:800; |
|
line-height:1.2; |
|
margin-bottom:0.5em; |
|
color:var(--p-dark); |
|
letter-spacing: -0.02em; |
|
} |
|
|
|
/* بهبود هدر */ |
|
header{ |
|
background:var(--gradient-primary); |
|
color:#fff; |
|
padding:24px 32px; |
|
display:flex; |
|
justify-content:space-between; |
|
align-items:center; |
|
box-shadow:var(--shadow-card); |
|
position:relative; |
|
z-index:10; |
|
border-bottom-left-radius: 24px; |
|
border-bottom-right-radius: 24px; |
|
overflow: hidden; |
|
} |
|
|
|
header::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
bottom: 0; |
|
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="none"/><path d="M0,0 L100,100 M100,0 L0,100" stroke="rgba(255,255,255,0.05)" stroke-width="1"/></svg>'); |
|
opacity: 0.4; |
|
z-index: 0; |
|
} |
|
|
|
header h1{ |
|
margin:0; |
|
font-size:1.8rem; |
|
font-weight:800; |
|
text-shadow:0 2px 4px rgba(0,0,0,0.2); |
|
position: relative; |
|
z-index: 1; |
|
} |
|
|
|
header .author{ |
|
font-size:1rem; |
|
opacity:0.95; |
|
text-align:right; |
|
position: relative; |
|
z-index: 1; |
|
} |
|
|
|
/* بهبود دکمه ذخیره دادهها */ |
|
#btnSaveData { |
|
background-color: rgba(255,255,255,0.2); |
|
color: white; |
|
border: 1px solid rgba(255,255,255,0.3); |
|
padding: 10px 20px; |
|
border-radius: 30px; |
|
font-weight: 700; |
|
transition: var(--transition); |
|
margin-right: 16px; |
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
|
backdrop-filter: blur(4px); |
|
} |
|
|
|
#btnSaveData:hover { |
|
background-color: rgba(255,255,255,0.3); |
|
transform: translateY(-2px); |
|
box-shadow: 0 8px 15px rgba(0,0,0,0.2); |
|
} |
|
|
|
main{max-width:1200px;margin:32px auto;padding:24px;position:relative;overflow-x:hidden} |
|
|
|
/* بهبود کارتها */ |
|
.card{ |
|
background:var(--card); |
|
border-radius:20px; |
|
padding:28px; |
|
box-shadow:var(--shadow-card); |
|
margin-bottom:28px; |
|
transition:var(--transition); |
|
border: 1px solid rgba(255,255,255,0.8); |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.card::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
height: 4px; |
|
background: var(--gradient-primary); |
|
transform: scaleX(0); |
|
transform-origin: right; |
|
transition: transform 0.4s ease; |
|
} |
|
|
|
.card:hover::before { |
|
transform: scaleX(1); |
|
transform-origin: left; |
|
} |
|
|
|
.card:hover{ |
|
transform: translateY(-5px); |
|
box-shadow:0 15px 30px rgba(0,0,0,0.1); |
|
} |
|
|
|
/* بهبود تبها */ |
|
.tabs{ |
|
display:flex; |
|
gap:12px; |
|
justify-content:center; |
|
margin-bottom:24px; |
|
position:relative; |
|
padding:8px; |
|
background:linear-gradient(145deg, #f1f3f5, #e9ecef); |
|
border-radius:20px; |
|
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); |
|
flex-wrap: wrap; |
|
} |
|
|
|
.tab{ |
|
padding:14px 24px; |
|
border-radius:14px; |
|
background:transparent; |
|
color:var(--muted); |
|
cursor:pointer; |
|
font-weight:700; |
|
transition:var(--transition); |
|
position:relative; |
|
z-index:2; |
|
font-size: 1rem; |
|
white-space: nowrap; |
|
text-overflow: ellipsis; |
|
overflow: hidden; |
|
max-width: 200px; |
|
} |
|
|
|
.tab:hover{ |
|
color:var(--p); |
|
} |
|
|
|
.tab.active{ |
|
background:var(--card); |
|
color:var(--p); |
|
box-shadow:0 4px 12px rgba(0,0,0,0.08); |
|
transform: scale(1.05); |
|
} |
|
|
|
.tab-content{ |
|
display:none; |
|
overflow-x:hidden; |
|
animation: fadeIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); |
|
} |
|
|
|
.tab-content.active{ |
|
display:block; |
|
} |
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(20px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
/* بهبود گرید */ |
|
.grid{ |
|
display:grid; |
|
grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); |
|
gap:20px; |
|
} |
|
|
|
/* بهبود لیبلها و اینپوتها */ |
|
label{ |
|
display:block; |
|
font-weight:700; |
|
color:var(--p-dark); |
|
margin-bottom:10px; |
|
font-size:0.95rem; |
|
letter-spacing: -0.01em; |
|
} |
|
|
|
input,select,textarea{ |
|
width:100%; |
|
padding:14px 18px; |
|
border-radius:12px; |
|
border:1px solid var(--border); |
|
background:var(--card); |
|
color:var(--text); |
|
font-size:1rem; |
|
transition:var(--transition); |
|
font-family:inherit; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.04); |
|
} |
|
|
|
input:focus,select:focus,textarea:focus{ |
|
outline:none; |
|
border-color:var(--p); |
|
box-shadow:0 0 0 4px var(--p-light), 0 4px 6px rgba(0,0,0,0.05); |
|
transform: translateY(-2px); |
|
} |
|
|
|
input[readonly]{ |
|
background:var(--bg); |
|
color:var(--text-light); |
|
} |
|
|
|
/* بهبود دکمهها */ |
|
button{ |
|
border:0; |
|
padding:12px 20px; |
|
border-radius:12px; |
|
background:var(--gradient-primary); |
|
color:#fff; |
|
font-weight:700; |
|
cursor:pointer; |
|
transition:var(--transition); |
|
font-size:1rem; |
|
display:inline-flex; |
|
align-items:center; |
|
justify-content:center; |
|
box-shadow:var(--shadow); |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
button::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: rgba(255,255,255,0.2); |
|
transform: translateX(-100%); |
|
transition: transform 0.6s ease; |
|
} |
|
|
|
button:hover::before { |
|
transform: translateX(0); |
|
} |
|
|
|
button:hover{ |
|
transform: translateY(-3px); |
|
box-shadow:var(--shadow-hover); |
|
} |
|
|
|
button:active{ |
|
transform: translateY(-1px); |
|
} |
|
|
|
button:disabled{ |
|
background:var(--muted); |
|
cursor:not-allowed; |
|
opacity:0.7; |
|
transform:none; |
|
box-shadow:none; |
|
} |
|
|
|
.btn-muted{ |
|
background:var(--gradient-secondary); |
|
} |
|
|
|
.btn-muted:hover{ |
|
background:var(--gradient-secondary); |
|
filter: brightness(1.1); |
|
} |
|
|
|
.btn-danger{ |
|
background:var(--gradient-danger); |
|
} |
|
|
|
.btn-danger:hover{ |
|
filter: brightness(1.1); |
|
} |
|
|
|
/* دکمه ویژه مدیر */ |
|
.btn-manager { |
|
background: var(--gradient-manager); |
|
border: 2px solid rgba(255, 255, 255, 0.3); |
|
box-shadow: 0 6px 12px rgba(30, 58, 138, 0.4); |
|
font-weight: 800; |
|
letter-spacing: 0.5px; |
|
position: relative; |
|
overflow: hidden; |
|
z-index: 1; |
|
} |
|
|
|
.btn-manager::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: linear-gradient(45deg, rgba(255,255,255,0.1), rgba(255,255,255,0.3)); |
|
transform: translateX(-100%); |
|
transition: transform 0.8s ease; |
|
z-index: -1; |
|
} |
|
|
|
.btn-manager:hover::before { |
|
transform: translateX(0); |
|
} |
|
|
|
.btn-manager:hover { |
|
transform: translateY(-4px); |
|
box-shadow: 0 12px 24px rgba(30, 58, 138, 0.5); |
|
filter: brightness(1.1); |
|
} |
|
|
|
.btn-manager::after { |
|
content: '📋'; |
|
margin-left: 8px; |
|
font-size: 1.2rem; |
|
} |
|
|
|
/* بهبود جداول */ |
|
table{ |
|
width:100%; |
|
border-collapse:collapse; |
|
margin-top:20px; |
|
table-layout:fixed; |
|
font-size:0.95rem; |
|
border-radius: 12px; |
|
overflow: hidden; |
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05); |
|
} |
|
|
|
th,td{ |
|
padding:14px 18px; |
|
border:1px solid var(--border); |
|
text-align:center; |
|
vertical-align:middle; |
|
word-break:break-word; |
|
white-space:normal; |
|
} |
|
|
|
th{ |
|
background:var(--gradient-primary); |
|
color:#fff; |
|
font-weight:700; |
|
text-shadow: 0 1px 2px rgba(0,0,0,0.1); |
|
} |
|
|
|
tbody tr:nth-child(even){ |
|
background:var(--bg); |
|
} |
|
|
|
tbody tr:hover{ |
|
background-color:rgba(67,97,238,0.08); |
|
transform: scale(1.01); |
|
transition: all 0.2s ease; |
|
} |
|
|
|
/* بهبود فوتر */ |
|
footer{ |
|
padding:24px; |
|
text-align:center; |
|
color:var(--text-light); |
|
font-weight:600; |
|
margin-top:40px; |
|
border-top:1px solid var(--border); |
|
background: rgba(255,255,255,0.7); |
|
backdrop-filter: blur(10px); |
|
border-radius: 20px; |
|
margin: 40px auto; |
|
max-width: 1200px; |
|
} |
|
|
|
/* بهبود نشان نویسنده - طراحی کاملاً جدید */ |
|
.author-badge{ |
|
position:fixed; |
|
left:24px; |
|
bottom:24px; |
|
background:var(--gradient-primary); |
|
padding:16px 24px; |
|
border-radius:50px; |
|
box-shadow:var(--shadow-card); |
|
font-weight:800; |
|
color:#fff; |
|
z-index:100; |
|
transition:var(--transition); |
|
display: flex; |
|
align-items: center; |
|
gap: 12px; |
|
border: 2px solid rgba(255,255,255,0.2); |
|
backdrop-filter: blur(10px); |
|
overflow: hidden; |
|
} |
|
|
|
.author-badge::before { |
|
content: ''; |
|
position: absolute; |
|
top: -50%; |
|
left: -50%; |
|
width: 200%; |
|
height: 200%; |
|
background: linear-gradient( |
|
45deg, |
|
rgba(255,255,255,0) 0%, |
|
rgba(255,255,255,0.2) 50%, |
|
rgba(255,255,255,0) 100% |
|
); |
|
transform: rotate(30deg); |
|
animation: shine 3s infinite; |
|
z-index: -1; |
|
} |
|
|
|
.author-badge::after { |
|
content: '✦'; |
|
font-size: 1.4rem; |
|
background: rgba(255,255,255,0.2); |
|
width: 36px; |
|
height: 36px; |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1); |
|
} |
|
|
|
.author-badge:hover{ |
|
transform:translateY(-5px) scale(1.05); |
|
box-shadow:0 15px 30px rgba(67,97,238,0.3); |
|
} |
|
|
|
.author-badge-text { |
|
display: flex; |
|
flex-direction: column; |
|
line-height: 1.2; |
|
} |
|
|
|
.author-badge-name { |
|
font-size: 1.1rem; |
|
margin-bottom: 2px; |
|
} |
|
|
|
.author-badge-title { |
|
font-size: 0.85rem; |
|
opacity: 0.9; |
|
} |
|
|
|
@keyframes shine { |
|
0% { |
|
transform: translateX(-100%) translateY(-100%) rotate(30deg); |
|
} |
|
100% { |
|
transform: translateX(100%) translateY(100%) rotate(30deg); |
|
} |
|
} |
|
|
|
.small-muted{ |
|
color:var(--text-light); |
|
font-size:0.95rem; |
|
} |
|
|
|
.hint{ |
|
font-size:0.95rem; |
|
color:var(--p); |
|
margin-top:12px; |
|
background:var(--p-light); |
|
padding:16px; |
|
border-radius:12px; |
|
border-right:5px solid var(--p); |
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05); |
|
} |
|
|
|
/* بهبود رسپانسیو */ |
|
@media(max-width:720px){ |
|
header{ |
|
flex-direction:column; |
|
gap:16px; |
|
text-align:center; |
|
padding: 20px 24px; |
|
} |
|
|
|
header h1 { |
|
font-size: 1.5rem; |
|
} |
|
|
|
.author-badge{ |
|
left:auto; |
|
right:20px; |
|
bottom: 20px; |
|
padding: 14px 20px; |
|
} |
|
|
|
.author-badge-name { |
|
font-size: 1rem; |
|
} |
|
|
|
.author-badge-title { |
|
font-size: 0.8rem; |
|
} |
|
|
|
.grid{ |
|
grid-template-columns:1fr; |
|
gap: 16px; |
|
} |
|
|
|
table{ |
|
font-size:0.85rem; |
|
} |
|
|
|
th,td{ |
|
padding:10px; |
|
} |
|
|
|
.card { |
|
padding: 20px; |
|
margin-bottom: 20px; |
|
} |
|
|
|
.tabs { |
|
gap: 8px; |
|
padding: 6px; |
|
} |
|
|
|
.tab { |
|
padding: 10px 16px; |
|
font-size: 0.9rem; |
|
max-width: 150px; |
|
} |
|
|
|
/* 🆕 اصلاحات واکنشگرایی برای تب آنالیز */ |
|
.analysis-grid-2-col { |
|
grid-template-columns: 1fr; /* کارتها در موبایل زیر هم قرار میگیرند */ |
|
} |
|
.bar-label { |
|
width: 90px; /* کاهش عرض لیبل نمودار برای جای بیشتر */ |
|
font-size: 0.85rem; |
|
} |
|
.bar { |
|
font-size: 0.85rem; |
|
padding-right: 8px; |
|
} |
|
.analysis-stat-card p { |
|
font-size: 2rem; /* کمی کوچکتر کردن فونت آمار اصلی در موبایل */ |
|
} |
|
} |
|
|
|
/* استایلهای جدید برای فرمهای چندگانه */ |
|
.form-container { |
|
margin-top: 28px; |
|
padding: 24px; |
|
border: 1px solid var(--border); |
|
border-radius:16px; |
|
background: linear-gradient(145deg, #f8f9fa, #e9ecef); |
|
transition: var(--transition); |
|
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); |
|
} |
|
|
|
.form-container:hover { |
|
box-shadow: 0 4px 12px rgba(0,0,0,0.08); |
|
} |
|
|
|
.form-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 24px; |
|
padding-bottom: 16px; |
|
border-bottom: 1px dashed var(--border); |
|
} |
|
|
|
.form-actions { |
|
display: flex; |
|
gap: 12px; |
|
} |
|
|
|
.worker-block { |
|
border: 1px dashed var(--border); |
|
padding:20px; |
|
border-radius:16px; |
|
margin-bottom:20px; |
|
background: linear-gradient(145deg, #f8f9fa, #e9ecef); |
|
transition: var(--transition); |
|
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); |
|
} |
|
|
|
.worker-block:hover { |
|
border-color: var(--p); |
|
background: var(--p-light); |
|
transform: translateY(-3px); |
|
box-shadow: 0 6px 12px rgba(0,0,0,0.08); |
|
} |
|
|
|
.worker-block .grid { |
|
gap: 16px; |
|
margin-bottom: 16px; |
|
} |
|
|
|
.worker-block .grid > div { |
|
min-width: 200px; |
|
} |
|
|
|
/* استایلهای جدید برای منوی خروجی */ |
|
.export-menu { |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-color: rgba(0, 0, 0, 0.7); |
|
z-index: 1000; |
|
display: none; |
|
justify-content: center; |
|
align-items: center; |
|
padding: 24px; |
|
box-sizing: border-box; |
|
overflow-y: auto; |
|
backdrop-filter: blur(8px); |
|
} |
|
|
|
.export-content { |
|
background-color: var(--card); |
|
border-radius:24px; |
|
padding: 36px; |
|
width: 100%; |
|
max-width: 640px; |
|
box-shadow: var(--shadow-modal); |
|
max-height: 90vh; |
|
overflow-y: auto; |
|
animation: slideUp 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); |
|
position: relative; |
|
margin: auto; |
|
border: 1px solid rgba(255,255,255,0.8); |
|
} |
|
|
|
@keyframes slideUp { |
|
from { opacity: 0; transform: translateY(40px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
.export-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 32px; |
|
padding-bottom: 20px; |
|
border-bottom: 1px solid var(--border); |
|
} |
|
|
|
.export-header h3 { |
|
margin: 0; |
|
color: var(--p-dark); |
|
font-size: 1.7rem; |
|
font-weight: 800; |
|
} |
|
|
|
.export-close { |
|
background: none; |
|
border: none; |
|
font-size: 32px; |
|
cursor: pointer; |
|
color: var(--muted); |
|
padding: 0; |
|
width: 48px; |
|
height: 48px; |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
border-radius: 50%; |
|
transition: var(--transition); |
|
} |
|
|
|
.export-close:hover { |
|
background-color: var(--bg); |
|
color: var(--text); |
|
transform: rotate(90deg); |
|
} |
|
|
|
.export-options { |
|
display: grid; |
|
grid-template-columns: 1fr; |
|
gap: 20px; |
|
} |
|
|
|
.export-option { |
|
padding: 24px; |
|
border: 1px solid var(--border); |
|
border-radius:16px; |
|
background-color: var(--bg); |
|
cursor: pointer; |
|
transition: var(--transition); |
|
text-align: right; |
|
position: relative; |
|
overflow: hidden; |
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05); |
|
} |
|
|
|
.export-option::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
right: 0; |
|
width: 6px; |
|
height: 100%; |
|
background: var(--gradient-primary); |
|
transform: scaleY(0); |
|
transition: transform 0.4s ease; |
|
} |
|
|
|
.export-option:hover { |
|
background-color: var(--p-light); |
|
border-color: var(--p); |
|
transform: translateY(-5px); |
|
box-shadow: 0 10px 20px rgba(26,115,232,0.15); |
|
} |
|
|
|
.export-option:hover::before { |
|
transform: scaleY(1); |
|
} |
|
|
|
.export-option h4 { |
|
margin: 0 0 12px 0; |
|
font-size: 1.3rem; |
|
font-weight: 800; |
|
color: var(--p-dark); |
|
} |
|
|
|
.export-option p { |
|
margin: 0; |
|
font-size: 1rem; |
|
color: var(--text-light); |
|
line-height: 1.6; |
|
} |
|
|
|
/* استایلهای مودال اشتراک با مدیر */ |
|
.modal { |
|
display: none; |
|
position: fixed; |
|
z-index: 1000; |
|
left: 0; |
|
top: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-color: rgba(0,0,0,0.7); |
|
justify-content: center; |
|
align-items: center; |
|
padding: 24px; |
|
box-sizing: border-box; |
|
overflow-y: auto; |
|
backdrop-filter: blur(8px); |
|
} |
|
|
|
.modal-content { |
|
background-color: var(--card); |
|
margin: auto; |
|
padding: 32px; |
|
border: none; |
|
width: 90%; |
|
max-width: 640px; |
|
border-radius:24px; |
|
box-shadow: var(--shadow-modal); |
|
max-height: 90vh; |
|
overflow-y: auto; |
|
position: relative; |
|
animation: slideUp 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); |
|
border: 1px solid rgba(255,255,255,0.8); |
|
} |
|
|
|
.modal-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 24px; |
|
padding-bottom: 20px; |
|
border-bottom: 1px solid var(--border); |
|
} |
|
|
|
.modal-header h3 { |
|
margin: 0; |
|
color: var(--p-dark); |
|
font-size: 1.7rem; |
|
font-weight: 800; |
|
} |
|
|
|
.close { |
|
color: var(--muted); |
|
font-size: 32px; |
|
font-weight: bold; |
|
cursor: pointer; |
|
line-height: 1; |
|
transition: var(--transition); |
|
width: 48px; |
|
height: 48px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
border-radius: 50%; |
|
} |
|
|
|
.close:hover, |
|
.close:focus { |
|
color: var(--text); |
|
background-color: var(--bg); |
|
transform: rotate(90deg); |
|
} |
|
|
|
.modal-body { |
|
margin-bottom: 28px; |
|
} |
|
|
|
/* استایلهای کادر توضیحات */ |
|
#inspectionComments { |
|
resize: vertical; |
|
min-height: 140px; |
|
transition: var(--transition); |
|
} |
|
|
|
/* استایلهای رسپانسیو */ |
|
@media (max-width: 768px) { |
|
.export-content { |
|
padding: 28px; |
|
max-width: 100%; |
|
width: 95%; |
|
} |
|
.export-header h3 { |
|
font-size: 1.5rem; |
|
} |
|
.export-option { |
|
padding: 20px; |
|
} |
|
.export-option h4 { |
|
font-size: 1.2rem; |
|
} |
|
.export-option p { |
|
font-size: 0.95rem; |
|
} |
|
.modal-content { |
|
width: 95%; |
|
padding: 24px; |
|
} |
|
} |
|
|
|
@media (max-width: 480px) { |
|
.export-content { |
|
padding: 24px; |
|
width: 95%; |
|
} |
|
.export-header { |
|
margin-bottom: 24px; |
|
padding-bottom: 16px; |
|
} |
|
.export-header h3 { |
|
font-size: 1.4rem; |
|
} |
|
.export-close { |
|
width: 40px; |
|
height: 40px; |
|
font-size: 28px; |
|
} |
|
.export-option { |
|
padding: 16px; |
|
} |
|
.export-option h4 { |
|
font-size: 1.1rem; |
|
margin-bottom: 8px; |
|
} |
|
.export-option p { |
|
font-size: 0.9rem; |
|
} |
|
.modal-content { |
|
width: 95%; |
|
padding: 20px; |
|
} |
|
.modal-header h3 { |
|
font-size: 1.4rem; |
|
} |
|
} |
|
|
|
/* استایلهای جدید برای جداول با ستونهای زیاد */ |
|
.table-container { |
|
overflow-x: auto; |
|
margin-top: 20px; |
|
border-radius:16px; |
|
box-shadow: 0 4px 12px rgba(0,0,0,0.08); |
|
} |
|
|
|
#managersTable { |
|
min-width: 900px; |
|
} |
|
|
|
#managersTable th:nth-child(3), |
|
#managersTable td:nth-child(3), |
|
#managersTable th:nth-child(5), |
|
#managersTable td:nth-child(5), |
|
#managersTable th:nth-child(6), |
|
#managersTable td:nth-child(6), |
|
#managersTable th:nth-child(7), |
|
#managersTable td:nth-child(7) { |
|
min-width: 120px; |
|
max-width: 150px; |
|
} |
|
|
|
#managersTable th:nth-child(8), |
|
#managersTable td:nth-child(8), |
|
#managersTable th:nth-child(9), |
|
#managersTable td:nth-child(9) { |
|
min-width: 100px; |
|
max-width: 120px; |
|
} |
|
|
|
/* استایلهای جدید برای جدول بازرسیها */ |
|
#inspectionsTable, #searchResultsTable { |
|
min-width: 800px; |
|
} |
|
|
|
#inspectionsTable th:nth-child(1), |
|
#inspectionsTable td:nth-child(1), |
|
#searchResultsTable th:nth-child(1), |
|
#searchResultsTable td:nth-child(1) { |
|
width: 50px; |
|
min-width: 50px; |
|
} |
|
|
|
#inspectionsTable th:nth-child(2), |
|
#inspectionsTable td:nth-child(2), |
|
#searchResultsTable th:nth-child(2), |
|
#searchResultsTable td:nth-child(2) { |
|
width: 100px; |
|
min-width: 100px; |
|
} |
|
|
|
#inspectionsTable th:nth-child(3), |
|
#inspectionsTable td:nth-child(3), |
|
#searchResultsTable th:nth-child(3), |
|
#searchResultsTable td:nth-child(3) { |
|
width: 120px; |
|
min-width: 120px; |
|
} |
|
|
|
#inspectionsTable th:nth-child(4), |
|
#inspectionsTable td:nth-child(4), |
|
#searchResultsTable th:nth-child(4), |
|
#searchResultsTable td:nth-child(4) { |
|
width: 150px; |
|
min-width: 150px; |
|
} |
|
|
|
#inspectionsTable th:nth-child(5), |
|
#inspectionsTable td:nth-child(5), |
|
#searchResultsTable th:nth-child(5), |
|
#searchResultsTable td:nth-child(5) { |
|
width: 80px; |
|
min-width: 80px; |
|
} |
|
|
|
#inspectionsTable th:nth-child(6), |
|
#inspectionsTable td:nth-child(6), |
|
#searchResultsTable th:nth-child(6), |
|
#searchResultsTable td:nth-child(6) { |
|
width: 150px; |
|
min-width: 150px; |
|
word-break: break-all; |
|
font-size: 0.85rem; |
|
} |
|
|
|
#inspectionsTable th:nth-child(7), |
|
#inspectionsTable td:nth-child(7), |
|
#searchResultsTable th:nth-child(7), |
|
#searchResultsTable td:nth-child(7) { |
|
width: 100px; |
|
min-width: 100px; |
|
} |
|
|
|
#inspectionsTable th:nth-child(8), |
|
#inspectionsTable td:nth-child(8), |
|
#searchResultsTable th:nth-child(8), |
|
#searchResultsTable td:nth-child(8) { |
|
width: 150px; |
|
min-width: 150px; |
|
} |
|
|
|
/* استایلهای جدید برای جدول بازرسها */ |
|
#inspectorsTable { |
|
min-width: 700px; |
|
} |
|
|
|
#inspectorsTable th:nth-child(1), |
|
#inspectorsTable td:nth-child(1) { |
|
width: 50px; |
|
min-width: 50px; |
|
} |
|
|
|
#inspectorsTable th:nth-child(2), |
|
#inspectorsTable td:nth-child(2) { |
|
width: 120px; |
|
min-width: 120px; |
|
} |
|
|
|
#inspectorsTable th:nth-child(3), |
|
#inspectorsTable td:nth-child(3) { |
|
width: 100px; |
|
min-width: 100px; |
|
} |
|
|
|
#inspectorsTable th:nth-child(4), |
|
#inspectorsTable td:nth-child(4) { |
|
width: 120px; |
|
min-width: 120px; |
|
} |
|
|
|
#inspectorsTable th:nth-child(5), |
|
#inspectorsTable td:nth-child(5) { |
|
width: 120px; |
|
min-width: 120px; |
|
} |
|
|
|
#inspectorsTable th:nth-child(6), |
|
#inspectorsTable td:nth-child(6) { |
|
width: 150px; |
|
min-width: 150px; |
|
} |
|
|
|
/* استایلهای جدید برای دکمههای عملیات */ |
|
.edit-inspection, .delete-inspection, .edit-inspector, .delete-inspector, .edit-manager, .delete-manager { |
|
padding: 8px 14px; |
|
margin: 0 4px; |
|
font-size: 0.9rem; |
|
white-space: nowrap; |
|
border-radius:10px; |
|
} |
|
|
|
/* استایلهای جدید برای پیامهای فرم */ |
|
#formMessage, #searchMessage { |
|
background-color: var(--p-light); |
|
color: var(--p-dark); |
|
border-radius:12px; |
|
padding: 16px; |
|
border-right: 5px solid var(--p); |
|
animation: fadeIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); |
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05); |
|
} |
|
|
|
/* استایلهای جدید برای لیست کارگران */ |
|
.workers-list { |
|
grid-column: 1 / -1; |
|
border-radius:16px; |
|
overflow: hidden; |
|
} |
|
|
|
/* استایلهای جدید برای تاریخ آخرین بازرسی */ |
|
#lastInspectionDate { |
|
background-color: var(--bg); |
|
border-radius:12px; |
|
padding: 16px; |
|
border: 1px solid var(--border); |
|
font-weight: 600; |
|
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); |
|
} |
|
|
|
/* استایلهای جدید برای اطلاعات بازرس */ |
|
#inspectorInfo { |
|
background-color: var(--p-light); |
|
border-radius:12px; |
|
padding: 16px; |
|
border-right: 5px solid var(--p); |
|
font-weight: 600; |
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05); |
|
} |
|
|
|
/* استایلهای جدید برای دکمههای اصلی */ |
|
#btnGetGPS, #btnManualGPS, #btnSubmitInspection, #btnReset, #exportBtn { |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
#btnGetGPS::after, #btnManualGPS::after, #btnSubmitInspection::after, #btnReset::after, #exportBtn::after { |
|
content: ''; |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
width: 5px; |
|
height: 5px; |
|
background: rgba(255, 255, 255, 0.5); |
|
opacity: 0; |
|
border-radius: 100%; |
|
transform: scale(1, 1) translate(-50%); |
|
transform-origin: 50% 50%; |
|
} |
|
|
|
#btnGetGPS:focus:not(:active)::after, #btnManualGPS:focus:not(:active)::after, #btnSubmitInspection:focus:not(:active)::after, #btnReset:focus:not(:active)::after, #exportBtn:focus:not(:active)::after { |
|
animation: ripple 1s ease-out; |
|
} |
|
|
|
@keyframes ripple { |
|
0% { |
|
transform: scale(0, 0); |
|
opacity: 0.5; |
|
} |
|
100% { |
|
transform: scale(100, 100); |
|
opacity: 0; |
|
} |
|
} |
|
|
|
/* استایلهای جدید برای دکمههای اضافه/بهروزرسانی */ |
|
#btnAddInspector, #btnAddManager { |
|
background: var(--gradient-success); |
|
} |
|
|
|
#btnAddInspector:hover, #btnAddManager:hover { |
|
filter: brightness(1.1); |
|
} |
|
|
|
/* استایلهای جدید برای دکمههای پاک کردن فرم */ |
|
#btnClearInspector, #btnClearManager { |
|
background: var(--gradient-warning); |
|
color: var(--text); |
|
} |
|
|
|
#btnClearInspector:hover, #btnClearManager:hover { |
|
filter: brightness(1.1); |
|
} |
|
|
|
/* استایلهای جدید برای دکمه ارسال اشتراک */ |
|
#btnSendShare { |
|
background: var(--gradient-success); |
|
} |
|
|
|
#btnSendShare:hover { |
|
filter: brightness(1.1); |
|
} |
|
|
|
/* استایلهای جدید برای دکمه انصراف اشتراک */ |
|
#btnCancelShare { |
|
background: var(--gradient-secondary); |
|
} |
|
|
|
#btnCancelShare:hover { |
|
filter: brightness(1.1); |
|
} |
|
|
|
/* استایلهای جدید برای انتخابگر اشتراک با مدیر */ |
|
#shareManager, #shareMethod { |
|
cursor: pointer; |
|
} |
|
|
|
/* استایلهای جدید برای متن اضافی در اشتراک */ |
|
#shareMessage { |
|
min-height: 120px; |
|
resize: vertical; |
|
} |
|
|
|
/* استایلهای جدید برای افکتهای ویژه */ |
|
.shine-effect { |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.shine-effect::before { |
|
content: ''; |
|
position: absolute; |
|
top: -50%; |
|
left: -50%; |
|
width: 200%; |
|
height: 200%; |
|
background: linear-gradient( |
|
to right, |
|
rgba(255, 255, 255, 0) 0%, |
|
rgba(255, 255, 255, 0.3) 50%, |
|
rgba(255, 255, 255, 0) 100% |
|
); |
|
transform: rotate(30deg); |
|
animation: shine 3s infinite; |
|
} |
|
|
|
@keyframes shine { |
|
0% { |
|
transform: translateX(-100%) translateY(-100%) rotate(30deg); |
|
} |
|
100% { |
|
transform: translateX(100%) translateY(100%) rotate(30deg); |
|
} |
|
} |
|
|
|
/* استایلهای جدید برای افکتهای لودینگ */ |
|
.loading { |
|
position: relative; |
|
pointer-events: none; |
|
} |
|
|
|
.loading::after { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: rgba(255, 255, 255, 0.7); |
|
z-index: 1; |
|
} |
|
|
|
.loading::before { |
|
content: ''; |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
width: 30px; |
|
height: 30px; |
|
margin: -15px 0 0 -15px; |
|
border: 3px solid rgba(0, 0, 0, 0.1); |
|
border-radius: 50%; |
|
border-top-color: var(--p); |
|
animation: spin 1s ease-in-out infinite; |
|
z-index: 2; |
|
} |
|
|
|
@keyframes spin { |
|
to { transform: rotate(360deg); } |
|
} |
|
|
|
/* استایلهای جدید برای چینش دکمههای فرم بازرسی */ |
|
.action-buttons { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 12px; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-top: 24px; |
|
padding: 20px; |
|
background: linear-gradient(145deg, #f8f9fa, #e9ecef); |
|
border-radius: 16px; |
|
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); |
|
} |
|
|
|
.action-buttons .button-group { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 10px; |
|
} |
|
|
|
.action-buttons .button-group.primary { |
|
flex: 1; |
|
justify-content: flex-start; |
|
} |
|
|
|
.action-buttons .button-group.secondary { |
|
flex: 1; |
|
justify-content: flex-end; |
|
} |
|
|
|
.action-buttons button { |
|
flex: 1; |
|
min-width: 150px; |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.action-buttons { |
|
flex-direction: column; |
|
gap: 16px; |
|
} |
|
|
|
.action-buttons .button-group { |
|
width: 100%; |
|
justify-content: center !important; |
|
} |
|
|
|
.action-buttons button { |
|
min-width: 120px; |
|
} |
|
} |
|
|
|
/* استایلهای جدید برای فیلدهای اختیاری */ |
|
.optional-field { |
|
position: relative; |
|
} |
|
|
|
.optional-field::after { |
|
content: '(اختیاری)'; |
|
position: absolute; |
|
left: 12px; |
|
top: 50%; |
|
transform: translateY(-50%); |
|
font-size: 0.75rem; |
|
color: var(--text-light); |
|
background: var(--card); |
|
padding: 2px 6px; |
|
border-radius: 4px; |
|
} |
|
|
|
/* 🎨 استایلهای جدید برای تب تحلیل */ |
|
.analysis-grid-2-col { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); |
|
gap: 24px; |
|
} |
|
.analysis-stat-card { |
|
background: var(--card); |
|
border-radius: 16px; |
|
padding: 20px; |
|
text-align: center; |
|
border-left: 5px solid var(--stat-color, var(--p)); |
|
box-shadow: var(--shadow); |
|
transition: var(--transition); |
|
} |
|
.analysis-stat-card:hover { |
|
transform: translateY(-4px); |
|
box-shadow: var(--shadow-hover); |
|
} |
|
.analysis-stat-card h4 { |
|
margin: 0 0 10px 0; |
|
font-size: 1rem; |
|
color: var(--text-light); |
|
font-weight: 700; |
|
} |
|
.analysis-stat-card p { |
|
margin: 0; |
|
font-size: 2.5rem; |
|
font-weight: 800; |
|
color: var(--stat-color, var(--p-dark)); |
|
} |
|
.chart-container { |
|
min-height: 100px; |
|
width: 100%; |
|
} |
|
.bar-chart { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 12px; |
|
} |
|
.bar-item { |
|
display: flex; |
|
align-items: center; |
|
gap: 12px; |
|
} |
|
.bar-label { |
|
width: 120px; |
|
text-align: right; |
|
font-weight: 700; |
|
font-size: 0.9rem; |
|
white-space: nowrap; |
|
overflow: hidden; |
|
text-overflow: ellipsis; |
|
color: var(--text); |
|
flex-shrink: 0; |
|
} |
|
.bar-wrapper { |
|
flex-grow: 1; |
|
background: var(--bg); |
|
border-radius: 8px; |
|
overflow: hidden; |
|
box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); |
|
} |
|
.bar { |
|
height: 28px; |
|
background: var(--gradient-primary); |
|
border-radius: 8px; |
|
color: white; |
|
display: flex; |
|
align-items: center; |
|
justify-content: flex-end; |
|
padding-right: 12px; |
|
font-weight: 700; |
|
font-size: 0.9rem; |
|
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1); |
|
white-space: nowrap; |
|
} |
|
#btnShowMap { |
|
width: 100%; |
|
padding: 16px; |
|
font-size: 1.1rem; |
|
background: var(--gradient-success); |
|
} |
|
#btnShowMap:hover { |
|
filter: brightness(1.1); |
|
} |
|
.card-header-icon { |
|
margin-left: 8px; |
|
vertical-align: middle; |
|
} |
|
/* استایلهای جدید برای پیام مدرن */ |
|
.modern-alert { |
|
position: fixed; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%) scale(0.9); |
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(240, 240, 240, 0.9)); |
|
backdrop-filter: blur(10px); |
|
color: var(--p-dark); |
|
padding: 24px 32px; |
|
border-radius: 20px; |
|
box-shadow: var(--shadow-modal); |
|
z-index: 2000; |
|
text-align: center; |
|
font-size: 1.1rem; |
|
font-weight: 700; |
|
border: 1px solid rgba(255, 255, 255, 1); |
|
opacity: 0; |
|
transition: opacity 0.5s ease, transform 0.5s ease; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
gap: 16px; |
|
max-width: 90%; |
|
pointer-events: none; |
|
} |
|
.modern-alert.show { |
|
opacity: 1; |
|
transform: translate(-50%, -50%) scale(1); |
|
} |
|
.modern-alert .spinner { |
|
width: 40px; |
|
height: 40px; |
|
border: 4px solid var(--p-light); |
|
border-top-color: var(--p); |
|
border-radius: 50%; |
|
animation: spin 1s linear infinite; |
|
} |
|
|
|
/* 🌟 استایلهای جدید برای تب تنظیمات و راهنما */ |
|
.settings-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
|
gap: 20px; |
|
} |
|
.setting-card { |
|
background: linear-gradient(145deg, var(--bg), #fff); |
|
border-radius: 16px; |
|
padding: 20px; |
|
border: 1px solid var(--border); |
|
text-align: center; |
|
transition: var(--transition); |
|
} |
|
.setting-card:hover { |
|
transform: translateY(-4px); |
|
box-shadow: var(--shadow-hover); |
|
border-color: var(--p); |
|
} |
|
.setting-card button { |
|
width: 100%; |
|
} |
|
.setting-card p { |
|
font-size: 0.9rem; |
|
color: var(--text-light); |
|
margin: 12px 0; |
|
min-height: 50px; |
|
} |
|
.help-section details { |
|
background: var(--bg); |
|
border-radius: 12px; |
|
margin-bottom: 12px; |
|
border: 1px solid var(--border); |
|
transition: var(--transition); |
|
} |
|
.help-section details:hover { |
|
border-color: var(--p); |
|
} |
|
.help-section summary { |
|
padding: 16px; |
|
font-weight: 700; |
|
color: var(--p-dark); |
|
cursor: pointer; |
|
list-style: none; |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
} |
|
.help-section summary::-webkit-details-marker { |
|
display: none; |
|
} |
|
.help-section summary::after { |
|
content: '▼'; |
|
transition: transform 0.3s; |
|
} |
|
.help-section details[open] summary::after { |
|
transform: rotate(180deg); |
|
} |
|
.help-section .details-content { |
|
padding: 0 16px 16px 16px; |
|
border-top: 1px solid var(--border); |
|
line-height: 1.7; |
|
color: var(--text); |
|
} |
|
.details-content p { |
|
margin-bottom: 1em; |
|
} |
|
.details-content code { |
|
background-color: var(--p-light); |
|
padding: 2px 6px; |
|
border-radius: 4px; |
|
color: var(--p-dark); |
|
font-family: monospace; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<header> |
|
<h1>سامانه بازرسی و پلاک برداری از کارگاه ها — سازمان تامین اجتماعی</h1> |
|
<div class="author"> |
|
طراح و توسعه دهنده: <strong>اشکان پورعلی</strong><br> |
|
حسابدار واحد مالی سازمان تامین اجتماعی — شعبه دو اهواز |
|
<button id="btnSaveData" class="btn-muted shine-effect">ذخیره دادهها</button> |
|
</div> |
|
</header> |
|
<main> |
|
<div class="card"> |
|
<div class="tabs" role="tablist"> |
|
<div class="tab active" data-target="inspectionTab" role="tab" tabindex="0">فرم بازرسی</div> |
|
<div class="tab" data-target="inspectorsTab" role="tab" tabindex="-1">ثبت بازرسها</div> |
|
<div class="tab" data-target="managersTab" role="tab" tabindex="-1">ثبت مدیران </div> |
|
<div class="tab" data-target="analysisTab" role="tab" tabindex="-1">📊 انالیز دادهها</div> |
|
<div class="tab" data-target="searchTab" role="tab" tabindex="-1">🔎 جستجوی پیشرفته</div> |
|
<div class="tab" data-target="settingsTab" role="tab" tabindex="-1">⚙️ تنظیمات و راهنما</div> |
|
</div> |
|
<section id="inspectionTab" class="tab-content active" role="tabpanel"> |
|
<div class="card"> |
|
<form id="inspectionForm" class="grid" autocomplete="off" novalidate> |
|
<div> |
|
<label for="insp_personnel">شماره پرسنلی بازرس</label> |
|
<input id="insp_personnel" type="text" /> |
|
</div> |
|
<div style="grid-column:1/-1"> |
|
<div id="inspectorInfo" class="small-muted"></div> |
|
</div> |
|
<div><label for="workshopName">نام کارگاه</label><input id="workshopName" type="text" /></div> |
|
<div><label for="activityField">رسته فعالیت</label><input id="activityField" type="text" /></div> |
|
<div><label for="employerName">نام کارفرما</label><input id="employerName" type="text" /></div> |
|
<div><label for="employerNationalId">کد ملی کارفرما</label><input id="employerNationalId" type="text" /></div> |
|
<div><label for="numEmployees">تعداد نیروهای شاغل</label><input id="numEmployees" type="number" min="0" max="200" value="0" /></div> |
|
<div class="workers-list" id="workersList"></div> |
|
<div style="grid-column:1/-1"> |
|
<label>تاریخ ثبت آخرین بازرسی (شمسی — خودکار)</label> |
|
<div id="lastInspectionDate" class="small-muted">هنوز بازرسی ثبت نشده</div> |
|
</div> |
|
<div><label for="gpsField">مختصات GPS</label><input id="gpsField" type="text" readonly placeholder="lat, lon" /></div> |
|
<div style="grid-column:1/-1"> |
|
<label for="inspectionComments">توضیحات و یادداشتهای بازرسی</label> |
|
<textarea id="inspectionComments" rows="4" placeholder="توضیحات و یادداشتهای خود را در مورد این بازرسی وارد کنید..."></textarea> |
|
</div> |
|
<div style="grid-column:1/-1"> |
|
<div class="action-buttons"> |
|
<div class="button-group primary"> |
|
<button id="btnGetGPS" type="button">ثبت مختصات GPS</button> |
|
<button id="btnManualGPS" type="button" class="btn-muted">وارد کردن دستی مختصات</button> |
|
</div> |
|
<div class="button-group secondary"> |
|
<button id="btnSubmitInspection" type="button" disabled>ثبت بازرسی</button> |
|
<button id="btnSendToManager" type="button" class="btn-manager" disabled>ارسال مستقیم به رئیس شعبه</button> |
|
<button id="exportBtn" type="button" class="btn-muted" disabled>خروجی گزارش</button> |
|
<button id="btnReset" type="button" class="btn-muted">پاک کردن فرم</button> |
|
</div> |
|
</div> |
|
</div> |
|
<div id="formMessage" class="small-muted" style="grid-column:1/-1;display:none"></div> |
|
<div style="grid-column:1/-1"> |
|
<div class="hint">تذکر: اگر فایل را بهصورت محلی (از حافظهٔ گوشی) اجرا میکنید و دکمهٔ GPS عمل نکرد، ابتدا تلاش میکنیم مختصات تقریبی از طریق IP بهدست آوریم. اگر دستگاه آفلاین یا API پاسخگو نبود، میتوانید مختصات را بهصورت دستی وارد کنید.</div> |
|
</div> |
|
</form> |
|
</div> |
|
<div class="card"> |
|
<h3 style="margin:0 0 16px 0;color:var(--p-dark)">لیست بازرسیهای ثبتشده</h3> |
|
<div class="table-container"> |
|
<table id="inspectionsTable" aria-label="لیست بازرسیها"> |
|
<thead> |
|
<tr><th>#</th><th>شماره پرسنلی</th><th>بازرس</th><th>نام کارگاه</th><th>تعداد نیرو</th><th>مختصات (Plus Code)</th><th>تاریخ شمسی</th><th>عملیات</th></tr> |
|
</thead> |
|
<tbody></tbody> |
|
</table> |
|
</div> |
|
</div> |
|
</section> |
|
<section id="inspectorsTab" class="tab-content" role="tabpanel" aria-hidden="true"> |
|
<div class="card"> |
|
<form id="inspectorForm" class="grid" autocomplete="off" novalidate> |
|
<div><label for="mgr_national">کد ملی بازرس</label><input id="mgr_national" type="text" /></div> |
|
<div><label for="mgr_name">نام</label><input id="mgr_name" type="text" /></div> |
|
<div><label for="mgr_family">نام خانوادگی</label><input id="mgr_family" type="text" /></div> |
|
<div><label for="mgr_personnel">شماره پرسنلی</label><input id="mgr_personnel" type="text" /></div> |
|
<div style="grid-column:1/-1" class="buttons"> |
|
<button id="btnAddInspector" type="button">افزودن/بهروزرسانی بازرس</button> |
|
<button id="btnClearInspector" type="button" class="btn-muted">پاک کردن فرم</button> |
|
</div> |
|
</form> |
|
</div> |
|
<div class="card"> |
|
<h3 style="margin:0 0 16px 0;color:var(--p-dark)">لیست بازرسها</h3> |
|
<div class="table-container"> |
|
<table id="inspectorsTable" aria-label="لیست بازرسها"> |
|
<thead><tr><th>#</th><th>کد ملی</th><th>نام</th><th>نام خانوادگی</th><th>شماره پرسنلی</th><th>عملیات</th></tr></thead> |
|
<tbody></tbody> |
|
</table> |
|
</div> |
|
</div> |
|
</section> |
|
<section id="managersTab" class="tab-content" role="tabpanel" aria-hidden="true"> |
|
<div class="card"> |
|
<form id="managerForm" class="grid" autocomplete="off" novalidate> |
|
<div><label for="mgt_national">کد ملی مدیر</label><input id="mgt_national" type="text" /></div> |
|
<div><label for="mgt_name">نام</label><input id="mgt_name" type="text" /></div> |
|
<div><label for="mgt_family">نام خانوادگی</label><input id="mgt_family" type="text" /></div> |
|
<div><label for="mgt_position">سمت</label><input id="mgt_position" type="text" /></div> |
|
<div><label for="mgt_email">ایمیل</label><input id="mgt_email" type="email" /></div> |
|
<div><label for="mgt_phone">تلفن تماس</label><input id="mgt_phone" type="tel" /></div> |
|
<div><label for="mgt_telegram">نام کاربری تلگرام</label><input id="mgt_telegram" type="text" placeholder="بدون @" /></div> |
|
<div style="grid-column:1/-1" class="buttons"> |
|
<button id="btnAddManager" type="button">افزودن/بهروزرسانی مدیر</button> |
|
<button id="btnClearManager" type="button" class="btn-muted">پاک کردن فرم</button> |
|
</div> |
|
</form> |
|
</div> |
|
<div class="card"> |
|
<h3 style="margin:0 0 16px 0;color:var(--p-dark)">لیست مدیران</h3> |
|
<div class="table-container"> |
|
<table id="managersTable" aria-label="لیست مدیران"> |
|
<thead><tr><th>#</th><th>کد ملی</th><th>نام</th><th>نام خانوادگی</th><th>سمت</th><th>ایمیل</th><th>تلفن</th><th>تلگرام</th><th>عملیات</th></tr></thead> |
|
<tbody></tbody> |
|
</table> |
|
</div> |
|
</div> |
|
</section> |
|
<section id="analysisTab" class="tab-content" role="tabpanel" aria-hidden="true"> |
|
<div class="card"> |
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;"> |
|
<h3 style="margin:0; color:var(--p-dark)"> |
|
<span class="card-header-icon">🚀</span>داشبورد هوشمند و آنالیز داده ها |
|
</h3> |
|
<button id="btnRefreshAnalysis" type="button" class="btn-muted">بروزرسانی آمار</button> |
|
</div> |
|
<div class="grid" id="analysis-grid"> |
|
<div class="analysis-stat-card" style="--stat-color: var(--p);"> |
|
<h4>کل بازرسیها</h4> |
|
<p id="totalInspectionsStat">0</p> |
|
</div> |
|
<div class="analysis-stat-card" style="--stat-color: var(--s);"> |
|
<h4>تعداد بازرس فعال</h4> |
|
<p id="totalInspectorsStat">0</p> |
|
</div> |
|
<div class="analysis-stat-card" style="--stat-color: var(--y);"> |
|
<h4>میانگین نیرو در کارگاه</h4> |
|
<p id="avgEmployeesStat">0</p> |
|
</div> |
|
<div class="analysis-stat-card" style="--stat-color: var(--w);"> |
|
<h4>کل نیروهای ثبت شده</h4> |
|
<p id="totalWorkersStat">0</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="analysis-grid-2-col"> |
|
<div class="card"> |
|
<h3 style="margin:0 0 16px 0;color:var(--p-dark)"> |
|
<span class="card-header-icon">📈</span>تفکیک بازرسی بر اساس رسته فعالیت |
|
</h3> |
|
<div id="activityFieldChart" class="chart-container"> |
|
<p class="small-muted">دادهای برای نمایش وجود ندارد.</p> |
|
</div> |
|
</div> |
|
<div class="card"> |
|
<h3 style="margin:0 0 16px 0;color:var(--p-dark)"> |
|
<span class="card-header-icon">📊</span>روند بازرسیها در طول زمان |
|
</h3> |
|
<div id="inspectionsOverTimeChart" class="chart-container"> |
|
<p class="small-muted">دادهای برای نمایش وجود ندارد.</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="card"> |
|
<h3 style="margin:0 0 16px 0;color:var(--p-dark)"> |
|
<span class="card-header-icon">👥</span>آنالیز پیشرفته عملکرد بازرسان |
|
</h3> |
|
<div id="advancedInspectorStats" class="table-container"> |
|
<p class="small-muted" style="text-align:center; padding: 20px;">دادهای برای نمایش وجود ندارد.</p> |
|
</div> |
|
</div> |
|
|
|
<div class="analysis-grid-2-col"> |
|
<div class="card"> |
|
<h3 style="margin:0 0 16px 0;color:var(--p-dark)"> |
|
<span class="card-header-icon">🏢</span>توزیع کارگاهها بر اساس تعداد نیرو |
|
</h3> |
|
<div id="employeeDistributionChart" class="chart-container"> |
|
<p class="small-muted">دادهای برای نمایش وجود ندارد.</p> |
|
</div> |
|
</div> |
|
<div class="card"> |
|
<h3 style="margin:0 0 16px 0;color:var(--p-dark)"> |
|
<span class="card-header-icon">🗺️</span>نمایش پراکندگی جغرافیایی |
|
</h3> |
|
<p class="small-muted" style="margin-bottom: 20px;"> |
|
نقاط ثبت شده تمام بازرسیها را به صورت همزمان بر روی نقشه گوگل مشاهده کنید. |
|
</p> |
|
<button id="btnShowMap" type="button"> |
|
📍 نمایش همه نقاط روی نقشه |
|
</button> |
|
</div> |
|
</div> |
|
</section> |
|
|
|
<section id="searchTab" class="tab-content" role="tabpanel" aria-hidden="true"> |
|
<div class="card"> |
|
<h3 style="margin:0 0 24px 0; color:var(--p-dark)"> |
|
<span class="card-header-icon">🔎</span>فیلتر و جستجوی پیشرفته بازرسیها |
|
</h3> |
|
<form id="searchForm" class="grid" autocomplete="off" novalidate> |
|
<div> |
|
<label for="searchInspector">بازرس</label> |
|
<select id="searchInspector"> |
|
<option value="">همه بازرسها</option> |
|
</select> |
|
</div> |
|
<div> |
|
<label for="searchWorkshopName">نام کارگاه</label> |
|
<input type="text" id="searchWorkshopName" placeholder="بخشی از نام کارگاه..."> |
|
</div> |
|
<div> |
|
<label for="searchActivityField">رسته فعالیت</label> |
|
<input type="text" id="searchActivityField" placeholder="بخشی از رسته فعالیت..."> |
|
</div> |
|
<div> |
|
<label for="searchEmployerName">نام کارفرما</label> |
|
<input type="text" id="searchEmployerName" placeholder="بخشی از نام کارفرما..."> |
|
</div> |
|
<div> |
|
<label for="searchMinEmployees">حداقل تعداد نیرو</label> |
|
<input type="number" id="searchMinEmployees" min="0" placeholder="مثلا: 5"> |
|
</div> |
|
<div> |
|
<label for="searchMaxEmployees">حداکثر تعداد نیرو</label> |
|
<input type="number" id="searchMaxEmployees" min="0" placeholder="مثلا: 20"> |
|
</div> |
|
<div style="grid-column:1/-1" class="action-buttons" > |
|
<div class="button-group primary"> |
|
<button id="btnRunSearch" type="button">اعمال فیلتر و جستجو</button> |
|
<button id="btnClearSearch" type="button" class="btn-muted">پاک کردن فیلترها</button> |
|
</div> |
|
<div class="button-group secondary"> |
|
<button id="btnExportFiltered" type="button" class="btn-manager" disabled>خروجی نتایج فیلتر شده</button> |
|
</div> |
|
</div> |
|
<div id="searchMessage" class="small-muted" style="grid-column:1/-1;display:none"></div> |
|
</form> |
|
</div> |
|
<div class="card"> |
|
<h3 style="margin:0 0 16px 0;color:var(--p-dark)">نتایج جستجو</h3> |
|
<p id="searchResultSummary" class="small-muted">برای مشاهده نتایج، فیلترها را تنظیم کرده و روی دکمه "اعمال فیلتر" کلیک کنید.</p> |
|
<div class="table-container"> |
|
<table id="searchResultsTable" aria-label="نتایج جستجوی بازرسیها"> |
|
<thead> |
|
<tr><th>#</th><th>شماره پرسنلی</th><th>بازرس</th><th>نام کارگاه</th><th>تعداد نیرو</th><th>مختصات (Plus Code)</th><th>تاریخ شمسی</th><th>عملیات</th></tr> |
|
</thead> |
|
<tbody></tbody> |
|
</table> |
|
</div> |
|
</div> |
|
</section> |
|
|
|
<section id="settingsTab" class="tab-content" role="tabpanel" aria-hidden="true"> |
|
<div class="card"> |
|
<h3 style="margin:0 0 24px 0; color:var(--p-dark)"> |
|
<span class="card-header-icon">⚙️</span>تنظیمات و مدیریت داده |
|
</h3> |
|
<div class="settings-grid"> |
|
<div class="setting-card"> |
|
<h4>ورود داده (Import)</h4> |
|
<p>بازگردانی اطلاعات از فایل پشتیبان (با فرمت JSON) که قبلاً ذخیره کردهاید.</p> |
|
<button id="btnImportData" type="button" class="btn-muted">انتخاب فایل پشتیبان</button> |
|
<input type="file" id="fileImporter" accept=".json" style="display:none;"> |
|
</div> |
|
<div class="setting-card"> |
|
<h4>خروج کلی داده (Export)</h4> |
|
<p>ذخیره یک فایل پشتیبان (JSON) از تمام بازرسیها، بازرسان و مدیران ثبت شده در سیستم.</p> |
|
<button id="btnExportAllData" type="button">ذخیره فایل پشتیبان</button> |
|
</div> |
|
<div class="setting-card"> |
|
<h4>پاکسازی کلیه دادهها</h4> |
|
<p>تمام اطلاعات ثبت شده (بازرسیها، بازرسان و مدیران) برای همیشه حذف خواهند شد.</p> |
|
<button id="btnClearAllData" type="button" class="btn-danger">حذف تمام اطلاعات</button> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="card"> |
|
<h3 style="margin:0 0 24px 0; color:var(--p-dark)"> |
|
<span class="card-header-icon">📖</span>راهنمای استفاده از سامانه |
|
</h3> |
|
<div class="help-section"> |
|
<details> |
|
<summary>شروع به کار و ثبت اطلاعات اولیه</summary> |
|
<div class="details-content"> |
|
<p>برای شروع، ابتدا باید اطلاعات اولیه را در سیستم ثبت کنید:</p> |
|
<ol> |
|
<li><strong>ثبت بازرسها:</strong> به تب «ثبت بازرسها» بروید و اطلاعات تمام بازرسان از جمله کد ملی، نام و شماره پرسنلی را وارد کنید.</li> |
|
<li><strong>ثبت مدیران:</strong> به تب «ثبت مدیران» مراجعه کرده و اطلاعات مدیرانی که گزارشها برایشان ارسال میشود را ثبت نمایید. این اطلاعات برای استفاده از دکمه «ارسال مستقیم به رئیس شعبه» ضروری است.</li> |
|
</ol> |
|
</div> |
|
</details> |
|
<details> |
|
<summary>نحوه ثبت یک بازرسی جدید</summary> |
|
<div class="details-content"> |
|
<p>در تب اصلی «فرم بازرسی» مراحل زیر را دنبال کنید:</p> |
|
<ol> |
|
<li>شماره پرسنلی بازرس را وارد کنید. نام او باید به صورت خودکار نمایش داده شود.</li> |
|
<li>اطلاعات کارگاه، کارفرما و تعداد نیروهای شاغل را وارد کنید.</li> |
|
<li>اگر کارگری در کارگاه حضور دارد، فرم مربوط به اطلاعات آنها را پر کنید.</li> |
|
<li>برای ثبت موقعیت جغرافیایی، روی دکمه <strong>«ثبت مختصات GPS»</strong> کلیک کنید. اگر GPS در دسترس نبود، سیستم از مختصات مبتنی بر اینترنت استفاده میکند. در غیر این صورت، با دکمه «وارد کردن دستی» مختصات را وارد نمایید.</li> |
|
<li>پس از تکمیل فرم، روی دکمه <strong>«ثبت بازرسی»</strong> کلیک کنید.</li> |
|
</ol> |
|
</div> |
|
</details> |
|
<details> |
|
<summary>خروجی و اشتراکگذاری گزارشها</summary> |
|
<div class="details-content"> |
|
<p>پس از ثبت بازرسیها، میتوانید از آنها گزارش تهیه کنید:</p> |
|
<ul> |
|
<li><strong>دکمه خروجی گزارش:</strong> با این دکمه میتوانید از کل بازرسیها در فرمتهای مختلف مانند Excel, Word, PDF و متن ساده خروجی بگیرید.</li> |
|
<li><strong>ارسال مستقیم به رئیس شعبه:</strong> این دکمه یک مودال باز میکند که در آن میتوانید مدیر و روش ارسال (ایمیل، تلگرام و...) را انتخاب کنید. پس از تایید، متن گزارش در حافظه کلیپبورد شما کپی شده و اپلیکیشن مربوطه باز میشود تا شما متن را Paste و ارسال کنید.</li> |
|
</ul> |
|
</div> |
|
</details> |
|
<details> |
|
<summary>استفاده از جستجوی پیشرفته</summary> |
|
<div class="details-content"> |
|
<p>در تب «جستجوی پیشرفته» میتوانید بازرسیها را بر اساس معیارهای مختلف فیلتر کنید. پس از اعمال فیلتر، لیست نتایج در جدول پایین نمایش داده میشود. دکمه <strong>«خروجی نتایج فیلتر شده»</strong> به شما این امکان را میدهد که فقط از نتایجی که مشاهده میکنید، خروجی تهیه کنید.</p> |
|
</div> |
|
</details> |
|
</div> |
|
</div> |
|
</section> |
|
</div> |
|
</main> |
|
<div id="exportMenu" class="export-menu"> |
|
<div class="export-content"> |
|
<div class="export-header"> |
|
<h3>انتخاب فرمت خروجی</h3> |
|
<button id="exportClose" class="export-close">×</button> |
|
</div> |
|
<div class="export-options"> |
|
<div class="export-option" data-format="excel"> |
|
<h4>Excel (.xlsx)</h4> |
|
<p>خروجی اکسل با تمام جزئیات بازرسیها و امکان فیلتر و جستجو</p> |
|
</div> |
|
<div class="export-option" data-format="word"> |
|
<h4>Word (HTML)</h4> |
|
<p>فایل HTML قابل باز شدن در Microsoft Word با قالببندی حرفهای</p> |
|
</div> |
|
<div class="export-option" data-format="pdf"> |
|
<h4>PDF (چاپی)</h4> |
|
<p>باز کردن گزارش در پنجره جدید با قابلیت چاپ مستقیم یا ذخیره به صورت PDF</p> |
|
</div> |
|
<div class="export-option" data-format="text"> |
|
<h4>متن ساده (.txt)</h4> |
|
<p>فایل متنی با تمام اطلاعات بازرسیها مناسب برای ارسال از طریق ایمیل</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<div id="shareModal" class="modal"> |
|
<div class="modal-content"> |
|
<div class="modal-header"> |
|
<h3>ارسال گزارش به مدیر</h3> |
|
<span class="close">×</span> |
|
</div> |
|
<div class="modal-body"> |
|
<div class="grid"> |
|
<div> |
|
<label for="shareManager">انتخاب مدیر</label> |
|
<select id="shareManager"> |
|
<option value="">-- انتخاب کنید --</option> |
|
</select> |
|
</div> |
|
<div> |
|
<label for="shareMethod">روش ارسال</label> |
|
<select id="shareMethod"> |
|
<option value="email">ایمیل</option> |
|
<option value="telegram">تلگرام</option> |
|
<option value="whatsapp">واتساپ</option> |
|
</select> |
|
</div> |
|
<div style="grid-column:1/-1"> |
|
<label for="shareMessage">پیام بازرس</label> |
|
<textarea id="shareMessage" rows="3" placeholder="پیام خود را وارد کنید..."></textarea> |
|
</div> |
|
</div> |
|
<div class="buttons"> |
|
<button id="btnSendShare" type="button" disabled>ارسال</button> |
|
<button id="btnCancelShare" type="button" class="btn-muted">انصراف</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<footer> |
|
© طراح و توسعه دهنده: اشکان پورعلی — حسابدار واحد مالی شعبه دو اهواز، — سازمان تامین اجتماعی |
|
</footer> |
|
<div class="author-badge"> |
|
<div class="author-badge-text"> |
|
<div class="author-badge-name">اشکان پورعلی</div> |
|
<div class="author-badge-title">حسابدار واحد مالی سازمان تامین اجتماعی شعبه دو اهواز</div> |
|
</div> |
|
</div> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jalaali.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/xlsx.full.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/openlocationcode.js"></script> |
|
<script> |
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
document.querySelectorAll('form').forEach(form => { |
|
form.addEventListener('submit', (e) => { |
|
e.preventDefault(); |
|
return false; |
|
}); |
|
}); |
|
|
|
|
|
const tabs = document.querySelectorAll('.tab'); |
|
const tabContents = document.querySelectorAll('.tab-content'); |
|
function activateTab(targetId){ |
|
tabs.forEach(t => t.classList.toggle('active', t.dataset.target === targetId)); |
|
tabContents.forEach(c => { |
|
c.classList.toggle('active', c.id === targetId); |
|
c.setAttribute('aria-hidden', c.id === targetId ? 'false' : 'true'); |
|
}); |
|
|
|
if (targetId === 'analysisTab') { |
|
renderAnalysis(); |
|
} |
|
|
|
if (targetId === 'searchTab') { |
|
updateSearchInspectorOptions(); |
|
} |
|
} |
|
tabs.forEach(t => { |
|
t.addEventListener('click', () => activateTab(t.dataset.target)); |
|
t.addEventListener('keydown', e => { if(e.key === 'Enter' || e.key === ' ') activateTab(t.dataset.target); }); |
|
}); |
|
|
|
const KEY_INSPECTORS = 'inspectors'; |
|
const KEY_INSPECTIONS = 'inspections'; |
|
const KEY_MANAGERS = 'managers'; |
|
function loadInspectors(){ return JSON.parse(localStorage.getItem(KEY_INSPECTORS) || '[]'); } |
|
function saveInspectors(arr){ localStorage.setItem(KEY_INSPECTORS, JSON.stringify(arr)); } |
|
function loadInspections(){ return JSON.parse(localStorage.getItem(KEY_INSPECTIONS) || '[]'); } |
|
function saveInspections(arr){ localStorage.setItem(KEY_INSPECTIONS, JSON.stringify(arr)); } |
|
function loadManagers(){ return JSON.parse(localStorage.getItem(KEY_MANAGERS) || '[]'); } |
|
function saveManagers(arr){ localStorage.setItem(KEY_MANAGERS, JSON.stringify(arr)); } |
|
|
|
const inspectorsTableBody = document.querySelector('#inspectorsTable tbody'); |
|
const managersTableBody = document.querySelector('#managersTable tbody'); |
|
const btnAddInspector = document.getElementById('btnAddInspector'); |
|
const btnClearInspector = document.getElementById('btnClearInspector'); |
|
const mgrNational = document.getElementById('mgr_national'); |
|
const mgrName = document.getElementById('mgr_name'); |
|
const mgrFamily = document.getElementById('mgr_family'); |
|
const mgrPersonnel = document.getElementById('mgr_personnel'); |
|
const inspPersonnel = document.getElementById('insp_personnel'); |
|
const inspectorInfo = document.getElementById('inspectorInfo'); |
|
const workshopName = document.getElementById('workshopName'); |
|
const activityField = document.getElementById('activityField'); |
|
const employerName = document.getElementById('employerName'); |
|
const employerNationalId = document.getElementById('employerNationalId'); |
|
const numEmployees = document.getElementById('numEmployees'); |
|
const workersList = document.getElementById('workersList'); |
|
const lastInspectionDate = document.getElementById('lastInspectionDate'); |
|
const gpsField = document.getElementById('gpsField'); |
|
const btnGetGPS = document.getElementById('btnGetGPS'); |
|
const btnManualGPS = document.getElementById('btnManualGPS'); |
|
const btnSubmitInspection = document.getElementById('btnSubmitInspection'); |
|
const btnReset = document.getElementById('btnReset'); |
|
const inspectionsTableBody = document.querySelector('#inspectionsTable tbody'); |
|
const exportBtn = document.getElementById('exportBtn'); |
|
const exportMenu = document.getElementById('exportMenu'); |
|
const exportClose = document.getElementById('exportClose'); |
|
const exportOptions = document.querySelectorAll('.export-option'); |
|
const formMessage = document.getElementById('formMessage'); |
|
|
|
|
|
const inspectionComments = document.getElementById('inspectionComments'); |
|
const btnSendToManager = document.getElementById('btnSendToManager'); |
|
const btnSaveData = document.getElementById('btnSaveData'); |
|
const btnAddManager = document.getElementById('btnAddManager'); |
|
const btnClearManager = document.getElementById('btnClearManager'); |
|
const mgtNational = document.getElementById('mgt_national'); |
|
const mgtName = document.getElementById('mgt_name'); |
|
const mgtFamily = document.getElementById('mgt_family'); |
|
const mgtPosition = document.getElementById('mgt_position'); |
|
const mgtEmail = document.getElementById('mgt_email'); |
|
const mgtPhone = document.getElementById('mgt_phone'); |
|
const mgtTelegram = document.getElementById('mgt_telegram'); |
|
const shareModal = document.getElementById('shareModal'); |
|
const shareManager = document.getElementById('shareManager'); |
|
const shareMethod = document.getElementById('shareMethod'); |
|
const shareMessage = document.getElementById('shareMessage'); |
|
const btnSendShare = document.getElementById('btnSendShare'); |
|
const btnCancelShare = document.getElementById('btnCancelShare'); |
|
const modalClose = document.querySelector('.close'); |
|
|
|
|
|
const btnRefreshAnalysis = document.getElementById('btnRefreshAnalysis'); |
|
const totalInspectionsStat = document.getElementById('totalInspectionsStat'); |
|
const totalInspectorsStat = document.getElementById('totalInspectorsStat'); |
|
const avgEmployeesStat = document.getElementById('avgEmployeesStat'); |
|
const totalWorkersStat = document.getElementById('totalWorkersStat'); |
|
const activityFieldChart = document.getElementById('activityFieldChart'); |
|
const inspectionsOverTimeChart = document.getElementById('inspectionsOverTimeChart'); |
|
const advancedInspectorStats = document.getElementById('advancedInspectorStats'); |
|
const employeeDistributionChart = document.getElementById('employeeDistributionChart'); |
|
const btnShowMap = document.getElementById('btnShowMap'); |
|
|
|
|
|
const searchForm = document.getElementById('searchForm'); |
|
const searchInspector = document.getElementById('searchInspector'); |
|
const searchWorkshopName = document.getElementById('searchWorkshopName'); |
|
const searchActivityField = document.getElementById('searchActivityField'); |
|
const searchEmployerName = document.getElementById('searchEmployerName'); |
|
const searchMinEmployees = document.getElementById('searchMinEmployees'); |
|
const searchMaxEmployees = document.getElementById('searchMaxEmployees'); |
|
const btnRunSearch = document.getElementById('btnRunSearch'); |
|
const btnClearSearch = document.getElementById('btnClearSearch'); |
|
const btnExportFiltered = document.getElementById('btnExportFiltered'); |
|
const searchResultsTableBody = document.querySelector('#searchResultsTable tbody'); |
|
const searchResultSummary = document.getElementById('searchResultSummary'); |
|
const searchMessageEl = document.getElementById('searchMessage'); |
|
|
|
|
|
const btnImportData = document.getElementById('btnImportData'); |
|
const fileImporter = document.getElementById('fileImporter'); |
|
const btnExportAllData = document.getElementById('btnExportAllData'); |
|
const btnClearAllData = document.getElementById('btnClearAllData'); |
|
|
|
const { toJalaali } = window.jalaali; |
|
function toJalaliString(d = new Date()){ |
|
const date = (d instanceof Date) ? d : new Date(d); |
|
const j = toJalaali(date.getFullYear(), date.getMonth()+1, date.getDate()); |
|
return `${j.jy}/${String(j.jm).padStart(2,'0')}/${String(j.jd).padStart(2,'0')}`; |
|
} |
|
|
|
|
|
function convertToPlusCode(gpsString) { |
|
if (!gpsString || gpsString.trim() === '') { |
|
return ''; |
|
} |
|
|
|
try { |
|
const parts = gpsString.split(','); |
|
if (parts.length !== 2) { |
|
return gpsString; |
|
} |
|
|
|
const lat = parseFloat(parts[0].trim()); |
|
const lon = parseFloat(parts[1].trim()); |
|
|
|
if (isNaN(lat) || isNaN(lon)) { |
|
return gpsString; |
|
} |
|
|
|
|
|
const plusCode = OpenLocationCode.encode(lat, lon, 10); |
|
return plusCode; |
|
} catch (error) { |
|
console.error('خطا در تبدیل به Plus Code:', error); |
|
return gpsString; |
|
} |
|
} |
|
|
|
|
|
let editingInspectorIndex = -1; |
|
let editingInspectionIndex = -1; |
|
let editingManagerIndex = -1; |
|
let currentInspector = null; |
|
let filteredInspections = []; |
|
|
|
|
|
function renderInspectors(){ |
|
const list = loadInspectors(); |
|
inspectorsTableBody.innerHTML = ''; |
|
list.forEach((it, idx) => { |
|
const tr = document.createElement('tr'); |
|
tr.innerHTML = `<td>${idx+1}</td><td>${it.national}</td><td>${it.name}</td><td>${it.family}</td><td>${it.personnel}</td> |
|
<td> |
|
<button type="button" class="edit-inspector" data-idx="${idx}">ویرایش</button> |
|
<button type="button" class="delete-inspector btn-danger" data-idx="${idx}">حذف</button> |
|
</td>`; |
|
inspectorsTableBody.appendChild(tr); |
|
}); |
|
document.querySelectorAll('.edit-inspector').forEach(b => b.addEventListener('click', e => { |
|
const idx = Number(e.currentTarget.dataset.idx); |
|
const list = loadInspectors(); |
|
const rec = list[idx]; |
|
mgrNational.value = rec.national; mgrName.value = rec.name; mgrFamily.value = rec.family; mgrPersonnel.value = rec.personnel; |
|
editingInspectorIndex = idx; |
|
activateTab('inspectorsTab'); |
|
})); |
|
document.querySelectorAll('.delete-inspector').forEach(b => b.addEventListener('click', e => { |
|
const idx = Number(e.currentTarget.dataset.idx); |
|
if(!confirm('آیا مطمئن هستید حذف شود؟')) return; |
|
const arr = loadInspectors(); arr.splice(idx,1); saveInspectors(arr); renderInspectors(); |
|
if(editingInspectorIndex === idx){ mgrNational.value=''; mgrName.value=''; mgrFamily.value=''; mgrPersonnel.value=''; editingInspectorIndex=-1; } |
|
})); |
|
} |
|
|
|
|
|
function renderManagers() { |
|
const list = loadManagers(); |
|
managersTableBody.innerHTML = ''; |
|
list.forEach((it, idx) => { |
|
const tr = document.createElement('tr'); |
|
tr.innerHTML = `<td>${idx+1}</td><td>${it.national}</td><td>${it.name}</td><td>${it.family}</td><td>${it.position}</td><td>${it.email}</td><td>${it.phone}</td><td>${it.telegram || '-'}</td> |
|
<td> |
|
<button type="button" class="edit-manager" data-idx="${idx}">ویرایش</button> |
|
<button type="button" class="delete-manager btn-danger" data-idx="${idx}">حذف</button> |
|
</td>`; |
|
managersTableBody.appendChild(tr); |
|
}); |
|
|
|
|
|
updateShareManagerOptions(); |
|
|
|
document.querySelectorAll('.edit-manager').forEach(b => b.addEventListener('click', e => { |
|
const idx = Number(e.currentTarget.dataset.idx); |
|
const list = loadManagers(); |
|
const rec = list[idx]; |
|
mgtNational.value = rec.national; |
|
mgtName.value = rec.name; |
|
mgtFamily.value = rec.family; |
|
mgtPosition.value = rec.position; |
|
mgtEmail.value = rec.email; |
|
mgtPhone.value = rec.phone; |
|
mgtTelegram.value = rec.telegram || ''; |
|
editingManagerIndex = idx; |
|
activateTab('managersTab'); |
|
})); |
|
|
|
document.querySelectorAll('.delete-manager').forEach(b => b.addEventListener('click', e => { |
|
const idx = Number(e.currentTarget.dataset.idx); |
|
if(!confirm('آیا مطمئن هستید حذف شود؟')) return; |
|
const arr = loadManagers(); |
|
arr.splice(idx,1); |
|
saveManagers(arr); |
|
renderManagers(); |
|
if(editingManagerIndex === idx){ |
|
mgtNational.value=''; |
|
mgtName.value=''; |
|
mgtFamily.value=''; |
|
mgtPosition.value=''; |
|
mgtEmail.value=''; |
|
mgtPhone.value=''; |
|
mgtTelegram.value=''; |
|
editingManagerIndex=-1; |
|
} |
|
})); |
|
} |
|
|
|
|
|
function updateShareManagerOptions() { |
|
const managers = loadManagers(); |
|
shareManager.innerHTML = '<option value="">-- انتخاب کنید --</option>'; |
|
|
|
managers.forEach(manager => { |
|
const option = document.createElement('option'); |
|
option.value = manager.national; |
|
option.textContent = `${manager.name} ${manager.family} - ${manager.position}`; |
|
shareManager.appendChild(option); |
|
}); |
|
|
|
|
|
btnSendShare.disabled = !shareManager.value; |
|
} |
|
|
|
|
|
btnAddInspector.addEventListener('click', () => { |
|
const national = mgrNational.value.trim(), name = mgrName.value.trim(), family = mgrFamily.value.trim(), personnel = mgrPersonnel.value.trim(); |
|
if(!national || !name || !family || !personnel){ alert('لطفاً همه فیلدها را کامل کنید.'); return; } |
|
const arr = loadInspectors(); |
|
const dup = arr.some((it,i) => (it.national === national || it.personnel === personnel) && i !== editingInspectorIndex); |
|
if(dup){ alert('کد ملی یا شماره پرسنلی تکراری است.'); return; } |
|
const rec = { national, name, family, personnel }; |
|
if(editingInspectorIndex > -1){ arr[editingInspectorIndex] = rec; editingInspectorIndex = -1; } else arr.push(rec); |
|
saveInspectors(arr); renderInspectors(); mgrNational.value=''; mgrName.value=''; mgrFamily.value=''; mgrPersonnel.value=''; alert('ذخیره شد.'); |
|
}); |
|
btnClearInspector.addEventListener('click', ()=>{ mgrNational.value=''; mgrName.value=''; mgrFamily.value=''; mgrPersonnel.value=''; editingInspectorIndex=-1; }); |
|
|
|
|
|
btnAddManager.addEventListener('click', () => { |
|
const national = mgtNational.value.trim(), name = mgtName.value.trim(), family = mgtFamily.value.trim(), |
|
position = mgtPosition.value.trim(), email = mgtEmail.value.trim(), phone = mgtPhone.value.trim(), |
|
telegram = mgtTelegram.value.trim(); |
|
if(!national || !name || !family || !position || !email || !phone){ alert('لطفاً فیلدهای ضروری را کامل کنید.'); return; } |
|
const arr = loadManagers(); |
|
const dup = arr.some((it,i) => (it.national === national || it.email === email) && i !== editingManagerIndex); |
|
if(dup){ alert('کد ملی یا ایمیل تکراری است.'); return; } |
|
const rec = { national, name, family, position, email, phone, telegram }; |
|
if(editingManagerIndex > -1){ arr[editingManagerIndex] = rec; editingManagerIndex = -1; } else arr.push(rec); |
|
saveManagers(arr); renderManagers(); |
|
mgtNational.value=''; mgtName.value=''; mgtFamily.value=''; mgtPosition.value=''; |
|
mgtEmail.value=''; mgtPhone.value=''; mgtTelegram.value=''; |
|
alert('ذخیره شد.'); |
|
}); |
|
btnClearManager.addEventListener('click', ()=>{ |
|
mgtNational.value=''; mgtName.value=''; mgtFamily.value=''; mgtPosition.value=''; |
|
mgtEmail.value=''; mgtPhone.value=''; mgtTelegram.value=''; |
|
editingManagerIndex=-1; |
|
}); |
|
|
|
|
|
function showMessage(msg, duration = 3000, element = formMessage) { |
|
element.textContent = msg; |
|
element.style.display = 'block'; |
|
setTimeout(() => { |
|
element.style.display = 'none'; |
|
}, duration); |
|
} |
|
|
|
|
|
inspPersonnel.addEventListener('input', () => { |
|
const personnel = inspPersonnel.value.trim(); |
|
if (!personnel) { |
|
inspectorInfo.textContent = ''; |
|
currentInspector = null; |
|
btnSubmitInspection.disabled = true; |
|
return; |
|
} |
|
|
|
const inspectors = loadInspectors(); |
|
const found = inspectors.find(it => it.personnel === personnel); |
|
|
|
if (found) { |
|
currentInspector = found; |
|
inspectorInfo.innerHTML = `بازرس: <strong>${found.name} ${found.family}</strong>`; |
|
btnSubmitInspection.disabled = false; |
|
} else { |
|
inspectorInfo.textContent = 'بازرسی با این شماره پرسنلی یافت نشد.'; |
|
currentInspector = null; |
|
btnSubmitInspection.disabled = true; |
|
} |
|
}); |
|
|
|
|
|
numEmployees.addEventListener('input', () => { |
|
const count = parseInt(numEmployees.value) || 0; |
|
workersList.innerHTML = ''; |
|
|
|
if (count > 0) { |
|
workersList.innerHTML = '<h4 style="margin:0 0 12px 0">لیست کارگران</h4>'; |
|
|
|
for (let i = 1; i <= count; i++) { |
|
const workerBlock = document.createElement('div'); |
|
workerBlock.className = 'worker-block'; |
|
workerBlock.innerHTML = ` |
|
<h5 style="margin:0 0 12px 0">کارگر شماره ${i}</h5> |
|
<div class="grid"> |
|
<div> |
|
<label>نام</label> |
|
<input type="text" id="worker_name_${i}" /> |
|
</div> |
|
<div> |
|
<label>نام خانوادگی</label> |
|
<input type="text" id="worker_family_${i}" /> |
|
</div> |
|
<div class="optional-field"> |
|
<label>کد ملی</label> |
|
<input type="text" id="worker_national_${i}" /> |
|
</div> |
|
<div class="optional-field"> |
|
<label>شماره بیمه</label> |
|
<input type="text" id="worker_insurance_${i}" /> |
|
</div> |
|
</div> |
|
`; |
|
workersList.appendChild(workerBlock); |
|
} |
|
} |
|
}); |
|
|
|
|
|
btnGetGPS.addEventListener('click', () => { |
|
if (navigator.geolocation) { |
|
btnGetGPS.disabled = true; |
|
btnGetGPS.textContent = 'در حال دریافت مختصات...'; |
|
|
|
navigator.geolocation.getCurrentPosition( |
|
position => { |
|
const lat = position.coords.latitude; |
|
const lon = position.coords.longitude; |
|
gpsField.value = `${lat.toFixed(6)}, ${lon.toFixed(6)}`; |
|
btnGetGPS.disabled = false; |
|
btnGetGPS.textContent = 'ثبت مختصات GPS'; |
|
showMessage('مختصات GPS با موفقیت ثبت شد.'); |
|
}, |
|
error => { |
|
|
|
fetch('https://ipapi.co/json/') |
|
.then(response => response.json()) |
|
.then(data => { |
|
if (data.latitude && data.longitude) { |
|
gpsField.value = `${data.latitude}, ${data.longitude}`; |
|
showMessage('مختصات تقریبی از طریق IP دریافت شد.'); |
|
} else { |
|
showMessage('خطا در دریافت مختصات. لطفاً مختصات را به صورت دستی وارد کنید.'); |
|
} |
|
}) |
|
.catch(err => { |
|
showMessage('خطا در دریافت مختصات. لطفاً مختصات را به صورت دستی وارد کنید.'); |
|
}) |
|
.finally(() => { |
|
btnGetGPS.disabled = false; |
|
btnGetGPS.textContent = 'ثبت مختصات GPS'; |
|
}); |
|
} |
|
); |
|
} else { |
|
showMessage('مرورگر شما از GPS پشتیبانی نمیکند. لطفاً مختصات را به صورت دستی وارد کنید.'); |
|
} |
|
}); |
|
|
|
|
|
btnManualGPS.addEventListener('click', () => { |
|
const manualGps = prompt('لطفاً مختصات GPS را به فرمت lat,lon وارد کنید:'); |
|
if (manualGps) { |
|
|
|
const parts = manualGps.split(','); |
|
if (parts.length === 2 && !isNaN(parseFloat(parts[0])) && !isNaN(parseFloat(parts[1]))) { |
|
gpsField.value = manualGps; |
|
showMessage('مختصات با موفقیت ثبت شد.'); |
|
} else { |
|
showMessage('فرمت مختصات نامعتبر است. لطفاً از فرمت lat,lon استفاده کنید.'); |
|
} |
|
} |
|
}); |
|
|
|
|
|
btnSubmitInspection.addEventListener('click', () => { |
|
if (!currentInspector) { |
|
showMessage('لطفاً شماره پرسنلی بازرس را وارد کنید.'); |
|
return; |
|
} |
|
|
|
if (!workshopName.value.trim()) { |
|
showMessage('لطفاً نام کارگاه را وارد کنید.'); |
|
return; |
|
} |
|
|
|
if (!gpsField.value.trim()) { |
|
showMessage('لطفاً مختصات GPS را ثبت کنید.'); |
|
return; |
|
} |
|
|
|
const inspection = { |
|
id: Date.now(), |
|
personnel: inspPersonnel.value.trim(), |
|
inspectorName: `${currentInspector.name} ${currentInspector.family}`, |
|
workshopName: workshopName.value.trim(), |
|
activityField: activityField.value.trim(), |
|
employerName: employerName.value.trim(), |
|
employerNationalId: employerNationalId.value.trim(), |
|
numEmployees: parseInt(numEmployees.value) || 0, |
|
workers: [], |
|
gps: gpsField.value.trim(), |
|
plusCode: convertToPlusCode(gpsField.value.trim()), |
|
date: new Date().toISOString(), |
|
jalaliDate: toJalaliString(), |
|
comments: inspectionComments.value.trim() |
|
}; |
|
|
|
|
|
if (inspection.numEmployees > 0) { |
|
for (let i = 1; i <= inspection.numEmployees; i++) { |
|
const name = document.getElementById(`worker_name_${i}`)?.value.trim() || ''; |
|
const family = document.getElementById(`worker_family_${i}`)?.value.trim() || ''; |
|
const national = document.getElementById(`worker_national_${i}`)?.value.trim() || ''; |
|
const insurance = document.getElementById(`worker_insurance_${i}`)?.value.trim() || ''; |
|
|
|
inspection.workers.push({ name, family, national, insurance }); |
|
} |
|
} |
|
|
|
const inspections = loadInspections(); |
|
if(editingInspectionIndex > -1){ |
|
inspections[editingInspectionIndex] = inspection; |
|
editingInspectionIndex = -1; |
|
showMessage('بازرسی با موفقیت بروزرسانی شد.'); |
|
} else { |
|
inspections.push(inspection); |
|
showMessage('بازرسی با موفقیت ثبت شد.'); |
|
} |
|
saveInspections(inspections); |
|
|
|
|
|
lastInspectionDate.textContent = inspection.jalaliDate; |
|
|
|
renderInspections(); |
|
|
|
|
|
document.getElementById('inspectionForm').reset(); |
|
workersList.innerHTML = ''; |
|
inspectorInfo.textContent = ''; |
|
lastInspectionDate.textContent = 'هنوز بازرسی ثبت نشده'; |
|
currentInspector = null; |
|
btnSubmitInspection.disabled = true; |
|
|
|
|
|
if(loadInspections().length > 0){ |
|
btnSendToManager.disabled = false; |
|
exportBtn.disabled = false; |
|
} |
|
renderAnalysis(); |
|
}); |
|
|
|
|
|
btnReset.addEventListener('click', () => { |
|
if (confirm('آیا از پاک کردن فرم مطمئن هستید؟')) { |
|
|
|
document.getElementById('inspectionForm').reset(); |
|
workersList.innerHTML = ''; |
|
inspectorInfo.textContent = ''; |
|
lastInspectionDate.textContent = 'هنوز بازرسی ثبت نشده'; |
|
currentInspector = null; |
|
|
|
showMessage('فرم با موفقیت پاک شد.'); |
|
} |
|
}); |
|
|
|
|
|
function renderInspections(inspections = loadInspections(), tableBody = inspectionsTableBody) { |
|
tableBody.innerHTML = ''; |
|
|
|
inspections.forEach((inspection, index) => { |
|
const tr = document.createElement('tr'); |
|
const displayGps = inspection.plusCode || inspection.gps; |
|
|
|
tr.innerHTML = ` |
|
<td>${index + 1}</td> |
|
<td>${inspection.personnel}</td> |
|
<td>${inspection.inspectorName}</td> |
|
<td>${inspection.workshopName}</td> |
|
<td>${inspection.numEmployees}</td> |
|
<td>${displayGps}</td> |
|
<td>${inspection.jalaliDate}</td> |
|
<td> |
|
<button type="button" class="edit-inspection" data-id="${inspection.id}">ویرایش</button> |
|
<button type="button" class="delete-inspection btn-danger" data-id="${inspection.id}">حذف</button> |
|
</td> |
|
`; |
|
|
|
tableBody.appendChild(tr); |
|
}); |
|
|
|
document.querySelectorAll('.edit-inspection').forEach(button => { |
|
button.addEventListener('click', handleEditInspection); |
|
}); |
|
|
|
document.querySelectorAll('.delete-inspection').forEach(button => { |
|
button.addEventListener('click', handleDeleteInspection); |
|
}); |
|
|
|
const allInspections = loadInspections(); |
|
if(allInspections.length > 0){ |
|
btnSendToManager.disabled = false; |
|
exportBtn.disabled = false; |
|
} else { |
|
btnSendToManager.disabled = true; |
|
exportBtn.disabled = true; |
|
} |
|
} |
|
|
|
|
|
function handleEditInspection(e) { |
|
const inspectionId = Number(e.target.dataset.id); |
|
const inspections = loadInspections(); |
|
const inspectionIndex = inspections.findIndex(insp => insp.id === inspectionId); |
|
|
|
if (inspectionIndex > -1) { |
|
const inspection = inspections[inspectionIndex]; |
|
inspPersonnel.value = inspection.personnel; |
|
inspPersonnel.dispatchEvent(new Event('input')); |
|
workshopName.value = inspection.workshopName; |
|
activityField.value = inspection.activityField; |
|
employerName.value = inspection.employerName; |
|
employerNationalId.value = inspection.employerNationalId; |
|
numEmployees.value = inspection.numEmployees; |
|
gpsField.value = inspection.gps; |
|
inspectionComments.value = inspection.comments; |
|
lastInspectionDate.textContent = inspection.jalaliDate; |
|
btnSubmitInspection.disabled = false; |
|
numEmployees.dispatchEvent(new Event('input')); |
|
inspection.workers.forEach((worker, i) => { |
|
const workerIndex = i + 1; |
|
if (document.getElementById(`worker_name_${workerIndex}`)) { |
|
document.getElementById(`worker_name_${workerIndex}`).value = worker.name; |
|
document.getElementById(`worker_family_${workerIndex}`).value = worker.family; |
|
document.getElementById(`worker_national_${workerIndex}`).value = worker.national; |
|
document.getElementById(`worker_insurance_${workerIndex}`).value = worker.insurance; |
|
} |
|
}); |
|
editingInspectionIndex = inspectionIndex; |
|
showMessage(`در حال ویرایش بازرسی کارگاه: ${inspection.workshopName}`); |
|
activateTab('inspectionTab'); |
|
window.scrollTo(0, 0); |
|
} |
|
} |
|
|
|
|
|
function handleDeleteInspection(e) { |
|
const inspectionId = Number(e.target.dataset.id); |
|
if (confirm(`آیا از حذف این بازرسی مطمئن هستید؟`)) { |
|
let inspections = loadInspections(); |
|
inspections = inspections.filter(insp => insp.id !== inspectionId); |
|
saveInspections(inspections); |
|
renderInspections(); |
|
showMessage('بازرسی با موفقیت حذف شد.'); |
|
renderAnalysis(); |
|
|
|
if(document.getElementById('searchTab').classList.contains('active')) { |
|
handleSearch(); |
|
} |
|
} |
|
} |
|
|
|
|
|
btnSaveData.addEventListener('click', () => { |
|
const data = { |
|
inspectors: loadInspectors(), |
|
inspections: loadInspections(), |
|
managers: loadManagers(), |
|
exportDate: new Date().toISOString() |
|
}; |
|
|
|
const dataStr = JSON.stringify(data, null, 2); |
|
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); |
|
|
|
const exportFileDefaultName = `backup_Tamin_${toJalaliString().replace(/\//g, '-')}.json`; |
|
|
|
const linkElement = document.createElement('a'); |
|
linkElement.setAttribute('href', dataUri); |
|
linkElement.setAttribute('download', exportFileDefaultName); |
|
linkElement.click(); |
|
|
|
showMessage('فایل پشتیبان با موفقیت ایجاد شد.'); |
|
}); |
|
|
|
|
|
exportBtn.addEventListener('click', () => { |
|
exportMenu.style.display = 'flex'; |
|
document.body.classList.add('modal-open'); |
|
}); |
|
|
|
exportClose.addEventListener('click', () => { |
|
exportMenu.style.display = 'none'; |
|
document.body.classList.remove('modal-open'); |
|
}); |
|
|
|
exportOptions.forEach(option => { |
|
option.addEventListener('click', () => { |
|
const format = option.dataset.format; |
|
exportMenu.style.display = 'none'; |
|
document.body.classList.remove('modal-open'); |
|
|
|
const inspectionsToExport = loadInspections(); |
|
|
|
switch (format) { |
|
case 'excel': |
|
exportToExcel(inspectionsToExport); |
|
break; |
|
case 'word': |
|
exportToWord(inspectionsToExport); |
|
break; |
|
case 'pdf': |
|
exportToPDF(inspectionsToExport); |
|
break; |
|
case 'text': |
|
exportToText(inspectionsToExport); |
|
break; |
|
} |
|
}); |
|
}); |
|
|
|
|
|
function exportToExcel(inspections) { |
|
if (inspections.length === 0) { |
|
alert('هیچ بازرسی برای خروجی گرفتن وجود ندارد.'); |
|
return; |
|
} |
|
const data = inspections.map((inspection, index) => { |
|
let workersInfo = (inspection.workers && inspection.workers.length > 0) |
|
? inspection.workers.map((w, i) => `${i + 1}. ${w.name} ${w.family} (کدملی: ${w.national || '-'})`).join('\n') |
|
: 'ندارد'; |
|
return [ |
|
index + 1, inspection.personnel, inspection.inspectorName, inspection.workshopName, |
|
inspection.activityField, inspection.employerName, inspection.employerNationalId, |
|
inspection.numEmployees, inspection.plusCode || '', inspection.gps, inspection.jalaliDate, |
|
inspection.comments, workersInfo |
|
]; |
|
}); |
|
|
|
data.unshift([ |
|
'#', 'شماره پرسنلی', 'بازرس', 'نام کارگاه', 'رسته فعالیت', |
|
'نام کارفرما', 'کد ملی کارفرما', 'تعداد نیرو', 'مختصات (Plus Code)', |
|
'مختصات (GPS)', 'تاریخ شمسی', 'توضیحات', 'اطلاعات کارگران' |
|
]); |
|
|
|
const ws = XLSX.utils.aoa_to_sheet(data); |
|
|
|
|
|
for (let i = 0; i < inspections.length; i++) { |
|
const inspection = inspections[i]; |
|
const rowIndex = i + 1; |
|
|
|
if (inspection.plusCode) { |
|
const plusCodeCellAddress = XLSX.utils.encode_cell({ r: rowIndex, c: 8 }); |
|
if (!ws[plusCodeCellAddress]) ws[plusCodeCellAddress] = {}; |
|
ws[plusCodeCellAddress].l = { Target: `https://www.google.com/maps/search/${encodeURIComponent(inspection.plusCode)}`, Tooltip: 'نمایش Plus Code در نقشه' }; |
|
} |
|
if (inspection.gps) { |
|
const gpsCellAddress = XLSX.utils.encode_cell({ r: rowIndex, c: 9 }); |
|
if (!ws[gpsCellAddress]) ws[gpsCellAddress] = {}; |
|
ws[gpsCellAddress].l = { Target: `https://maps.google.com/?q=${encodeURIComponent(inspection.gps)}`, Tooltip: 'نمایش GPS در نقشه' }; |
|
} |
|
} |
|
|
|
ws['!cols'] = [ |
|
{ wch: 5 }, { wch: 15 }, { wch: 20 }, { wch: 25 }, { wch: 20 }, { wch: 20 }, |
|
{ wch: 15 }, { wch: 10 }, { wch: 20 }, { wch: 25 }, { wch: 12 }, { wch: 30 }, { wch: 50 } |
|
]; |
|
|
|
const wb = XLSX.utils.book_new(); |
|
XLSX.utils.book_append_sheet(wb, ws, 'بازرسیها'); |
|
const fileName = `inspections_${toJalaliString().replace(/\//g, '-')}.xlsx`; |
|
XLSX.writeFile(wb, fileName); |
|
showMessage('خروجی Excel با موفقیت ایجاد شد.'); |
|
} |
|
|
|
|
|
function exportToWord(inspections) { |
|
if (inspections.length === 0) { alert('هیچ بازرسی برای خروجی گرفتن وجود ندارد.'); return; } |
|
let html = ` |
|
<!DOCTYPE html><html dir="rtl"><head><meta charset="UTF-8"><title>گزارش بازرسیها</title> |
|
<style>body{font-family:'Tahoma',sans-serif;direction:rtl;}table{border-collapse:collapse;width:100%;}th,td{border:1px solid #ddd;padding:8px;text-align:right;}th{background-color:#f2f2f2;}h1{color:#4361ee;}</style> |
|
</head><body><h1>گزارش بازرسیها</h1><p>تاریخ: ${toJalaliString()}</p> |
|
<table><thead><tr><th>#</th><th>بازرس</th><th>کارگاه</th><th>کارفرما</th><th>تعداد نیرو</th><th>Plus Code</th><th>GPS</th><th>تاریخ</th></tr></thead><tbody>`; |
|
|
|
inspections.forEach((inspection, index) => { |
|
const plusCodeLink = inspection.plusCode ? `<a href="https://www.google.com/maps/search/${encodeURIComponent(inspection.plusCode)}" target="_blank">${inspection.plusCode}</a>` : ''; |
|
const gpsLink = inspection.gps ? `<a href="https://maps.google.com/?q=${encodeURIComponent(inspection.gps)}" target="_blank">${inspection.gps}</a>` : ''; |
|
html += `<tr> |
|
<td>${index + 1}</td> |
|
<td>${inspection.inspectorName}</td> |
|
<td>${inspection.workshopName}</td> |
|
<td>${inspection.employerName}</td> |
|
<td>${inspection.numEmployees}</td> |
|
<td>${plusCodeLink}</td> |
|
<td>${gpsLink}</td> |
|
<td>${inspection.jalaliDate}</td> |
|
</tr>`; |
|
}); |
|
|
|
html += `</tbody></table></body></html>`; |
|
const blob = new Blob([html], { type: 'application/msword' }); |
|
const url = URL.createObjectURL(blob); |
|
const a = document.createElement('a'); |
|
a.href = url; |
|
a.download = `inspections_${toJalaliString().replace(/\//g, '-')}.doc`; |
|
a.click(); |
|
URL.revokeObjectURL(url); |
|
showMessage('خروجی Word با موفقیت ایجاد شد.'); |
|
} |
|
|
|
|
|
function exportToPDF(inspections) { |
|
if (inspections.length === 0) { alert('هیچ بازرسی برای خروجی گرفتن وجود ندارد.'); return; } |
|
const reportWindow = window.open('', '_blank'); |
|
let html = ` |
|
<!DOCTYPE html><html dir="rtl"><head><meta charset="UTF-8"><title>گزارش بازرسیها</title> |
|
<style>body{font-family:'Tahoma',sans-serif;direction:rtl;margin:20px;}table{border-collapse:collapse;width:100%;margin-top:20px;}th,td{border:1px solid #ddd;padding:8px;text-align:right;}th{background-color:#f2f2f2;}h1{color:#4361ee;text-align:center;}@media print{.no-print{display:none;}} a{color:#4361ee;text-decoration:none;} a:hover{text-decoration:underline;}</style> |
|
</head><body><div style="text-align:center;"><h1>گزارش بازرسیها</h1><p>تاریخ: ${toJalaliString()}</p></div> |
|
<table><thead><tr><th>#</th><th>بازرس</th><th>کارگاه</th><th>کارفرما</th><th>تعداد نیرو</th><th>Plus Code</th><th>GPS</th><th>تاریخ</th></tr></thead><tbody>`; |
|
|
|
inspections.forEach((inspection, index) => { |
|
const plusCodeLink = inspection.plusCode ? `<a href="https://www.google.com/maps/search/${encodeURIComponent(inspection.plusCode)}" target="_blank">${inspection.plusCode}</a>` : ''; |
|
const gpsLink = inspection.gps ? `<a href="https://maps.google.com/?q=${encodeURIComponent(inspection.gps)}" target="_blank">${inspection.gps}</a>` : ''; |
|
html += `<tr> |
|
<td>${index + 1}</td> |
|
<td>${inspection.inspectorName}</td> |
|
<td>${inspection.workshopName}</td> |
|
<td>${inspection.employerName}</td> |
|
<td>${inspection.numEmployees}</td> |
|
<td>${plusCodeLink}</td> |
|
<td>${gpsLink}</td> |
|
<td>${inspection.jalaliDate}</td> |
|
</tr>`; |
|
}); |
|
|
|
html += `</tbody></table><div class="no-print" style="text-align:center;margin-top:30px;"><button onclick="window.print()">چاپ</button></div></body></html>`; |
|
reportWindow.document.write(html); |
|
reportWindow.document.close(); |
|
showMessage('گزارش چاپی در پنجره جدید باز شد.'); |
|
} |
|
|
|
|
|
function exportToText(inspections) { |
|
if (inspections.length === 0) { alert('هیچ بازرسی برای خروجی گرفتن وجود ندارد.'); return; } |
|
let text = `گزارش بازرسیها - تاریخ: ${toJalaliString()}\n========================================\n\n`; |
|
|
|
inspections.forEach((inspection, index) => { |
|
text += `بازرسی شماره ${index + 1}:\n`; |
|
text += `----------------------------------------\n`; |
|
text += `بازرس: ${inspection.inspectorName}\n`; |
|
text += `کارگاه: ${inspection.workshopName}\n`; |
|
text += `تعداد نیرو: ${inspection.numEmployees}\n`; |
|
if (inspection.plusCode) text += `Plus Code: ${inspection.plusCode} (لینک: https://www.google.com/maps/search/${encodeURIComponent(inspection.plusCode)})\n`; |
|
if (inspection.gps) text += `GPS: ${inspection.gps} (لینک: https://maps.google.com/?q=${encodeURIComponent(inspection.gps)})\n`; |
|
text += `تاریخ: ${inspection.jalaliDate}\n\n`; |
|
}); |
|
|
|
const blob = new Blob([text], { type: 'text/plain' }); |
|
const url = URL.createObjectURL(blob); |
|
const a = document.createElement('a'); |
|
a.href = url; |
|
a.download = `inspections_${toJalaliString().replace(/\//g, '-')}.txt`; |
|
a.click(); |
|
URL.revokeObjectURL(url); |
|
showMessage('خروجی متنی با موفقیت ایجاد شد.'); |
|
} |
|
|
|
|
|
|
|
btnSendToManager.addEventListener('click', () => { |
|
const inspections = loadInspections(); |
|
if (inspections.length === 0) { |
|
showMessage('هیچ بازرسی ثبت شدهای وجود ندارد.'); |
|
return; |
|
} |
|
shareModal.style.display = 'flex'; |
|
document.body.classList.add('modal-open'); |
|
}); |
|
|
|
|
|
|
|
modalClose.addEventListener('click', () => { |
|
shareModal.style.display = 'none'; |
|
document.body.classList.remove('modal-open'); |
|
}); |
|
|
|
btnCancelShare.addEventListener('click', () => { |
|
shareModal.style.display = 'none'; |
|
document.body.classList.remove('modal-open'); |
|
}); |
|
|
|
|
|
shareManager.addEventListener('change', () => { |
|
btnSendShare.disabled = !shareManager.value; |
|
}); |
|
|
|
|
|
function showModernMessage(message) { |
|
|
|
const existingAlert = document.getElementById('modernAlert'); |
|
if (existingAlert) { |
|
existingAlert.remove(); |
|
} |
|
|
|
const alertDiv = document.createElement('div'); |
|
alertDiv.id = 'modernAlert'; |
|
alertDiv.className = 'modern-alert'; |
|
alertDiv.innerHTML = ` |
|
<div class="spinner"></div> |
|
<p style="margin:0;">${message}</p> |
|
`; |
|
document.body.appendChild(alertDiv); |
|
|
|
|
|
setTimeout(() => { |
|
alertDiv.classList.add('show'); |
|
}, 10); |
|
|
|
return alertDiv; |
|
} |
|
|
|
|
|
function hideModernMessage(alertElement) { |
|
if (alertElement) { |
|
alertElement.classList.remove('show'); |
|
setTimeout(() => { |
|
alertElement.remove(); |
|
}, 500); |
|
} |
|
} |
|
|
|
|
|
btnSendShare.addEventListener('click', () => { |
|
const managerId = shareManager.value; |
|
const method = shareMethod.value; |
|
const message = shareMessage.value.trim(); |
|
|
|
if (!managerId) { |
|
showMessage('لطفاً یک مدیر انتخاب کنید.'); |
|
return; |
|
} |
|
|
|
const managers = loadManagers(); |
|
const manager = managers.find(m => m.national === managerId); |
|
|
|
if (!manager) { |
|
showMessage('مدیر انتخاب شده یافت نشد.'); |
|
return; |
|
} |
|
|
|
const reportContent = createFullReportContent(loadInspections()); |
|
let finalMessage = `گزارش تمام بازرسیها\n\n`; |
|
finalMessage += `تاریخ: ${toJalaliString()}\n`; |
|
if (message) { |
|
finalMessage += `\nپیام بازرس:\n${message}\n`; |
|
} |
|
finalMessage += `\n${reportContent}`; |
|
|
|
navigator.clipboard.writeText(finalMessage).then(() => { |
|
shareModal.style.display = 'none'; |
|
document.body.classList.remove('modal-open'); |
|
const modernAlert = showModernMessage('گزارش در کلیپبورد کپی شد.<br>لطفاً آن را در برنامه مورد نظر الصاق (Paste) کنید.'); |
|
|
|
setTimeout(() => { |
|
hideModernMessage(modernAlert); |
|
const subject = encodeURIComponent(`گزارش بازرسی - ${toJalaliString()}`); |
|
const openLink = (url) => { |
|
const a = document.createElement('a'); |
|
a.href = url; |
|
a.target = '_blank'; |
|
document.body.appendChild(a); |
|
a.click(); |
|
document.body.removeChild(a); |
|
}; |
|
|
|
switch (method) { |
|
case 'email': openLink(`mailto:${manager.email}?subject=${subject}`); break; |
|
case 'telegram': openLink(`tg://resolve?domain=${manager.telegram}`); break; |
|
case 'whatsapp': |
|
if (!manager.phone) { alert('شماره تلفنی برای این مدیر ثبت نشده است.'); return; } |
|
let phoneNumber = manager.phone.replace(/\D/g, ''); |
|
if (phoneNumber.startsWith('09')) { phoneNumber = '98' + phoneNumber.substring(1); } |
|
openLink(`https://wa.me/${phoneNumber}`); |
|
break; |
|
} |
|
shareMessage.value = ''; |
|
}, 5000); |
|
|
|
}).catch(err => { |
|
console.error('خطا در کپی کردن: ', err); |
|
showMessage('متاسفانه کپی کردن در کلیپبورد با خطا مواجه شد.'); |
|
}); |
|
}); |
|
|
|
|
|
function createFullReportContent(inspections) { |
|
let content = ''; |
|
inspections.forEach((inspection, index) => { |
|
content += `بازرسی شماره ${index + 1}:\n`; |
|
content += `----------------------------------------\n`; |
|
content += `بازرس: ${inspection.inspectorName} (${inspection.personnel})\n`; |
|
content += `کارگاه: ${inspection.workshopName} (رسته: ${inspection.activityField})\n`; |
|
content += `کارفرما: ${inspection.employerName} (کدملی: ${inspection.employerNationalId})\n`; |
|
content += `تعداد نیرو: ${inspection.numEmployees}\n`; |
|
content += `مختصات: ${inspection.plusCode || inspection.gps}\n`; |
|
content += `لینک نقشه: https://maps.google.com/?q=${encodeURIComponent(inspection.gps)}\n` |
|
content += `تاریخ: ${inspection.jalaliDate}\n`; |
|
content += `توضیحات: ${inspection.comments || 'ندارد'}\n`; |
|
if (inspection.workers && inspection.workers.length > 0) { |
|
content += `\nلیست کارگران:\n`; |
|
inspection.workers.forEach((w, i) => { |
|
content += `${i + 1}. ${w.name} ${w.family} (کدملی: ${w.national || '-'}, بیمه: ${w.insurance || '-'}) \n`; |
|
}); |
|
} |
|
content += `\n\n`; |
|
}); |
|
return content; |
|
} |
|
|
|
|
|
function renderAnalysis() { |
|
const inspections = loadInspections(); |
|
const inspectors = loadInspectors(); |
|
|
|
const placeholder = (el, type='chart') => { |
|
const message = '<p class="small-muted" style="text-align:center; padding: 20px;">هنوز بازرسی ثبت نشده است تا تحلیلی ارائه شود.</p>'; |
|
el.innerHTML = message; |
|
}; |
|
|
|
if (inspections.length === 0) { |
|
totalInspectionsStat.textContent = '0'; |
|
totalInspectorsStat.textContent = inspectors.length; |
|
avgEmployeesStat.textContent = '0'; |
|
totalWorkersStat.textContent = '0'; |
|
placeholder(activityFieldChart); |
|
placeholder(inspectionsOverTimeChart); |
|
placeholder(advancedInspectorStats, 'table'); |
|
placeholder(employeeDistributionChart); |
|
btnShowMap.disabled = true; |
|
return; |
|
} |
|
|
|
totalInspectionsStat.textContent = inspections.length; |
|
totalInspectorsStat.textContent = inspectors.length; |
|
const totalEmployees = inspections.reduce((sum, insp) => sum + insp.numEmployees, 0); |
|
totalWorkersStat.textContent = totalEmployees; |
|
avgEmployeesStat.textContent = (totalEmployees / inspections.length).toFixed(1); |
|
btnShowMap.disabled = false; |
|
|
|
const inspectionsByActivity = inspections.reduce((acc, insp) => { |
|
const activity = insp.activityField.trim() || 'نامشخص'; |
|
acc[activity] = (acc[activity] || 0) + 1; |
|
return acc; |
|
}, {}); |
|
const sortedActivities = Object.entries(inspectionsByActivity).sort((a, b) => b[1] - a[1]); |
|
let activityChartHtml = '<div class="bar-chart">'; |
|
if (sortedActivities.length > 0) { |
|
const maxActivityCount = sortedActivities[0][1]; |
|
sortedActivities.forEach(([activity, count]) => { |
|
const barWidth = Math.max((count / maxActivityCount) * 100, 5); |
|
activityChartHtml += `<div class="bar-item"><span class="bar-label" title="${activity}">${activity}</span><div class="bar-wrapper"><div class="bar" style="width: ${barWidth}%;">${count} مورد</div></div></div>`; |
|
}); |
|
} |
|
activityChartHtml += '</div>'; |
|
activityFieldChart.innerHTML = activityChartHtml; |
|
|
|
const inspectionsByDate = inspections.reduce((acc, insp) => { |
|
acc[insp.jalaliDate] = (acc[insp.jalaliDate] || 0) + 1; |
|
return acc; |
|
}, {}); |
|
const sortedDates = Object.entries(inspectionsByDate).sort((a, b) => a[0].localeCompare(b[0])); |
|
let timeChartHtml = '<div class="bar-chart">'; |
|
if (sortedDates.length > 0) { |
|
const maxCount = Math.max(...sortedDates.map(d => d[1])); |
|
sortedDates.forEach(([date, count]) => { |
|
const barWidth = Math.max((count / maxCount) * 100, 5); |
|
timeChartHtml += `<div class="bar-item"><span class="bar-label" title="${date}">${date}</span><div class="bar-wrapper"><div class="bar" style="width: ${barWidth}%; background: var(--gradient-success);">${count} بازرسی</div></div></div>`; |
|
}); |
|
} |
|
timeChartHtml += '</div>'; |
|
inspectionsOverTimeChart.innerHTML = timeChartHtml; |
|
|
|
const inspectorData = inspections.reduce((acc, insp) => { |
|
if (!acc[insp.inspectorName]) { |
|
acc[insp.inspectorName] = { count: 0, totalEmployees: 0 }; |
|
} |
|
acc[insp.inspectorName].count++; |
|
acc[insp.inspectorName].totalEmployees += insp.numEmployees; |
|
return acc; |
|
}, {}); |
|
const sortedInspectorsData = Object.entries(inspectorData).sort((a, b) => b[1].count - a[1].count); |
|
let inspectorTableHtml = ` |
|
<table aria-label="آمار پیشرفته بازرسان"> |
|
<thead><tr><th>#</th><th>نام بازرس</th><th>تعداد بازرسی</th><th>مجموع نیرو</th><th>میانگین نیرو/بازرسی</th></tr></thead> |
|
<tbody> |
|
`; |
|
sortedInspectorsData.forEach(([name, data], idx) => { |
|
const avg = (data.totalEmployees / data.count).toFixed(1); |
|
inspectorTableHtml += `<tr><td>${idx+1}</td><td>${name}</td><td>${data.count}</td><td>${data.totalEmployees}</td><td>${avg}</td></tr>`; |
|
}); |
|
inspectorTableHtml += '</tbody></table>'; |
|
advancedInspectorStats.innerHTML = inspectorTableHtml; |
|
|
|
const employeeBins = { "۱-۵ نفر": 0, "۶-۱۰ نفر": 0, "۱۱-۲۰ نفر": 0, "۲۱-۵۰ نفر": 0, "۵۰+ نفر": 0 }; |
|
inspections.forEach(insp => { |
|
const n = insp.numEmployees; |
|
if (n >= 1 && n <= 5) employeeBins["۱-۵ نفر"]++; |
|
else if (n >= 6 && n <= 10) employeeBins["۶-۱۰ نفر"]++; |
|
else if (n >= 11 && n <= 20) employeeBins["۱۱-۲۰ نفر"]++; |
|
else if (n >= 21 && n <= 50) employeeBins["۲۱-۵۰ نفر"]++; |
|
else if (n > 50) employeeBins["۵۰+ نفر"]++; |
|
}); |
|
const sortedBins = Object.entries(employeeBins).filter(b => b[1] > 0); |
|
let distChartHtml = '<div class="bar-chart">'; |
|
if (sortedBins.length > 0) { |
|
const maxCount = Math.max(...sortedBins.map(b => b[1])); |
|
sortedBins.forEach(([label, count]) => { |
|
const barWidth = Math.max((count / maxCount) * 100, 5); |
|
distChartHtml += `<div class="bar-item"><span class="bar-label" title="${label}">${label}</span><div class="bar-wrapper"><div class="bar" style="width: ${barWidth}%; background: var(--gradient-warning); color: var(--text);">${count} کارگاه</div></div></div>`; |
|
}); |
|
} |
|
distChartHtml += '</div>'; |
|
employeeDistributionChart.innerHTML = distChartHtml; |
|
} |
|
|
|
btnRefreshAnalysis.addEventListener('click', () => { |
|
renderAnalysis(); |
|
showMessage('آمار و تحلیلها با موفقیت بروزرسانی شد.', 2000); |
|
}); |
|
|
|
btnShowMap.addEventListener('click', () => { |
|
const inspections = loadInspections(); |
|
const locations = inspections |
|
.filter(insp => insp.gps && insp.gps.includes(',')) |
|
.map(insp => insp.gps.trim().replace(/\s/g, '')); |
|
|
|
if (locations.length === 0) { |
|
alert('هیچ بازرسی با مختصات GPS معتبر برای نمایش روی نقشه یافت نشد.'); |
|
return; |
|
} |
|
|
|
const baseUrl = 'https://www.google.com/maps/dir/'; |
|
const url = baseUrl + locations.join('/'); |
|
window.open(url, '_blank'); |
|
}); |
|
|
|
|
|
function updateSearchInspectorOptions() { |
|
const inspectors = loadInspectors(); |
|
searchInspector.innerHTML = '<option value="">همه بازرسها</option>'; |
|
inspectors.forEach(insp => { |
|
const option = document.createElement('option'); |
|
option.value = insp.personnel; |
|
option.textContent = `${insp.name} ${insp.family} (${insp.personnel})`; |
|
searchInspector.appendChild(option); |
|
}); |
|
} |
|
|
|
function handleSearch() { |
|
const allInspections = loadInspections(); |
|
|
|
const inspectorPersonnel = searchInspector.value; |
|
const workshop = searchWorkshopName.value.trim().toLowerCase(); |
|
const activity = searchActivityField.value.trim().toLowerCase(); |
|
const employer = searchEmployerName.value.trim().toLowerCase(); |
|
const minEmp = parseInt(searchMinEmployees.value) || 0; |
|
const maxEmp = parseInt(searchMaxEmployees.value) || Infinity; |
|
|
|
filteredInspections = allInspections.filter(insp => { |
|
const inspectorMatch = !inspectorPersonnel || insp.personnel === inspectorPersonnel; |
|
const workshopMatch = !workshop || insp.workshopName.toLowerCase().includes(workshop); |
|
const activityMatch = !activity || insp.activityField.toLowerCase().includes(activity); |
|
const employerMatch = !employer || insp.employerName.toLowerCase().includes(employer); |
|
const employeeMatch = insp.numEmployees >= minEmp && insp.numEmployees <= maxEmp; |
|
return inspectorMatch && workshopMatch && activityMatch && employerMatch && employeeMatch; |
|
}); |
|
|
|
renderInspections(filteredInspections, searchResultsTableBody); |
|
searchResultSummary.textContent = `تعداد نتایج یافت شده: ${filteredInspections.length} مورد`; |
|
btnExportFiltered.disabled = filteredInspections.length === 0; |
|
} |
|
|
|
btnRunSearch.addEventListener('click', handleSearch); |
|
btnClearSearch.addEventListener('click', () => { |
|
searchForm.reset(); |
|
filteredInspections = []; |
|
searchResultsTableBody.innerHTML = ''; |
|
searchResultSummary.textContent = 'برای مشاهده نتایج، فیلترها را تنظیم کرده و روی دکمه "اعمال فیلتر" کلیک کنید.'; |
|
btnExportFiltered.disabled = true; |
|
}); |
|
|
|
btnExportFiltered.addEventListener('click', () => { |
|
if (filteredInspections.length > 0) { |
|
|
|
exportToExcel(filteredInspections); |
|
showMessage('خروجی Excel از نتایج فیلتر شده با موفقیت ایجاد شد.', 3000, searchMessageEl); |
|
} |
|
}); |
|
|
|
|
|
btnExportAllData.addEventListener('click', () => { |
|
btnSaveData.click(); |
|
}); |
|
|
|
btnImportData.addEventListener('click', () => { |
|
fileImporter.click(); |
|
}); |
|
|
|
fileImporter.addEventListener('change', (event) => { |
|
const file = event.target.files[0]; |
|
if (!file) return; |
|
|
|
const reader = new FileReader(); |
|
reader.onload = (e) => { |
|
try { |
|
const data = JSON.parse(e.target.result); |
|
if (data && data.inspections && data.inspectors && data.managers) { |
|
if (confirm('آیا مطمئن هستید که میخواهید دادههای فعلی را با اطلاعات فایل پشتیبان جایگزین کنید؟ این عمل غیرقابل بازگشت است.')) { |
|
saveInspections(data.inspections); |
|
saveInspectors(data.inspectors); |
|
saveManagers(data.managers); |
|
alert('دادهها با موفقیت بازیابی شدند.'); |
|
location.reload(); |
|
} |
|
} else { |
|
alert('فایل پشتیبان معتبر نیست. ساختار فایل صحیح نمیباشد.'); |
|
} |
|
} catch (error) { |
|
alert('خطا در خواندن فایل. لطفاً از یک فایل JSON معتبر استفاده کنید.'); |
|
console.error("خطای Import:", error); |
|
} finally { |
|
fileImporter.value = ''; |
|
} |
|
}; |
|
reader.readAsText(file); |
|
}); |
|
|
|
btnClearAllData.addEventListener('click', () => { |
|
if (confirm('هشدار: آیا واقعاً قصد دارید تمام دادههای سامانه را حذف کنید؟')) { |
|
if (confirm('این عمل غیرقابل بازگشت است. برای تایید نهایی، دوباره روی OK کلیک کنید.')) { |
|
localStorage.clear(); |
|
alert('تمام دادهها با موفقیت حذف شدند.'); |
|
location.reload(); |
|
} |
|
} |
|
}); |
|
|
|
|
|
|
|
renderInspectors(); |
|
renderManagers(); |
|
renderInspections(); |
|
renderAnalysis(); |
|
|
|
if (navigator.geolocation) { |
|
btnGetGPS.disabled = false; |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |