anycoder-561ad228 / index.html
Esmaeilkianii's picture
Upload folder using huggingface_hub
80fa4eb verified
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>نرم‌افزار مدیریت آزمایشگاه آب و خاک - آزمایشگاه اهواز</title>
<link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jalaali-js@1.1.3/dist/jalaali.min.js"></script>
<style>
:root{
--bg: #f6f7fb;
--card: #ffffff;
--primary: #0061ff;
--primary-600:#0053d6;
--primary-50:#e6f0ff;
--success:#10b981;
--success-600:#059669;
--warning:#f59e0b;
--danger:#ef4444;
--text:#1f2937;
--muted:#6b7280;
--border:#e5e7eb;
--shadow: 0 10px 25px rgba(0,0,0,0.07);
--radius: 16px;
--soil: #8B4513;
--soil-50: #F4E4D4;
--water: #0EA5E9;
--water-50: #E0F2FE;
}
*{ box-sizing:border-box }
html,body{ height:100% }
body{
margin:0;
font-family: "Vazirmatn", "IRANSans", Tahoma, Arial, sans-serif;
background: var(--bg);
color: var(--text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a{ color: var(--primary); text-decoration: none }
header{
position: sticky; top:0; z-index: 50;
backdrop-filter: saturate(180%) blur(12px);
background: rgba(255,255,255,0.85);
border-bottom: 1px solid var(--border);
}
.topbar{
max-width: 1400px; margin: 0 auto; padding: 12px 20px;
display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.brand{
display: flex; align-items: center; gap: 12px;
}
.brand .logo{
width: 40px; height: 40px; border-radius: 12px;
background: linear-gradient(135deg, var(--primary), var(--soil));
display: grid; place-items: center; color: white; font-weight: 800;
box-shadow: var(--shadow);
}
.brand .title{
display: flex; flex-direction: column; line-height: 1.2;
}
.brand .title b{ font-size: 18px }
.brand .title small{ color: var(--muted) }
.actions{ display: flex; gap: 10px; align-items: center }
.badge{
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 10px; border-radius: 999px; font-size: 13px;
border: 1px solid var(--border); background: var(--card);
}
.badge.success{ color: var(--success-600); background: #ecfdf5; border-color:#d1fae5 }
.badge.warn{ color: #b45309; background: #fffbeb; border-color:#fde68a }
.badge.danger{ color: var(--danger); background: #fef2f2; border-color:#fee2e2 }
.badge.iso{ color: var(--primary-600); background: var(--primary-50); border-color:#cfe0ff }
.user{
display:flex; align-items:center; gap:10px; padding:6px 10px; border-radius: 999px;
background: var(--card); border: 1px solid var(--border);
}
.user i{ font-size: 18px }
nav{
border-top: 1px solid var(--border);
background: white;
}
.tabs{
max-width: 1400px; margin: 0 auto;
display:flex; gap: 8px; padding: 10px 20px; overflow:auto;
}
.tab{
display:inline-flex; align-items:center; gap:8px;
padding: 10px 14px; border-radius: 12px;
color: var(--muted); cursor: pointer; border: 1px solid transparent;
}
.tab.active{
color: var(--primary); background: var(--primary-50); border-color:#cfe0ff;
}
main{ max-width: 1400px; margin: 18px auto; padding: 0 20px 40px }
.grid{
display: grid; gap: 18px;
}
@media (min-width: 900px){
.grid.cols-4{ grid-template-columns: repeat(4, 1fr) }
.grid.cols-3{ grid-template-columns: repeat(3, 1fr) }
.grid.cols-2{ grid-template-columns: 2fr 1fr }
}
.card{
background: var(--card); border: 1px solid var(--border);
border-radius: var(--radius); box-shadow: var(--shadow);
padding: 16px;
}
.card h3{ margin: 0 0 10px; font-size: 18px }
.kpi{
display:flex; align-items:center; justify-content: space-between;
gap: 14px; padding: 16px; border-radius: 14px;
background: linear-gradient(180deg, #ffffff, #f8fafc);
border: 1px solid var(--border);
}
.kpi .val{ font-size: 32px; font-weight: 800 }
.kpi .label{ color: var(--muted); font-size: 14px }
.kpi .icon{
width: 46px; height: 46px; border-radius: 12px; display:grid; place-items:center;
color: white; font-size: 22px; font-weight: 700; background: var(--primary);
box-shadow: var(--shadow);
}
.kpi.soil .icon{ background: var(--soil) }
.kpi.water .icon{ background: var(--water) }
.section-title{
display:flex; align-items:center; justify-content: space-between; margin-bottom: 10px;
}
.btn{
display:inline-flex; align-items:center; gap:8px;
padding: 10px 14px; border-radius: 12px; border: 1px solid var(--border);
background: white; color: var(--text); cursor:pointer;
}
.btn.primary{
background: var(--primary); color: white; border-color: transparent;
}
.btn.soil{ background: var(--soil); color: white; border-color: transparent }
.btn.water{ background: var(--water); color: white; border-color: transparent }
.btn.primary:hover{ background: var(--primary-600) }
.btn.ghost:hover{ background: #f3f4f6 }
.btn.success{ background: var(--success); color: white; border-color: transparent }
.btn.success:hover{ background: #059669 }
.btn:disabled{ opacity: .6; cursor:not-allowed }
.table{
width:100%; border-collapse: collapse; font-size: 14px;
}
.table th, .table td{
padding: 12px; border-bottom: 1px solid var(--border); text-align: right;
}
.status{
padding: 6px 10px; border-radius: 999px; font-size: 12px; display:inline-block;
}
.status.received{ background: #eef2ff; color:#3730a3 }
.status.intest{ background: #ecfeff; color:#0e7490 }
.status.done{ background: #ecfdf5; color:#065f46 }
.status.approved{ background: #fef3c7; color:#92400e }
.muted{ color: var(--muted) }
.sample-type{ display: inline-flex; align-items: center; gap: 6px }
.sample-type.soil{ color: var(--soil) }
.sample-type.water{ color: var(--water) }
/* Sample Registration */
.sample-form{
display: grid; gap: 16px;
}
.form-group{
display: grid; gap: 6px;
}
.form-group label{
font-size: 14px; font-weight: 600; color: var(--text);
}
.form-group input, .form-group select, .form-group textarea{
padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px;
font-size: 14px; font-family: inherit;
}
.form-group textarea{ resize: vertical; min-height: 80px }
.form-row{
display: grid; gap: 16px;
}
@media (min-width: 768px){
.form-row{ grid-template-columns: repeat(2, 1fr) }
}
/* Instruments */
.instrument-card{
display: grid; gap: 12px;
padding: 16px; border: 1px solid var(--border); border-radius: 12px;
background: white;
}
.instrument-header{
display: flex; align-items: center; justify-content: space-between;
}
.instrument-status{
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 8px; border-radius: 8px; font-size: 12px;
}
.instrument-status.active{ background: #ecfdf5; color:#065f46 }
.instrument-status.maintenance{ background: #fef3c7; color:#92400e }
/* Audit Trail */
.audit-item{
display: flex; gap: 12px; padding: 12px; border-radius: 8px;
background: #f8fafc; border-left: 3px solid var(--primary);
}
.audit-time{ color: var(--muted); font-size: 12px }
/* Charts */
.chart-container{
position: relative; height: 300px;
}
/* ISO Badge */
.iso-compliance{
display: inline-flex; align-items: center; gap: 8px;
padding: 8px 12px; border-radius: 12px;
background: linear-gradient(135deg, var(--primary-50), white);
border: 1px solid var(--primary-200);
}
/* Test Results */
.test-results{
display: grid; gap: 12px;
}
.test-item{
padding: 16px; border: 1px solid var(--border); border-radius: 12px;
background: white;
}
.parameter-grid{
display: grid; gap: 8px; margin-top: 12px;
}
.parameter-row{
display: flex; justify-content: space-between; align-items: center;
padding: 8px 12px; background: #f8fafc; border-radius: 8px;
}
.parameter-value{ font-weight: 700 }
/* Responsive */
@media (max-width: 768px){
.grid.cols-4{ grid-template-columns: repeat(2, 1fr) }
.tabs{ padding: 10px 12px }
main{ padding: 0 12px 40px }
}
</style>
</head>
<body>
<header>
<div class="topbar">
<div class="brand">
<div class="logo">LIMS</div>
<div class="title">
<b>نرم‌افزار مدیریت آزمایشگاه آب و خاک - اهواز</b>
<small>سیستم مدیریت اطلاعات آزمایشگاهی • انطباق ISO 17025</small>
</div>
</div>
<div class="actions">
<span class="badge iso"><i class="ri-award-line"></i> ISO 17025</span>
<span class="badge success" id="systemStatus"><i class="ri-shield-check-line"></i> سیستم فعال</span>
<span class="badge" id="todayBadge"><i class="ri-time-line"></i></span>
<div class="user">
<i class="ri-admin-line"></i>
<span id="currentUser">مدیر سیستم</span>
</div>
</div>
</div>
<nav>
<div class="tabs">
<a class="tab active" data-view="dashboard"><i class="ri-dashboard-line"></i> داشبورد</a>
<a class="tab" data-view="samples"><i class="ri-flask-line"></i> مدیریت نمونه‌ها</a>
<a class="tab" data-view="instruments"><i class="ri-tools-line"></i> تجهیزات</a>
<a class="tab" data-view="tests"><i class="ri-test-tube-line"></i> آزمون‌ها</a>
<a class="tab" data-view="reports"><i class="ri-file-chart-line"></i> گزارش‌ها</a>
<a class="tab" data-view="audit"><i class="ri-history-line"></i> بازرسی</a>
</div>
</nav>
</header>
<main>
<!-- DASHBOARD -->
<section id="view-dashboard">
<div class="grid cols-4">
<div class="kpi card">
<div>
<div class="label">کل نمونه‌ها</div>
<div class="val" id="totalSamples">0</div>
</div>
<div class="icon"><i class="ri-flask-line"></i></div>
</div>
<div class="kpi card soil">
<div>
<div class="label">نمونه‌های خاک</div>
<div class="val" id="soilSamples">0</div>
</div>
<div class="icon"><i class="ri-landscape-line"></i></div>
</div>
<div class="kpi card water">
<div>
<div class="label">نمونه‌های آب</div>
<div class="val" id="waterSamples">0</div>
</div>
<div class="icon"><i class="ri-drop-line"></i></div>
</div>
<div class="kpi card">
<div>
<div class="label">آزمون‌های تکمیل</div>
<div class="val" id="completedTests">0</div>
</div>
<div class="icon" style="background: var(--success)"><i class="ri-check-double-line"></i></div>
</div>
</div>
<div class="grid cols-2" style="margin-top: 18px">
<div class="card">
<h3><i class="ri-bar-chart-2-line"></i> روند پارامترها</h3>
<div class="chart-container">
<canvas id="trendChart"></canvas>
</div>
</div>
<div class="card">
<h3><i class="ri-notification-3-line"></i> فعالیت‌های اخیر</h3>
<div id="recentActivities" style="max-height: 300px; overflow-y: auto"></div>
</div>
</div>
<div class="card" style="margin-top: 18px">
<div class="section-title">
<h3><i class="ri-list-unordered"></i> نمونه‌های اخیر</h3>
<button class="btn ghost" onclick="gotoView('samples')"><i class="ri-arrow-right-up-line"></i> مشاهده همه</button>
</div>
<div style="overflow-x: auto">
<table class="table" id="recentSamplesTable">
<thead>
<tr>
<th>شناسه نمونه</th>
<th>نوع نمونه</th>
<th>مشتری</th>
<th>محصول</th>
<th>وضعیت</th>
<th>تاریخ ثبت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</section>
<!-- SAMPLES -->
<section id="view-samples" style="display:none">
<div class="card">
<div class="section-title">
<h3><i class="ri-flask-line"></i> مدیریت نمونه‌ها</h3>
<button class="btn primary" onclick="showSampleForm()"><i class="ri-add-line"></i> ثبت نمونه جدید</button>
</div>
<div id="sampleForm" style="display:none; margin-top: 20px">
<div class="sample-form">
<h4><i class="ri-file-add-line"></i> فرم ثبت نمونه</h4>
<div class="form-row">
<div class="form-group">
<label>شناسه نمونه</label>
<input type="text" id="sampleId" readonly style="background: #f8fafc">
</div>
<div class="form-group">
<label>نوع نمونه</label>
<select id="sampleType">
<option value="soil">خاک</option>
<option value="water">آب</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>نام مشتری</label>
<input type="text" id="clientName" placeholder="نام مشتری یا سازمان">
</div>
<div class="form-group">
<label>محصول/منبع</label>
<input type="text" id="product" placeholder="نوع محصول یا منبع آب">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>مکان نمونه‌برداری</label>
<input type="text" id="location">
</div>
<div class="form-group">
<label>تاریخ نمونه‌برداری</label>
<input type="date" id="collectionDate">
</div>
</div>
<div class="form-group">
<label>آزمون‌های درخواستی</label>
<div style="display: flex; gap: 12px; flex-wrap: wrap">
<label><input type="checkbox" value="pH"> pH</label>
<label><input type="checkbox" value="EC"> EC</label>
<label><input type="checkbox" value="N"> نیتروژن (N)</label>
<label><input type="checkbox" value="P"> فسفر (P)</label>
<label><input type="checkbox" value="K"> پتاسیم (K)</label>
<label><input type="checkbox" value="organic"> مواد آلی</label>
<label><input type="checkbox" value="texture"> بافت خاک</label>
</div>
</div>
<div class="form-group">
<label>یادداشت‌ها</label>
<textarea id="notes" placeholder="توضیحات اضافی..."></textarea>
</div>
<div style="display: flex; gap: 12px">
<button class="btn primary" onclick="registerSample()"><i class="ri-save-line"></i> ثبت نمونه</button>
<button class="btn ghost" onclick="hideSampleForm()"><i class="ri-close-line"></i> انصراف</button>
</div>
</div>
</div>
<div style="overflow-x: auto; margin-top: 20px">
<table class="table" id="samplesTable">
<thead>
<tr>
<th>شناسه نمونه</th>
<th>نوع نمونه</th>
<th>مشتری</th>
<th>محصول/منبع</th>
<th>مکان</th>
<th>آزمون‌ها</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</section>
<!-- INSTRUMENTS -->
<section id="view-instruments" style="display:none">
<div class="card">
<div class="section-title">
<h3><i class="ri-tools-line"></i> مدیریت تجهیزات</h3>
<button class="btn primary" onclick="addInstrument()"><i class="ri-add-line"></i> افزودن تجهیزات</button>
</div>
<div class="grid cols-3" id="instrumentsGrid"></div>
</div>
</section>
<!-- TESTS -->
<section id="view-tests" style="display:none">
<div class="card">
<div class="section-title">
<h3><i class="ri-test-tube-line"></i> اجرای آزمون‌ها</h3>
</div>
<div id="testExecution"></div>
</div>
</section>
<!-- REPORTS -->
<section id="view-reports" style="display:none">
<div class="card">
<div class="section-title">
<h3><i class="ri-file-chart-line"></i> گزارش‌های آزمایشگاهی</h3>
<div>
<button class="btn primary" onclick="generateReport()"><i class="ri-download-2-line"></i> تولید گزارش PDF</button>
<button class="btn ghost" onclick="window.print()"><i class="ri-printer-line"></i> چاپ</button>
</div>
</div>
<div id="reportContent"></div>
</div>
</section>
<!-- AUDIT TRAIL -->
<section id="view-audit" style="display:none">
<div class="card">
<div class="section-title">
<h3><i class="ri-history-line"></i> سوابق و بازرسی (Audit Trail)</h3>
<div class="iso-compliance">
<i class="ri-shield-check-line"></i>
<span>انطباق کامل با ISO 17025</span>
</div>
</div>
<div id="auditTrail"></div>
</div>
</section>
</main>
<footer>
© ۱۴۰۴ – آزمایشگاه آب و خاک اهواز • سیستم مدیریت اطلاعات آزمایشگاهی (LIMS) • Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
</footer>
<script>
// ---------- Localization helpers ----------
const fa = (n) => new Intl.NumberFormat('fa-IR').format(n);
function toPersianDigits(str){ return String(str).replace(/\d/g, d => '۰۱۲۳۴۵۶۷۸۹'[d]) }
function nowJalaliString(){
const d = new Date();
const j = jalaali(d.getFullYear(), d.getMonth()+1, d.getDate());
return `${j.jy}/${j.jm}/${j.jd} ${d.getHours().toString().padStart(2,'0')}:${d.getMinutes().toString().padStart(2,'0')}`;
}
function timeAgo(ts){
const d = Math.floor((Date.now() - ts)/1000);
if (d < 60) return `${d} ثانیه پیش`;
const m = Math.floor(d/60); if (m < 60) return `${m} دقیقه پیش`;
const h = Math.floor(m/60); if (h < 24) return `${h} ساعت پیش`;
const day = Math.floor(h/24); return `${day} روز پیش`;
}
// ---------- App State ----------
const State = {
currentUser: { userId: 'U001', fullName: 'مدیر سیستم', role: 'Admin' },
samples: [],
instruments: [],
tests: [],
reports: [],
auditTrail: [],
charts: {}
};
// ---------- Initialize Data ----------
function initializeData(){
// Add sample data from JSON
State.samples.push({
sampleId: 'SAMPLE_DKH_2025_001',
type: 'soil',
clientName: 'کشت و صنعت دهخدا',
product: 'نیشکر',
collectionDate: '2025-09-28',
location: 'مزرعه شماره 12 - دهخدا',
gps: { latitude: 31.318, longitude: 48.670 },
status: 'Received',
requestedTests: ['pH', 'EC', 'N', 'P', 'K'],
tests: [
{
testId: 'T001',
testName: 'Ph و Conductivity',
methodCode: 'STD_SOIL_001',
results: [
{ parameter: 'pH', value: 7.5, unit: '' },
{ parameter: 'EC', value: 1200, unit: 'µS/cm' }
],
instrument: 'EC/pH Meter',
performedBy: 'Technician1',
performedDate: '2025-09-29'
},
{
testId: 'T002',
testName: 'NPK عناصر غذایی',
methodCode: 'STD_SOIL_002',
results: [
{ parameter: 'N', value: 0.18, unit: '%' },
{ parameter: 'P', value: 22, unit: 'mg/kg' },
{ parameter: 'K', value: 180, unit: 'mg/kg' }
],
instrument: 'Spectrophotometer',
performedBy: 'Technician2',
performedDate: '2025-09-29'
}
],
createdAt: Date.now() - (1000*60*60*24*2)
});
// Add instruments
State.instruments = [
{
instrumentId: 'I001',
name: 'EC/pH Meter',
type: 'Conductivity & pH',
status: 'Active',
calibration: '2025-08-01',
nextCalibration: '2025-11-01'
},
{
instrumentId: 'I002',
name: 'Spectrophotometer',
type: 'Spectrophotometer',
status: 'Active',
calibration: '2025-07-15',
nextCalibration: '2025-10-15'
}
];
// Add audit trail entries
State.auditTrail = [
{
action: 'registered',
by: 'Admin',
timestamp: '2025-09-28T10:00:00Z',
details: 'ثبت نمونه SAMPLE_DKH_2025_001'
},
{
action: 'test_completed',
by: 'Technician1',
timestamp: '2025-09-29T14:30:00Z',
details: 'تکمیل آزمون pH و EC برای نمونه SAMPLE_DKH_2025_001'
}
];
updateDashboard();
}
// ---------- Navigation ----------
const views = ['dashboard','samples','instruments','tests','reports','audit'];
function gotoView(view){
views.forEach(v=>{
document.getElementById('view-'+v).style.display = (v===view)?'block':'none';
document.querySelectorAll('.tab').forEach(t=>{
if (t.dataset.view===view) t.classList.add('active'); else t.classList.remove('active');
});
});
if (view==='dashboard') updateDashboard();
if (view==='samples') renderSamples();
if (view==='instruments') renderInstruments();
if (view==='audit') renderAuditTrail();
}
document.querySelectorAll('.tab').forEach(t=>{
t.addEventListener('click', ()=> gotoView(t.dataset.view));
});
// ---------- Dashboard ----------
function updateDashboard(){
const totalSamples = State.samples.length;
const soilSamples = State.samples.filter(s => s.type === 'soil').length;
const waterSamples = State.samples.filter(s => s.type === 'water').length;
const completedTests = State.samples.reduce((acc, s) => acc + (s.tests?.length || 0), 0);
document.getElementById('totalSamples').innerText = toPersianDigits(totalSamples);
document.getElementById('soilSamples').innerText = toPersianDigits(soilSamples);
document.getElementById('waterSamples').innerText = toPersianDigits(waterSamples);
document.getElementById('completedTests').innerText = toPersianDigits(completedTests);
// Update recent samples table
const tbody = document.querySelector('#recentSamplesTable tbody');
tbody.innerHTML = '';
const recentSamples = [...State.samples].sort((a,b) => b.createdAt - a.createdAt).slice(0,5);
recentSamples.forEach(s => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${s.sampleId}</td>
<td><span class="sample-type ${s.type}"><i class="ri-${s.type === 'soil' ? 'landscape' : 'drop'}-line"></i> ${s.type === 'soil' ? 'خاک' : 'آب'}</span></td>
<td>${s.clientName}</td>
<td>${s.product || '-'}</td>
<td>${getStatusBadge(s.status)}</td>
<td>${s.collectionDate}</td>
<td><button class="btn ghost" onclick="viewSampleDetails('${s.sampleId}')"><i class="ri-eye-line"></i></button></td>
`;
tbody.appendChild(tr);
});
// Update recent activities
const activitiesDiv = document.getElementById('recentActivities');
activitiesDiv.innerHTML = '';
State.auditTrail.slice(0,5).forEach(a => {
const div = document.createElement('div');
div.className = 'audit-item';
div.innerHTML = `
<div>
<div>${a.details}</div>
<div class="audit-time">توسط ${a.by}${new Date(a.timestamp).toLocaleString('fa-IR')}</div>
</div>
`;
activitiesDiv.appendChild(div);
});
// Initialize trend chart
initializeTrendChart();
document.getElementById('todayBadge').innerHTML = `<i class="ri-time-line"></i> ${nowJalaliString()}`;
}
function getStatusBadge(status){
const badges = {
'Received': '<span class="status received">دریافت‌شده</span>',
'In Progress': '<span class="status intest">در حال انجام</span>',
'Completed': '<span class="status done">تکمیل‌شده</span>',
'Approved': '<span class="status approved">تایید شده</span>'
};
return badges[status] || status;
}
// ---------- Trend Chart ----------
function initializeTrendChart(){
const ctx = document.getElementById('trendChart');
if (!ctx) return;
if (State.charts.trend) State.charts.trend.destroy();
State.charts.trend = new Chart(ctx, {
type: 'line',
data: {
labels: ['شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن'],
datasets: [
{
label: 'pH',
data: [7.2, 7.4, 7.1, 7.5, 7.3, 7.5],
borderColor: '#0061ff',
tension: 0.4
},
{
label: 'EC (µS/cm)',
data: [1100, 1150, 1200, 1180, 1220, 1200],
borderColor: '#10b981',
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
}
}
}
});
}
// ---------- Samples Management ----------
function showSampleForm(){
document.getElementById('sampleForm').style.display = 'block';
// Generate sample ID
const year = new Date().getFullYear();
const random = Math.floor(Math.random()*1000).toString().padStart(3,'0');
document.getElementById('sampleId').value = `SAMPLE_AHV_${year}_${random}`;
// Set today's date
document.getElementById('collectionDate').value = new Date().toISOString().split('T')[0];
}
function hideSampleForm(){
document.getElementById('sampleForm').style.display = 'none';
}
function registerSample(){
const sample = {
sampleId: document.getElementById('sampleId').value,
type: document.getElementById('sampleType').value,
clientName: document.getElementById('clientName').value,
product: document.getElementById('product').value,
location: document.getElementById('location').value,
collectionDate: document.getElementById('collectionDate').value,
status: 'Received',
requestedTests: Array.from(document.querySelectorAll('#sampleForm input[type="checkbox"]:checked')).map(cb => cb.value),
notes: document.getElementById('notes').value,
createdAt: Date.now(),
tests: []
};
State.samples.unshift(sample);
// Add to audit trail
addAuditEntry('registered', State.currentUser.fullName, `ثبت نمونه ${sample.sampleId}`);
hideSampleForm();
renderSamples();
updateDashboard();
}
function renderSamples(){
const tbody = document.querySelector('#samplesTable tbody');
tbody.innerHTML = '';
State.samples.forEach(s => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${s.sampleId}</td>
<td><span class="sample-type ${s.type}"><i class="ri-${s.type === 'soil' ? 'landscape' : 'drop'}-line"></i> ${s.type === 'soil' ? 'خاک' : 'آب'}</span></td>
<td>${s.clientName}</td>
<td>${s.product || '-'}</td>
<td>${s.location}</td>
<td