export file frontend

This commit is contained in:
2025-07-23 00:28:00 +08:00
parent 7a99219917
commit c8eab1f0f6
3 changed files with 230 additions and 32 deletions

View File

@@ -2,6 +2,7 @@
import { supabase } from "$lib/supabaseClient";
import { onMount } from "svelte";
import { v4 as uuidv4 } from "uuid";
import * as XLSX from 'xlsx';
type Timesheets = {
id: number;
@@ -447,44 +448,182 @@
async function exportTimesheets() {
if (!selectedMonth || !selectedYear) {
alert("Please select a month and year to export.");
alert("Please select a month and year to export.");
return;
}
try {
const response = await fetch("https://flow.catalis.app/webhook-test/villabugis-timesheets", {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
month: selectedMonth,
year: selectedYear
})
});
if (!response.ok) {
throw new Error(`Export failed: ${response.statusText}`);
}
const randomUuid = uuidv4().toString();
// Jika response adalah file (application/octet-stream atau Excel)
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `timesheet-${selectedYear}-${String(selectedMonth).padStart(2, '0')}-${randomUuid}.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Export error:", error);
alert("Failed to export timesheet.");
}
}
async function exportToExcelFromFrontend() {
if (!selectedMonth || !selectedYear) {
alert("Please select both month and year");
return;
}
try {
const response = await fetch("https://flow.catalis.app/webhook-test/villabugis-timesheets", {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
month: selectedMonth,
year: selectedYear
})
const startDate = new Date(selectedYear, selectedMonth - 1, 1);
const endDate = new Date(selectedYear, selectedMonth, 0, 23, 59, 59);
// 1. Fetch timesheet data
const { data: timesheetData, error: timesheetErr } = await supabase
.from("vb_timesheet_data")
.select(`
id,
work_description,
total_work_hour,
type_of_work,
category_of_work,
approval,
datetime_in,
datetime_out,
remarks,
approved_date,
villa_name,
entered_name,
approved_name
`)
.gte("datetime_in", startDate.toISOString())
.lte("datetime_in", endDate.toISOString());
if (timesheetErr) {
console.error("Error fetching timesheet:", timesheetErr);
alert("Failed to fetch timesheet data.");
return;
}
// 2. Fetch active villas
const { data: villaData, error: villaErr } = await supabase
.from("vb_villas")
.select("villa_name")
.eq("villa_status", "Active");
if (villaErr) {
console.error("Error fetching villas:", villaErr);
alert("Failed to fetch villas.");
return;
}
const categoryHeaders = [
"Cleaning",
"Gardening/Pool",
"Maintenance",
"Supervision",
"Guest Service",
"Administration",
"Non Billable"
];
const sheet2Name = "Data";
const ws2 = XLSX.utils.json_to_sheet(timesheetData || []);
// 3. Build Sheet1 rows
const sheet1Rows: any[][] = [];
// A1:B1 → Period label
const periodText = `${startDate.toLocaleString("default", { month: "short" })}/${selectedYear}`;
sheet1Rows.push(["Period", periodText]);
// A2:I2 → Headers
const headers = ["Villa Name", ...categoryHeaders, "TOTAL"];
sheet1Rows.push(headers);
// A3:A(n) → Villa rows
villaData?.forEach((villa, idx) => {
const excelRow = idx + 3; // Excel row number
const row: any[] = [villa.villa_name];
// BH formulas
categoryHeaders.forEach((cat, catIdx) => {
const colLetter = String.fromCharCode(66 + catIdx); // 'B' = 66
const formula = `SUMIFS(${sheet2Name}!$C:$C, ${sheet2Name}!$K:$K, $A${excelRow}, ${sheet2Name}!$E:$E, ${colLetter}$2, ${sheet2Name}!$F:$F, TRUE)`;
row.push({ f: formula });
});
if (!response.ok) {
throw new Error(`Export failed: ${response.statusText}`);
// I column (TOTAL)
row.push({ f: `SUM(B${excelRow}:H${excelRow})` });
sheet1Rows.push(row);
});
// Horizontal summary beside the villa table
const headerRowIdx = 1;
const officeRowIdx = 2;
const fbRowIdx = 3;
const villasOnlyRowIdx = 4;
const ensureCols = (row: any[], length: number) => {
while (row.length < length) row.push(null);
return row;
};
[headerRowIdx, officeRowIdx, fbRowIdx, villasOnlyRowIdx].forEach((idx) => {
sheet1Rows[idx] = sheet1Rows[idx] || [];
ensureCols(sheet1Rows[idx], 13); // at least to column M
});
// Insert labels and formulas
sheet1Rows[headerRowIdx][10] = "Total Work Hours"; // K2
sheet1Rows[headerRowIdx][11] = { f: `SUM(${sheet2Name}!$C:$C)` }; // L2
sheet1Rows[officeRowIdx][10] = "Office"; // K3
sheet1Rows[officeRowIdx][11] = { f: `SUMIFS(${sheet2Name}!$C:$C, ${sheet2Name}!$K:$K, K3)` }; // L3
sheet1Rows[fbRowIdx][10] = "FB"; // K4
sheet1Rows[fbRowIdx][11] = { f: `SUMIFS(${sheet2Name}!$C:$C, ${sheet2Name}!$K:$K, K4)` }; // L4
sheet1Rows[villasOnlyRowIdx][10] = "Villas Only"; // K5
sheet1Rows[villasOnlyRowIdx][11] = { f: `L2-L3` }; // L5
// Convert to worksheet
const ws1 = XLSX.utils.aoa_to_sheet(sheet1Rows);
// Bold header row (A2:I2)
for (let c = 0; c <= headers.length; c++) {
const cellRef = XLSX.utils.encode_cell({ r: 1, c });
if (ws1[cellRef]) {
ws1[cellRef].s = { font: { bold: true } };
}
const randomUuid = uuidv4().toString();
// Jika response adalah file (application/octet-stream atau Excel)
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `timesheet-${selectedYear}-${String(selectedMonth).padStart(2, '0')}-${randomUuid}.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Export error:", error);
alert("Failed to export timesheet.");
}
}
// 4. Build Workbook
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws1, "Summary");
XLSX.utils.book_append_sheet(wb, ws2, sheet2Name);
// 5. Export
const filename = `timesheet_${selectedYear}-${String(selectedMonth).padStart(2, "0")}.xlsx`;
XLSX.writeFile(wb, filename);
}
// Function to delete a timesheet
async function deleteTimesheet(id: string) {
if (confirm("Are you sure you want to delete this Timesheet?")) {
@@ -862,7 +1001,7 @@
<!-- Export Button -->
<button
class="bg-green-600 text-white px-3 py-1 rounded hover:bg-green-700 text-sm"
on:click={exportTimesheets}
on:click={exportToExcelFromFrontend}
>
⬇️ Export
</button>