Dev.to WebDev 🛠 Dev 👁 0 📖 12 min read

مدیریت هزینه خودرو

<!DOCTYPE html> مدیریت هزینه‌ها و تعمیرات خودرو <!-- تنظیمات اختصاصی PWA و هوم‌اسکرین --> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" cont

<!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/[email protected]/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 =&gt; 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 =&gt; el.classList.remove('active'));
    document.querySelectorAll('.nav-tab').forEach(el =&gt; 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 =&gt; c.id === db.activeCarId);
    const banner = document.getElementById('activeCarBanner');
    if (car) {
        banner.innerHTML = `خودرو: &lt;b&gt;${car.brand} ${car.type} (${car.model})&lt;/b&gt; | پلاک: &lt;b&gt;${toPersianDigits(car.plate)}&lt;/b&gt;`;
    } else {
        banner.innerHTML = '⚠️ خودرویی انتخاب نشده. ابتدا یک خودرو ثبت کنید.';
    }
}

function renderCarsList() {
    const container = document.getElementById('carsListContainer');
    container.innerHTML = '';

    if (db.cars.length === 0) {
        container.innerHTML = '&lt;p style="color:var(--text-muted); font-size:0.85rem; text-align:center; padding:10px;"&gt;هیچ خودرویی ثبت نشده است.&lt;/p&gt;';
        return;
    }

    db.cars.forEach(car =&gt; {
        const isActive = car.id === db.activeCarId;
        const div = document.createElement('div');
        div.className = `car-card-item ${isActive ? 'active-car' : ''}`;
        div.innerHTML = `
            &lt;div&gt;
                &lt;b&gt;${car.brand} ${car.type}&lt;/b&gt; (${car.model}) - سال ${toPersianDigits(car.year)}
                &lt;br&gt;&lt;small style="color:var(--text-muted);"&gt;پلاک: ${toPersianDigits(car.plate)}&lt;/small&gt;
            &lt;/div&gt;
            &lt;div style="display:flex; gap:6px;"&gt;
                ${!isActive ? `&lt;button class="btn btn-secondary" style="padding:6px 10px; font-size:0.75rem;" onclick="setActiveCar('${car.id}')"&gt;انتخاب&lt;/button&gt;` : '&lt;span class="badge"&gt;فعال&lt;/span&gt;'}
                &lt;button class="btn btn-danger" style="padding:6px 10px; font-size:0.75rem;" onclick="deleteCar('${car.id}')"&gt;🗑️&lt;/button&gt;
            &lt;/div&gt;
        `;
        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 =&gt; e.carId !== id);
        db.cars = db.cars.filter(c =&gt; c.id !== id);
        db.activeCarId = db.cars.length &gt; 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 =&gt; 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 = `&lt;tr&gt;&lt;td colspan="6" style="text-align:center; color: var(--text-muted);"&gt;لطفاً ابتدا یک خودرو انتخاب یا ثبت کنید.&lt;/td&gt;&lt;/tr&gt;`;
        return;
    }

    const search = document.getElementById('searchBox').value.toLowerCase();
    const carExpenses = db.expenses.filter(e =&gt; e.carId === db.activeCarId);

    carExpenses.forEach((exp) =&gt; {
        const matchText = `${exp.desc} ${exp.mechanicName} ${exp.category} ${exp.type}`.toLowerCase();
        if (search &amp;&amp; !matchText.includes(search)) return;

        const tr = document.createElement('tr');
        tr.innerHTML = `
            &lt;td&gt;${toPersianDigits(exp.date)}&lt;/td&gt;
            &lt;td&gt;&lt;span class="badge"&gt;${exp.type}&lt;/span&gt;&lt;br&gt;&lt;small&gt;${exp.category}&lt;/small&gt;&lt;/td&gt;
            &lt;td&gt;${toPersianDigits(Number(exp.km).toLocaleString())}&lt;/td&gt;
            &lt;td&gt;&lt;b&gt;${toPersianDigits(exp.total.toLocaleString())}&lt;/b&gt;&lt;/td&gt;
            &lt;td&gt;${exp.mechanicName || '-'}&lt;br&gt;&lt;small&gt;${toPersianDigits(exp.mechanicPhone || '')}&lt;/small&gt;&lt;/td&gt;
            &lt;td&gt;
                &lt;button class="btn btn-secondary" style="padding:4px 8px; font-size:0.75rem;" onclick="editExpense('${exp.id}')"&gt;✏️&lt;/button&gt;
                &lt;button class="btn btn-danger" style="padding:4px 8px; font-size:0.75rem;" onclick="deleteExpense('${exp.id}')"&gt;🗑️&lt;/button&gt;
            &lt;/td&gt;
        `;
        tbody.appendChild(tr);
    });

    if (tbody.innerHTML === '') {
        tbody.innerHTML = `&lt;tr&gt;&lt;td colspan="6" style="text-align:center; color: var(--text-muted);"&gt;سابقه هزینه‌ای ثبت نشده است.&lt;/td&gt;&lt;/tr&gt;`;
    }
}

