feat(sales): 升級銷售紀錄高奢翡翠綠導出下拉選單與修復進度條卡死
This commit is contained in:
parent
b5259a5b28
commit
1aa5aed72e
@ -125,6 +125,12 @@ class SalesController extends Controller
|
||||
if ($status) $ordersQuery->where('status', $status);
|
||||
}
|
||||
|
||||
// 匯出功能攔截
|
||||
if ($request->filled('export')) {
|
||||
$exportType = $request->input('export');
|
||||
return $this->handleExport($tab, $exportType, $ordersQuery, $invoicesQuery, $dispenseQuery);
|
||||
}
|
||||
|
||||
// 3. 執行分頁 (使用獨立的 pageName)
|
||||
$perPage = $request->input('per_page', 10);
|
||||
$data['orders'] = $ordersQuery->latest()->paginate($perPage, ['*'], 'orders_page')->withQueryString();
|
||||
@ -597,4 +603,272 @@ class SalesController extends Controller
|
||||
'description' => '來店優惠活動設定',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 處理銷售中心資料匯出 (CSV 串流下載)
|
||||
*/
|
||||
private function handleExport($tab, $type, $ordersQuery, $invoicesQuery, $dispenseQuery)
|
||||
{
|
||||
$isExcel = ($type === 'excel');
|
||||
$ext = $isExcel ? 'xls' : 'csv';
|
||||
$contentType = $isExcel ? 'application/vnd.ms-excel; charset=utf-8' : 'text/csv; charset=utf-8';
|
||||
|
||||
$filename = '';
|
||||
$headers = [];
|
||||
$callback = null;
|
||||
|
||||
if ($tab === 'orders') {
|
||||
$filename = '交易紀錄_' . now()->format('YmdHis') . '.' . $ext;
|
||||
$headers = [
|
||||
__('Order Number'),
|
||||
__('Transaction Time'),
|
||||
__('Machine Name'),
|
||||
__('Machine Serial No'),
|
||||
__('Product Items'),
|
||||
__('Quantity'),
|
||||
__('Original Amount'),
|
||||
__('Discount Amount'),
|
||||
__('Payment Amount'),
|
||||
__('Payment Type'),
|
||||
__('Status'),
|
||||
__('Invoice Number'),
|
||||
__('Staff Name'),
|
||||
__('Staff ID'),
|
||||
__('Card UID'),
|
||||
];
|
||||
|
||||
// 預先載入關聯以防 N+1
|
||||
$ordersQuery->with([
|
||||
'machine:id,name,serial_no',
|
||||
'invoice:id,order_id,invoice_no',
|
||||
'items:id,order_id,product_name,quantity',
|
||||
'staffCardLog:id,order_id,staff_card_id',
|
||||
'staffCardLog.staffCard:id,employee_id,name,card_uid'
|
||||
]);
|
||||
|
||||
$callback = function () use ($ordersQuery, $headers, $isExcel) {
|
||||
$file = fopen('php://output', 'w');
|
||||
if ($isExcel) {
|
||||
fwrite($file, '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">');
|
||||
fwrite($file, '<head><meta charset="utf-8"/><style>table { border-collapse: collapse; } th, td { border: 1px solid #cbd5e1; padding: 8px 12px; text-align: left; } th { background-color: #f1f5f9; font-weight: bold; }</style></head><body>');
|
||||
fwrite($file, '<table><thead><tr>');
|
||||
foreach ($headers as $header) {
|
||||
fwrite($file, '<th>' . htmlspecialchars($header) . '</th>');
|
||||
}
|
||||
fwrite($file, '</tr></thead><tbody>');
|
||||
} else {
|
||||
fwrite($file, "\xEF\xBB\xBF"); // 注入 UTF-8 BOM
|
||||
fputcsv($file, $headers);
|
||||
}
|
||||
|
||||
// 分批 (Chunk) 查詢,每次 500 筆,確保常數記憶體用量
|
||||
$ordersQuery->latest()->chunk(500, function ($orders) use ($file, $isExcel) {
|
||||
foreach ($orders as $order) {
|
||||
$productSummary = $order->items->map(function ($item) {
|
||||
return $item->product_name . ' x' . number_format($item->quantity, 0);
|
||||
})->implode(', ');
|
||||
|
||||
$totalQty = $order->items->sum('quantity');
|
||||
|
||||
$row = [
|
||||
$order->order_no,
|
||||
$order->created_at->format('Y-m-d H:i:s'),
|
||||
$order->machine->name ?? 'Unknown',
|
||||
$order->machine->serial_no ?? '---',
|
||||
$productSummary ?: '---',
|
||||
$totalQty,
|
||||
number_format($order->total_amount, 2, '.', ''),
|
||||
number_format($order->discount_amount, 2, '.', ''),
|
||||
number_format($order->pay_amount, 2, '.', ''),
|
||||
$order->payment_type_label,
|
||||
__($order->status),
|
||||
$order->invoice->invoice_no ?? '---',
|
||||
$order->staffCardLog->staffCard->name ?? '---',
|
||||
$order->staffCardLog->staffCard->employee_id ?? '---',
|
||||
$order->staffCardLog->staffCard->card_uid ?? '---',
|
||||
];
|
||||
|
||||
if ($isExcel) {
|
||||
fwrite($file, '<tr>');
|
||||
foreach ($row as $val) {
|
||||
fwrite($file, '<td>' . htmlspecialchars($val) . '</td>');
|
||||
}
|
||||
fwrite($file, '</tr>');
|
||||
} else {
|
||||
fputcsv($file, $row);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ($isExcel) {
|
||||
fwrite($file, '</tbody></table></body></html>');
|
||||
}
|
||||
fclose($file);
|
||||
};
|
||||
} elseif ($tab === 'invoices') {
|
||||
$filename = '電子發票紀錄_' . now()->format('YmdHis') . '.' . $ext;
|
||||
$headers = [
|
||||
__('Invoice Number'),
|
||||
__('Invoice Date'),
|
||||
__('Flow ID'),
|
||||
__('Machine Name'),
|
||||
__('Machine Serial No'),
|
||||
__('Associated Order'),
|
||||
__('Amount'),
|
||||
__('Status'),
|
||||
];
|
||||
|
||||
// 預先載入關聯
|
||||
$invoicesQuery->with([
|
||||
'machine:id,name,serial_no',
|
||||
'order:id,order_no'
|
||||
]);
|
||||
|
||||
$callback = function () use ($invoicesQuery, $headers, $isExcel) {
|
||||
$file = fopen('php://output', 'w');
|
||||
if ($isExcel) {
|
||||
fwrite($file, '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">');
|
||||
fwrite($file, '<head><meta charset="utf-8"/><style>table { border-collapse: collapse; } th, td { border: 1px solid #cbd5e1; padding: 8px 12px; text-align: left; } th { background-color: #f1f5f9; font-weight: bold; }</style></head><body>');
|
||||
fwrite($file, '<table><thead><tr>');
|
||||
foreach ($headers as $header) {
|
||||
fwrite($file, '<th>' . htmlspecialchars($header) . '</th>');
|
||||
}
|
||||
fwrite($file, '</tr></thead><tbody>');
|
||||
} else {
|
||||
fwrite($file, "\xEF\xBB\xBF");
|
||||
fputcsv($file, $headers);
|
||||
}
|
||||
|
||||
$invoicesQuery->latest()->chunk(500, function ($invoices) use ($file, $isExcel) {
|
||||
foreach ($invoices as $inv) {
|
||||
// 狀態判斷邏輯需與 tab-invoices.blade.php 完全一致
|
||||
$status = 'valid';
|
||||
if (empty($inv->invoice_no)) $status = 'failed';
|
||||
if ($inv->rtn_code === 'void') $status = 'void';
|
||||
|
||||
$statusMap = [
|
||||
'valid' => __('Valid'),
|
||||
'void' => __('Void'),
|
||||
'failed' => __('Failed'),
|
||||
'refunded' => __('Refunded'),
|
||||
];
|
||||
$statusLabel = $statusMap[$status] ?? $status;
|
||||
|
||||
$row = [
|
||||
$inv->invoice_no ?: '---',
|
||||
$inv->invoice_date ?: '---',
|
||||
$inv->flow_id ?: '---',
|
||||
$inv->machine->name ?? ($inv->order->machine->name ?? 'Unknown'),
|
||||
$inv->machine->serial_no ?? ($inv->order->machine->serial_no ?? '---'),
|
||||
$inv->order->order_no ?? '---',
|
||||
number_format($inv->amount, 2, '.', ''),
|
||||
$statusLabel,
|
||||
];
|
||||
|
||||
if ($isExcel) {
|
||||
fwrite($file, '<tr>');
|
||||
foreach ($row as $val) {
|
||||
fwrite($file, '<td>' . htmlspecialchars($val) . '</td>');
|
||||
}
|
||||
fwrite($file, '</tr>');
|
||||
} else {
|
||||
fputcsv($file, $row);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ($isExcel) {
|
||||
fwrite($file, '</tbody></table></body></html>');
|
||||
}
|
||||
fclose($file);
|
||||
};
|
||||
} elseif ($tab === 'dispense') {
|
||||
$filename = '遠端出貨紀錄_' . now()->format('YmdHis') . '.' . $ext;
|
||||
$headers = [
|
||||
__('Machine'),
|
||||
__('Machine Serial No'),
|
||||
__('Product Name'),
|
||||
__('Slot No'),
|
||||
__('Amount'),
|
||||
__('Dispense Status'),
|
||||
__('Dispense Time'),
|
||||
__('Associated Order'),
|
||||
];
|
||||
|
||||
// 預先載入關聯
|
||||
$dispenseQuery->with([
|
||||
'machine:id,name,serial_no',
|
||||
'product:id,name',
|
||||
'order:id,order_no'
|
||||
]);
|
||||
|
||||
$callback = function () use ($dispenseQuery, $headers, $isExcel) {
|
||||
$file = fopen('php://output', 'w');
|
||||
if ($isExcel) {
|
||||
fwrite($file, '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">');
|
||||
fwrite($file, '<head><meta charset="utf-8"/><style>table { border-collapse: collapse; } th, td { border: 1px solid #cbd5e1; padding: 8px 12px; text-align: left; } th { background-color: #f1f5f9; font-weight: bold; }</style></head><body>');
|
||||
fwrite($file, '<table><thead><tr>');
|
||||
foreach ($headers as $header) {
|
||||
fwrite($file, '<th>' . htmlspecialchars($header) . '</th>');
|
||||
}
|
||||
fwrite($file, '</tr></thead><tbody>');
|
||||
} else {
|
||||
fwrite($file, "\xEF\xBB\xBF");
|
||||
fputcsv($file, $headers);
|
||||
}
|
||||
|
||||
$dispenseQuery->latest()->chunk(500, function ($logs) use ($file, $isExcel) {
|
||||
foreach ($logs as $log) {
|
||||
// 出貨狀態對照,需與 tab-dispense.blade.php 完全一致
|
||||
$statusMap = [
|
||||
'success' => __('Dispense Success'),
|
||||
'failed' => __('Dispense Failed'),
|
||||
'pending' => __('Dispense Pending'),
|
||||
'1' => __('Dispense Success'),
|
||||
'0' => __('Dispense Failed'),
|
||||
];
|
||||
$statusKey = (string)($log->dispense_status ?? 'pending');
|
||||
$statusLabel = $statusMap[$statusKey] ?? $statusKey;
|
||||
|
||||
$row = [
|
||||
$log->machine->name ?? 'Unknown',
|
||||
$log->machine->serial_no ?? '---',
|
||||
$log->product->name ?? 'Unknown Product',
|
||||
$log->slot_no,
|
||||
number_format($log->amount, 2, '.', ''),
|
||||
$statusLabel,
|
||||
$log->machine_time ? $log->machine_time->format('Y-m-d H:i:s') : '---',
|
||||
$log->order->order_no ?? '---',
|
||||
];
|
||||
|
||||
if ($isExcel) {
|
||||
fwrite($file, '<tr>');
|
||||
foreach ($row as $val) {
|
||||
fwrite($file, '<td>' . htmlspecialchars($val) . '</td>');
|
||||
}
|
||||
fwrite($file, '</tr>');
|
||||
} else {
|
||||
fputcsv($file, $row);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ($isExcel) {
|
||||
fwrite($file, '</tbody></table></body></html>');
|
||||
}
|
||||
fclose($file);
|
||||
};
|
||||
}
|
||||
|
||||
if ($callback) {
|
||||
return response()->streamDownload($callback, $filename, [
|
||||
'Content-Type' => $contentType,
|
||||
'Cache-Control' => 'no-cache, must-revalidate',
|
||||
'Pragma' => 'no-cache',
|
||||
'Expires' => '0',
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2085,5 +2085,9 @@
|
||||
"Total Net Profit": "總淨利潤",
|
||||
"Gross Margin": "毛利率",
|
||||
"No product data matching search criteria": "沒有符合搜尋條件的商品資料",
|
||||
"Analyze Solely": "單獨分析"
|
||||
"Analyze Solely": "單獨分析",
|
||||
"Order Number": "訂單編號",
|
||||
"Product Items": "商品品項",
|
||||
"Original Amount": "應付金額",
|
||||
"Discount Amount": "折扣金額"
|
||||
}
|
||||
@ -140,7 +140,8 @@
|
||||
<p class="text-xs text-slate-400 mb-4 leading-relaxed">
|
||||
{{ __('Please use our standard template to ensure data compatibility.') }}
|
||||
</p>
|
||||
<a href="{{ route('admin.data-config.staff-cards.template') }}"
|
||||
<a href="{{ route('admin.data-config.staff-cards.template') }}" download
|
||||
@click="setTimeout(() => { const bar = document.getElementById('top-loading-bar'); if(bar) bar.classList.remove('loading'); }, 1000);"
|
||||
class="inline-flex items-center gap-2 text-sm font-bold text-cyan-600 hover:text-cyan-700 transition-colors group">
|
||||
<span>{{ __('Download Template') }}</span>
|
||||
<svg class="w-4 h-4 transition-transform group-hover:translate-y-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
@ -196,6 +196,131 @@ function salesCenter() {
|
||||
console.error('Failed to load order detail:', err);
|
||||
this.showPanel = false;
|
||||
});
|
||||
},
|
||||
|
||||
exportData(type) {
|
||||
const container = document.getElementById('tab-' + this.activeTab + '-container');
|
||||
let params = new URLSearchParams();
|
||||
params.set('tab', this.activeTab);
|
||||
params.set('export', type);
|
||||
|
||||
if (container) {
|
||||
const form = container.querySelector('form');
|
||||
if (form) {
|
||||
const formData = new FormData(form);
|
||||
formData.forEach((value, key) => {
|
||||
if (value && value.trim() !== '') {
|
||||
if (key !== 'tab' && key !== 'export') {
|
||||
params.append(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 使用隱藏的 iframe 進行下載,避免觸發主視窗的 beforeunload 事件,
|
||||
// 從而防止全域的頂部進度條 (top-loading-bar) 被觸發並卡在 95% 的位置。
|
||||
let iframe = document.getElementById('export-download-iframe');
|
||||
if (!iframe) {
|
||||
iframe = document.createElement('iframe');
|
||||
iframe.id = 'export-download-iframe';
|
||||
iframe.style.display = 'none';
|
||||
document.body.appendChild(iframe);
|
||||
}
|
||||
iframe.src = `${window.location.pathname}?${params.toString()}`;
|
||||
|
||||
// 顯示成功提示訊息,與商品報表保持一致
|
||||
if (type === 'csv') {
|
||||
window.showToast?.('CSV 報表導出成功', 'success');
|
||||
} else if (type === 'excel') {
|
||||
window.showToast?.('Excel 報表導出成功', 'success');
|
||||
}
|
||||
},
|
||||
|
||||
copyTableData() {
|
||||
const container = document.getElementById('tab-' + this.activeTab + '-container');
|
||||
if (!container) return;
|
||||
const table = container.querySelector('table');
|
||||
if (!table) return;
|
||||
|
||||
let headers = [];
|
||||
table.querySelectorAll('thead th').forEach((th, idx, arr) => {
|
||||
if (idx < arr.length - 1) {
|
||||
headers.push(th.innerText.trim().replace(/\s+/g, ' '));
|
||||
}
|
||||
});
|
||||
|
||||
let rows = [headers.join('\t')];
|
||||
table.querySelectorAll('tbody tr').forEach(tr => {
|
||||
if (tr.querySelector('td.colspan') || tr.querySelector('td[colspan]')) return;
|
||||
let cells = [];
|
||||
tr.querySelectorAll('td').forEach((td, idx, arr) => {
|
||||
if (idx < arr.length - 1) {
|
||||
let text = td.innerText.trim().replace(/\r?\n/g, ' ').replace(/\s+/g, ' ');
|
||||
cells.push(text);
|
||||
}
|
||||
});
|
||||
if (cells.length > 0) {
|
||||
rows.push(cells.join('\t'));
|
||||
}
|
||||
});
|
||||
|
||||
navigator.clipboard.writeText(rows.join('\n')).then(() => {
|
||||
window.showToast?.('已成功將明細複製至剪貼簿', 'success');
|
||||
}).catch(err => {
|
||||
console.error('Clipboard copy failed:', err);
|
||||
window.showToast?.('複製失敗,瀏覽器權限可能不足', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
printTable() {
|
||||
const container = document.getElementById('tab-' + this.activeTab + '-container');
|
||||
if (!container) return;
|
||||
const table = container.querySelector('table');
|
||||
if (!table) return;
|
||||
|
||||
const clonedTable = table.cloneNode(true);
|
||||
|
||||
// 移除所有 SVG 圖標,防止列印或 PDF 輸出時因為沒有載入 Tailwind 樣式而顯示巨大黑色區塊
|
||||
clonedTable.querySelectorAll('svg').forEach(svg => svg.remove());
|
||||
|
||||
clonedTable.querySelectorAll('tr').forEach(tr => {
|
||||
const cells = tr.querySelectorAll('th, td');
|
||||
if (cells.length > 0) {
|
||||
cells[cells.length - 1].remove();
|
||||
}
|
||||
});
|
||||
|
||||
let tabTitle = '';
|
||||
if (this.activeTab === 'orders') tabTitle = '交易紀錄';
|
||||
else if (this.activeTab === 'invoices') tabTitle = '電子發票';
|
||||
else if (this.activeTab === 'dispense') tabTitle = '遠端出貨紀錄';
|
||||
|
||||
const printWindow = window.open('', '_blank');
|
||||
let html = `<html><head><title>${tabTitle}</title>`;
|
||||
html += `<style>
|
||||
body { font-family: system-ui, -apple-system, sans-serif; padding: 30px; color: #1e293b; }
|
||||
h1 { font-size: 24px; font-weight: 800; margin-bottom: 5px; }
|
||||
.subtitle { font-size: 13px; font-weight: 600; color: #64748b; margin-bottom: 25px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 12px; }
|
||||
th, td { border: 1px solid #cbd5e1; padding: 10px 12px; text-align: left; }
|
||||
th { background-color: #f1f5f9; font-weight: bold; color: #334155; }
|
||||
@media print {
|
||||
body { padding: 0; }
|
||||
}
|
||||
</style></head><body>`;
|
||||
html += `<h1>${tabTitle}</h1>`;
|
||||
html += `<div class="subtitle">列印時間:${new Date().toLocaleString()}</div>`;
|
||||
html += clonedTable.outerHTML;
|
||||
html += `</body></html>`;
|
||||
|
||||
printWindow.document.write(html);
|
||||
printWindow.document.close();
|
||||
printWindow.focus();
|
||||
setTimeout(() => {
|
||||
printWindow.print();
|
||||
printWindow.close();
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,6 +100,37 @@
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Exporters Dropdown -->
|
||||
<div class="relative flex items-center gap-2" x-data="{ exportOpen: false }">
|
||||
<button type="button" @click="exportOpen = !exportOpen" @click.away="exportOpen = false"
|
||||
class="p-2.5 rounded-xl bg-emerald-500 text-white hover:bg-emerald-600 shadow-lg shadow-emerald-500/25 transition-all active:scale-95 flex items-center gap-2 text-xs font-bold"
|
||||
title="{{ __('Export Report') }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
<svg class="w-3 h-3 text-white/80 transition-transform duration-200" :class="exportOpen ? 'rotate-180' : ''" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Exporter Menu -->
|
||||
<div x-show="exportOpen" x-transition
|
||||
class="absolute right-0 top-full mt-2 bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 shadow-xl rounded-2xl p-2 z-30 min-w-[160px]"
|
||||
x-cloak>
|
||||
<button type="button" @click="copyTableData(); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
📋 {{ __('Copy to Clipboard') }}
|
||||
</button>
|
||||
<button type="button" @click="exportData('csv'); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
📄 {{ __('Export to CSV') }}
|
||||
</button>
|
||||
<button type="button" @click="exportData('excel'); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
📊 {{ __('Export to Excel') }}
|
||||
</button>
|
||||
<button type="button" @click="printTable(); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
🖨️ {{ __('Print & PDF') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
@ -101,6 +101,37 @@
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Exporters Dropdown -->
|
||||
<div class="relative flex items-center gap-2" x-data="{ exportOpen: false }">
|
||||
<button type="button" @click="exportOpen = !exportOpen" @click.away="exportOpen = false"
|
||||
class="p-2.5 rounded-xl bg-emerald-500 text-white hover:bg-emerald-600 shadow-lg shadow-emerald-500/25 transition-all active:scale-95 flex items-center gap-2 text-xs font-bold"
|
||||
title="{{ __('Export Report') }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
<svg class="w-3 h-3 text-white/80 transition-transform duration-200" :class="exportOpen ? 'rotate-180' : ''" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Exporter Menu -->
|
||||
<div x-show="exportOpen" x-transition
|
||||
class="absolute right-0 top-full mt-2 bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 shadow-xl rounded-2xl p-2 z-30 min-w-[160px]"
|
||||
x-cloak>
|
||||
<button type="button" @click="copyTableData(); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
📋 {{ __('Copy to Clipboard') }}
|
||||
</button>
|
||||
<button type="button" @click="exportData('csv'); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
📄 {{ __('Export to CSV') }}
|
||||
</button>
|
||||
<button type="button" @click="exportData('excel'); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
📊 {{ __('Export to Excel') }}
|
||||
</button>
|
||||
<button type="button" @click="printTable(); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
🖨️ {{ __('Print & PDF') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
@ -101,6 +101,37 @@
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Exporters Dropdown -->
|
||||
<div class="relative flex items-center gap-2" x-data="{ exportOpen: false }">
|
||||
<button type="button" @click="exportOpen = !exportOpen" @click.away="exportOpen = false"
|
||||
class="p-2.5 rounded-xl bg-emerald-500 text-white hover:bg-emerald-600 shadow-lg shadow-emerald-500/25 transition-all active:scale-95 flex items-center gap-2 text-xs font-bold"
|
||||
title="{{ __('Export Report') }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
<svg class="w-3 h-3 text-white/80 transition-transform duration-200" :class="exportOpen ? 'rotate-180' : ''" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Exporter Menu -->
|
||||
<div x-show="exportOpen" x-transition
|
||||
class="absolute right-0 top-full mt-2 bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 shadow-xl rounded-2xl p-2 z-30 min-w-[160px]"
|
||||
x-cloak>
|
||||
<button type="button" @click="copyTableData(); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
📋 {{ __('Copy to Clipboard') }}
|
||||
</button>
|
||||
<button type="button" @click="exportData('csv'); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
📄 {{ __('Export to CSV') }}
|
||||
</button>
|
||||
<button type="button" @click="exportData('excel'); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
📊 {{ __('Export to Excel') }}
|
||||
</button>
|
||||
<button type="button" @click="printTable(); exportOpen = false" class="w-full text-left py-2 px-3 hover:bg-slate-50 dark:hover:bg-slate-800 text-xs font-bold text-slate-700 dark:text-slate-300 rounded-xl transition-all flex items-center gap-2">
|
||||
🖨️ {{ __('Print & PDF') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user