<!DOCTYPE html>
<!-- تنظیمات اختصاصی PWA و هوماسکرین -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="هزینه خودرو">
<meta name="theme-color" content="#2563eb">
<link rel="apple-touch-icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚗</text></svg>">
<!-- Google Font Vazirmatn -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css">
<style>
:root {
--bg-color: #f1f5f9;
--card-bg: #ffffff;
--text-main: #0f172a;
--text-muted: #64748b;
--border: #e2e8f0;
--primary: #2563eb;
--primary-hover: #1d4ed8;
--primary-light: #eff6ff;
--danger: #ef4444;
--danger-hover: #dc2626;
--success: #10b981;
--shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.05);
}
[data-theme="dark"] {
--bg-color: #0f172a;
--card-bg: #1e293b;
--text-main: #f8fafc;
--text-muted: #94a3b8;
--border: #334155;
--primary: #3b82f6;
--primary-hover: #60a5fa;
--primary-light: rgba(59, 130, 246, 0.15);
--danger: #f87171;
--danger-hover: #ef4444;
--success: #34d399;
--shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.3);
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif; -webkit-tap-highlight-color: transparent; }
body { background-color: var(--bg-color); color: var(--text-main); line-height: 1.6; padding: 12px; transition: background-color 0.3s, color 0.3s; }
.container { max-width: 850px; margin: 0 auto; }
header {
background: linear-gradient(135deg, var(--primary), #1e40af);
color: white;
padding: 18px;
border-radius: 20px;
margin-bottom: 16px;
box-shadow: 0 8px 25px -5px rgba(37,99,235,0.3);
display: flex;
justify-content: space-between;
align-items: center;
}
header h1 { font-size: 1.2rem; display: flex; align-items: center; gap: 8px; font-weight: 800; }
header p { font-size: 0.8rem; opacity: 0.9; margin-top: 4px; }
.theme-toggle-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
padding: 8px 12px;
border-radius: 12px;
cursor: pointer;
font-size: 1.1rem;
backdrop-filter: blur(5px);
transition: background 0.2s;
}
.card {
background: var(--card-bg);
border-radius: 20px;
padding: 18px;
margin-bottom: 16px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
transition: background-color 0.3s, border-color 0.3s;
}
h2 {
font-size: 1.05rem;
margin-bottom: 16px;
color: var(--primary);
display: flex;
align-items: center;
gap: 8px;
border-bottom: 2px solid var(--border);
padding-bottom: 10px;
font-weight: 700;
}
.car-banner {
display: flex;
align-items: center;
justify-content: space-between;
background: var(--primary-light);
border: 1px dashed var(--primary);
padding: 12px 16px;
border-radius: 14px;
margin-bottom: 16px;
}
.car-banner-info { font-size: 0.88rem; font-weight: 600; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } }
.form-group { margin-bottom: 12px; }
.form-group.full { grid-column: span 2; }
@media (max-width: 640px) { .form-group.full { grid-column: span 1; } }
label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 6px; color: var(--text-muted); }
input, select, textarea {
width: 100%;
padding: 11px 14px;
border: 1px solid var(--border);
border-radius: 12px;
font-size: 0.92rem;
background: var(--card-bg);
color: var(--text-main);
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px var(--primary-light);
}
.btn {
background: var(--primary);
color: white;
border: none;
padding: 12px 20px;
border-radius: 12px;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
width: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn:active { transform: scale(0.98); }
.btn-danger { background: var(--danger); }
.btn-secondary { background: #64748b; }
.btn-success { background: var(--success); }
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 20px; }
.stat-card { background: var(--primary-light); padding: 14px; border-radius: 14px; text-align: center; border: 1px solid var(--border); }
.stat-card .val { font-size: 1.15rem; font-weight: 800; color: var(--primary); margin-top: 4px; }
.stat-card .lbl { font-size: 0.78rem; color: var(--text-muted); font-weight: 600; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 0.85rem; }
th, td { padding: 10px 8px; text-align: right; border-bottom: 1px solid var(--border); }
th { background: var(--primary-light); font-weight: 700; color: var(--text-muted); }
.nav-tabs { display: flex; gap: 6px; margin-bottom: 16px; background: var(--card-bg); padding: 6px; border-radius: 14px; border: 1px solid var(--border); overflow-x: auto; }
.nav-tab { flex: 1; min-width: max-content; padding: 10px 12px; text-align: center; border: none; background: transparent; font-weight: 700; font-size: 0.82rem; border-radius: 10px; cursor: pointer; color: var(--text-muted); }
.nav-tab.active { background: var(--primary); color: white; box-shadow: 0 4px 12px rgba(37,99,235,0.2); }
.tab-content { display: none; }
.tab-content.active { display: block; }
.badge { display: inline-block; padding: 3px 8px; font-size: 0.72rem; border-radius: 8px; font-weight: 700; background: var(--primary-light); color: var(--primary); }
.table-responsive { overflow-x: auto; }
.car-card-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px;
background: var(--bg-color);
border-radius: 12px;
margin-bottom: 10px;
border: 1px solid var(--border);
}
.car-card-item.active-car {
border: 2px solid var(--primary);
background: var(--primary-light);
}
</style>
<h1>🚗 هزینهها و تعمیرات خودرو</h1>
<p>مدیریت هوشمند، سوابق و گزارش مالی</p>
🌙
در حال بارگذاری...
⚙️ تغییر
➕ ثبت
📋 سوابق
📊 گزارش
🚘 خودروها
📤 خروجی
<h2 id="formTitle">➕ ثبت هزینه جدید</h2>
نوع هزینه
خرید
سرویس دورهای
نصب تجهیزات
خدمات خودرو
بیمه
سایر
موضوع / دسته
قطعه و لوازم یدکی
تعویض روغن و فیلتر
تعمیر موتور
تعمیر گیربکس و کلاج
جلوبندی و تعلیق
ترمز و ایمنی
برق و باتری
لاستیک و رینگ
نصب تجهیزات و آپشن
سیستم صوتی و تصویری
بدنه و رنگ
سوخت و سیالات
بیمه
معاینه فنی
کارواش و سایر
تاریخ (شمسی)
کیلومتر
هزینه قطعات (تومان)
هزینه دستمزد (تومان)
مجموع کل: <span id="totalCostDisplay">۰</span> تومان
نام تعمیرکار / فروشنده
شماره تلفن
آدرس
تاریخ سرویس بعدی
کیلومتر سرویس بعدی
گارانتی
توضیحات
💾 ذخیره هزینه
<h2>📋 سوابق هزینهها</h2>
<table>
<thead>
<tr>
<th>تاریخ</th>
<th>دسته</th>
<th>کیلومتر</th>
<th>مبلغ (تومان)</th>
<th>تعمیرکار</th>
<th>عملیات</th>
</tr>
</thead>
<tbody id="historyTableBody"></tbody>
</table>
<h2>📊 گزارش مالی</h2>
مجموع کل
۰ ت
مجموع دستمزد
۰ ت
میانگین هر ثبت
۰ ت
تعداد ثبتها
۰
<h3>تفکیک هزینهها</h3>
<h2>🚘 لیست خودروها</h2>
<h2>➕ افزودن خودرو</h2>
برند / سازنده
نوع / مدل
تیپ
سال ساخت
شماره پلاک
➕ ثبت خودرو
<h2>📤 خروجی و پشتیبان</h2>
📱 کپی فرمت نوت / تلگرام
📄 دانلود فایل متنی (.txt)
📊 دانلود فایل اکسل (.csv)
💾 پشتیبانگیری کامل (JSON)
function toPersianDigits(num) {
if (num === null || num === undefined) return '';
const pDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
return num.toString().replace(/\d/g, x => pDigits[x]);
}
let db = JSON.parse(localStorage.getItem('car_tracker_db_v5')) || {
activeCarId: 'car_1',
theme: 'light',
cars: [
{ id: 'car_1', brand: 'ایران خودرو', type: 'سمند', model: 'EF7', year: '1401', plate: 'ایران ۱۱ - ۱۲۳ ب ۴۵' }
],
expenses: []
};
function saveData() {
localStorage.setItem('car_tracker_db_v5', JSON.stringify(db));
}
function initApp() {
applyTheme();
updateCarBanner();
renderHistory();
renderReports();
renderCarsList();
}
function toggleTheme() {
db.theme = db.theme === 'light' ? 'dark' : 'light';
saveData();
applyTheme();
}
function applyTheme() {
document.documentElement.setAttribute('data-theme', db.theme || 'light');
document.getElementById('themeBtn').textContent = db.theme === 'dark' ? '☀️' : '🌙';
}
function switchTab(tabName, btnEl) {
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.nav-tab').forEach(el => el.classList.remove('active'));
document.getElementById('tab-' + tabName).classList.add('active');
if(btnEl) btnEl.classList.add('active');
}
function updateCarBanner() {
const car = db.cars.find(c => c.id === db.activeCarId);
const banner = document.getElementById('activeCarBanner');
if (car) {
banner.innerHTML = `خودرو: <b>${car.brand} ${car.type} (${car.model})</b> | پلاک: <b>${toPersianDigits(car.plate)}</b>`;
} else {
banner.innerHTML = '⚠️ خودرویی انتخاب نشده. ابتدا یک خودرو ثبت کنید.';
}
}
function renderCarsList() {
const container = document.getElementById('carsListContainer');
container.innerHTML = '';
if (db.cars.length === 0) {
container.innerHTML = '<p style="color:var(--text-muted); font-size:0.85rem; text-align:center; padding:10px;">هیچ خودرویی ثبت نشده است.</p>';
return;
}
db.cars.forEach(car => {
const isActive = car.id === db.activeCarId;
const div = document.createElement('div');
div.className = `car-card-item ${isActive ? 'active-car' : ''}`;
div.innerHTML = `
<div>
<b>${car.brand} ${car.type}</b> (${car.model}) - سال ${toPersianDigits(car.year)}
<br><small style="color:var(--text-muted);">پلاک: ${toPersianDigits(car.plate)}</small>
</div>
<div style="display:flex; gap:6px;">
${!isActive ? `<button class="btn btn-secondary" style="padding:6px 10px; font-size:0.75rem;" onclick="setActiveCar('${car.id}')">انتخاب</button>` : '<span class="badge">فعال</span>'}
<button class="btn btn-danger" style="padding:6px 10px; font-size:0.75rem;" onclick="deleteCar('${car.id}')">🗑️</button>
</div>
`;
container.appendChild(div);
});
}
function setActiveCar(id) {
db.activeCarId = id;
saveData();
initApp();
}
function saveNewCar(e) {
e.preventDefault();
const brand = document.getElementById('newBrand').value.trim();
const type = document.getElementById('newType').value.trim();
const model = document.getElementById('newModel').value.trim();
const year = document.getElementById('newYear').value.trim();
const plate = document.getElementById('newPlate').value.trim();
const newId = 'car_' + Date.now();
db.cars.push({ id: newId, brand, type, model, year, plate });
db.activeCarId = newId;
saveData();
document.getElementById('newBrand').value = '';
document.getElementById('newType').value = '';
document.getElementById('newModel').value = '';
document.getElementById('newYear').value = '';
document.getElementById('newPlate').value = '';
initApp();
alert('خودروی جدید افزوده شد.');
}
function deleteCar(id) {
if (confirm('آیا از حذف این خودرو و تمام سوابق آن اطمینان دارید؟')) {
db.expenses = db.expenses.filter(e => e.carId !== id);
db.cars = db.cars.filter(c => c.id !== id);
db.activeCarId = db.cars.length > 0 ? db.cars[0].id : null;
saveData();
initApp();
}
}
function calculateTotal() {
const part = parseFloat(document.getElementById('partCost').value) || 0;
const labor = parseFloat(document.getElementById('laborCost').value) || 0;
const total = part + labor;
document.getElementById('totalCostDisplay').textContent = toPersianDigits(total.toLocaleString());
}
function saveExpense(e) {
e.preventDefault();
if (!db.activeCarId) {
alert('لطفاً ابتدا از بخش "خودروها" یک خودرو انتخاب کنید یا بسازید.');
switchTab('cars', document.querySelectorAll('.nav-tab')[3]);
return;
}
const editIndex = document.getElementById('editIndex').value;
const expenseData = {
id: editIndex !== '' ? db.expenses[editIndex].id : 'exp_' + Date.now(),
carId: db.activeCarId,
type: document.getElementById('expenseType').value,
category: document.getElementById('expenseCategory').value,
date: document.getElementById('expenseDate').value,
km: document.getElementById('expenseKm').value,
partCost: parseFloat(document.getElementById('partCost').value) || 0,
laborCost: parseFloat(document.getElementById('laborCost').value) || 0,
total: (parseFloat(document.getElementById('partCost').value) || 0) + (parseFloat(document.getElementById('laborCost').value) || 0),
mechanicName: document.getElementById('mechanicName').value,
mechanicPhone: document.getElementById('mechanicPhone').value,
mechanicAddress: document.getElementById('mechanicAddress').value,
nextServiceDate: document.getElementById('nextServiceDate').value,
nextServiceKm: document.getElementById('nextServiceKm').value,
warranty: document.getElementById('warrantyInfo').value,
desc: document.getElementById('expenseDesc').value
};
if (editIndex !== '') {
const targetId = db.expenses[editIndex].id;
const globalIdx = db.expenses.findIndex(item => item.id === targetId);
db.expenses[globalIdx] = expenseData;
document.getElementById('editIndex').value = '';
document.getElementById('formTitle').textContent = '➕ ثبت هزینه جدید';
} else {
db.expenses.push(expenseData);
}
saveData();
document.getElementById('expenseForm').reset();
calculateTotal();
alert('هزینه با موفقیت ذخیره شد.');
renderHistory();
renderReports();
switchTab('history', document.querySelectorAll('.nav-tab')[1]);
}
function renderHistory() {
const tbody = document.getElementById('historyTableBody');
tbody.innerHTML = '';
if (!db.activeCarId) {
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center; color: var(--text-muted);">لطفاً ابتدا یک خودرو انتخاب یا ثبت کنید.</td></tr>`;
return;
}
const search = document.getElementById('searchBox').value.toLowerCase();
const carExpenses = db.expenses.filter(e => e.carId === db.activeCarId);
carExpenses.forEach((exp) => {
const matchText = `${exp.desc} ${exp.mechanicName} ${exp.category} ${exp.type}`.toLowerCase();
if (search && !matchText.includes(search)) return;
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${toPersianDigits(exp.date)}</td>
<td><span class="badge">${exp.type}</span><br><small>${exp.category}</small></td>
<td>${toPersianDigits(Number(exp.km).toLocaleString())}</td>
<td><b>${toPersianDigits(exp.total.toLocaleString())}</b></td>
<td>${exp.mechanicName || '-'}<br><small>${toPersianDigits(exp.mechanicPhone || '')}</small></td>
<td>
<button class="btn btn-secondary" style="padding:4px 8px; font-size:0.75rem;" onclick="editExpense('${exp.id}')">✏️</button>
<button class="btn btn-danger" style="padding:4px 8px; font-size:0.75rem;" onclick="deleteExpense('${exp.id}')">🗑️</button>
</td>
`;
tbody.appendChild(tr);
});
if (tbody.innerHTML === '') {
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center; color: var(--text-muted);">سابقه هزینهای ثبت نشده است.</td></tr>`;
}
}
function editExpense(id) {
const exp = db.expenses.find(e => e.id === id);
if (!exp) return;
const globalIdx = db.expenses.findIndex(e => e.id === id);
document.getElementById('editIndex').value = globalIdx;
document.getElementById('expenseType').value = exp.type;
document.getElementById('expenseCategory').value = exp.category;
document.getElementById('expenseDate').value = exp.date;
document.getElementById('expenseKm').value = exp.km;
document.getElementById('partCost').value = exp.partCost;
document.getElementById('laborCost').value = exp.laborCost;
document.getElementById('mechanicName').value = exp.mechanicName;
document.getElementById('mechanicPhone').value = exp.mechanicPhone;
document.getElementById('mechanicAddress').value = exp.mechanicAddress;
document.getElementById('nextServiceDate').value = exp.nextServiceDate;
document.getElementById('nextServiceKm').value = exp.nextServiceKm;
document.getElementById('warrantyInfo').value = exp.warranty;
document.getElementById('expenseDesc').value = exp.desc;
calculateTotal();
document.getElementById('formTitle').textContent = '✏️ ویرایش هزینه';
switchTab('register', document.querySelectorAll('.nav-tab')[0]);
}
function deleteExpense(id) {
if (confirm('آیا از حذف این سابقه اطمینان دارید؟')) {
db.expenses = db.expenses.filter(e => e.id !== id);
saveData();
renderHistory();
renderReports();
}
}
function renderReports() {
if (!db.activeCarId) return;
const carExpenses = db.expenses.filter(e => e.carId === db.activeCarId);
let totalCost = 0;
let totalLabor = 0;
let categoryMap = {};
carExpenses.forEach(e => {
totalCost += e.total;
totalLabor += (e.laborCost || 0);
categoryMap[e.category] = (categoryMap[e.category] || 0) + e.total;
});
const count = carExpenses.length;
const avg = count > 0 ? Math.round(totalCost / count) : 0;
document.getElementById('repTotalCost').textContent = toPersianDigits(totalCost.toLocaleString()) + ' ت';
document.getElementById('repTotalLabor').textContent = toPersianDigits(totalLabor.toLocaleString()) + ' ت';
document.getElementById('repAvgCost').textContent = toPersianDigits(avg.toLocaleString()) + ' ت';
document.getElementById('repCount').textContent = toPersianDigits(count);
const catDiv = document.getElementById('categoryBreakdown');
catDiv.innerHTML = '';
for (let cat in categoryMap) {
const pct = totalCost > 0 ? Math.round((categoryMap[cat] / totalCost) * 100) : 0;
catDiv.innerHTML += `
<div style="margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between; font-size: 0.82rem; margin-bottom: 4px;">
<span>${cat}</span>
<span><b>${toPersianDigits(categoryMap[cat].toLocaleString())} تومان</b> (${toPersianDigits(pct)}%)</span>
</div>
<div style="background: var(--border); height: 8px; border-radius: 4px; overflow: hidden;">
<div style="background: var(--primary); width: ${pct}%; height: 100%;"></div>
</div>
</div>
`;
}
if (Object.keys(categoryMap).length === 0) {
catDiv.innerHTML = '<span style="color: var(--text-muted);">دادهای برای نمایش وجود ندارد.</span>';
}
}
function exportToNotes() {
if (!db.activeCarId) { alert('خودرویی انتخاب نشده است.'); return; }
const car = db.cars.find(c => c.id === db.activeCarId);
const carExpenses = db.expenses.filter(e => e.carId === db.activeCarId);
let text = `🚗 گزارش هزینههای خودرو\n`;
text += `خودرو: ${car.brand} ${car.type} (${car.model}) - پلاک: ${car.plate}\n`;
text += `------------------------------------\n`;
let sum = 0;
carExpenses.forEach((e, idx) => {
sum += e.total;
text += `${toPersianDigits(idx+1)}. تاریخ: ${toPersianDigits(e.date)} | ${e.type} (${e.category})\n`;
text += ` کیلومتر: ${toPersianDigits(e.km)} | مبلغ: ${toPersianDigits(e.total.toLocaleString())} تومان\n`;
if(e.mechanicName) text += ` تعمیرکار: ${e.mechanicName} (${toPersianDigits(e.mechanicPhone || '-')})\n`;
if(e.desc) text += ` توضیحات: ${e.desc}\n`;
text += `------------------------------------\n`;
});
text += `مجموع کل: ${toPersianDigits(sum.toLocaleString())} تومان`;
navigator.clipboard.writeText(text).then(() => {
alert('متن گزارش کپی شد!');
}).catch(() => {
alert('خطا در کپی متن.');
});
}
function exportToTxtFile() {
if (!db.activeCarId) { alert('خودرویی انتخاب نشده است.'); return; }
const car = db.cars.find(c => c.id === db.activeCarId);
const carExpenses = db.expenses.filter(e => e.carId === db.activeCarId);
let text = `گزارش هزینههای خودرو: ${car.brand} ${car.type}\n\n`;
carExpenses.forEach((e, idx) => {
text += `${idx+1}. ${e.date} - ${e.type} (${e.category}) - ${e.total} تومان\n`;
});
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `Car_Expenses.txt`;
a.click();
}
function exportToCsv() {
if (!db.activeCarId) { alert('خودرویی انتخاب نشده است.'); return; }
const carExpenses = db.expenses.filter(e => e.carId === db.activeCarId);
let csv = '\uFEFFتاریخ,نوع,دسته,کیلومتر,مبلغ کل\n';
carExpenses.forEach(e => {
csv += `"${e.date}","${e.type}","${e.category}",${e.km},${e.total}\n`;
});
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const aElem = document.createElement('a');
aElem.href = url;
aElem.download = 'car_expenses.csv';
aElem.click();
}
function exportJsonBackup() {
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(db, null, 2));
const a = document.createElement('a');
a.href = dataStr;
a.download = 'car_backup.json';
a.click();
}
window.onload = initApp;
Top comments (0)