function editExpense(id) {
    const exp = db.expenses.find(e =&gt; e.id === id);
    if (!exp) return;

    const globalIdx = db.expenses.findIndex(e =&gt; 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 =&gt; e.id !== id);
        saveData();
        renderHistory();
        renderReports();
    }
}

function renderReports() {
    if (!db.activeCarId) return;
    const carExpenses = db.expenses.filter(e =&gt; e.carId === db.activeCarId);

    let totalCost = 0;
    let totalLabor = 0;
    let categoryMap = {};

    carExpenses.forEach(e =&gt; {
        totalCost += e.total;
        totalLabor += (e.laborCost || 0);
        categoryMap[e.category] = (categoryMap[e.category] || 0) + e.total;
    });

    const count = carExpenses.length;
    const avg = count &gt; 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 &gt; 0 ? Math.round((categoryMap[cat] / totalCost) * 100) : 0;
        catDiv.innerHTML += `
            &lt;div style="margin-bottom: 10px;"&gt;
                &lt;div style="display: flex; justify-content: space-between; font-size: 0.82rem; margin-bottom: 4px;"&gt;
                    &lt;span&gt;${cat}&lt;/span&gt;
                    &lt;span&gt;&lt;b&gt;${toPersianDigits(categoryMap[cat].toLocaleString())} تومان&lt;/b&gt; (${toPersianDigits(pct)}%)&lt;/span&gt;
                &lt;/div&gt;
                &lt;div style="background: var(--border); height: 8px; border-radius: 4px; overflow: hidden;"&gt;
                    &lt;div style="background: var(--primary); width: ${pct}%; height: 100%;"&gt;&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        `;
    }
    if (Object.keys(categoryMap).length === 0) {
        catDiv.innerHTML = '&lt;span style="color: var(--text-muted);"&gt;داده‌ای برای نمایش وجود ندارد.&lt;/span&gt;';
    }
}

function exportToNotes() {
    if (!db.activeCarId) { alert('خودرویی انتخاب نشده است.'); return; }
    const car = db.cars.find(c =&gt; c.id === db.activeCarId);
    const carExpenses = db.expenses.filter(e =&gt; 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) =&gt; {
        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(() =&gt; {
        alert('متن گزارش کپی شد!');
    }).catch(() =&gt; {
        alert('خطا در کپی متن.');
    });
}

function exportToTxtFile() {
    if (!db.activeCarId) { alert('خودرویی انتخاب نشده است.'); return; }
    const car = db.cars.find(c =&gt; c.id === db.activeCarId);
    const carExpenses = db.expenses.filter(e =&gt; e.carId === db.activeCarId);
    let text = `گزارش هزینه‌های خودرو: ${car.brand} ${car.type}\n\n`;
    carExpenses.forEach((e, idx) =&gt; {
        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 =&gt; e.carId === db.activeCarId);
    let csv = '\uFEFFتاریخ,نوع,دسته,کیلومتر,مبلغ کل\n';
    carExpenses.forEach(e =&gt; {
        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;
📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.