penambahan download report
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { onMount } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import Pagination from "$lib/Pagination.svelte";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
type Timesheets = {
|
||||
id: number;
|
||||
@@ -147,6 +148,8 @@
|
||||
let isEditing = false;
|
||||
let currentEditingId: string | null = null;
|
||||
let newTsdata: Record<string, any> = {};
|
||||
let selectedMonth: number | null = null;
|
||||
let selectedYear: number | null = null;
|
||||
let employees: Employee[] = [];
|
||||
let villas: Villa[] = [];
|
||||
let form = {
|
||||
@@ -434,6 +437,47 @@
|
||||
);
|
||||
}
|
||||
|
||||
async function exportTimesheets() {
|
||||
if (!selectedMonth || !selectedYear) {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// // Function to fetch timesheets with optional filters and sorting
|
||||
// async function fetchTimeSheets(
|
||||
// villaIdFilter: string | null = null,
|
||||
@@ -880,35 +924,73 @@
|
||||
Showing {(currentPage - 1) * rowsPerPage + 1}–
|
||||
{Math.min(currentPage * rowsPerPage, totalItems)} of {totalItems} items
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<button
|
||||
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
|
||||
on:click={() => goToPage(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
{#each pageRange(totalPages, currentPage) as page}
|
||||
{#if page === "..."}
|
||||
<span class="px-2">...</span>
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => changePage(page as number)}
|
||||
class="px-2 py-1 border rounded {page === currentPage
|
||||
? 'bg-blue-600 text-white border-blue-600'
|
||||
: 'bg-white border-gray-300 hover:bg-gray-100'}"
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
<button
|
||||
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
|
||||
on:click={() => goToPage(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- Export Controls -->
|
||||
<div class="">
|
||||
<!-- Month Selector -->
|
||||
<label for="month" class="text-sm">Month:</label>
|
||||
<select
|
||||
id="month"
|
||||
class="border border-gray-300 px-2 py-1 rounded text-sm"
|
||||
bind:value={selectedMonth}
|
||||
>
|
||||
<option value="" disabled selected>Select</option>
|
||||
{#each Array.from({ length: 12 }, (_, i) => i + 1) as m}
|
||||
<option value={m}>{m}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
<!-- Year Selector -->
|
||||
<label for="year" class="text-sm ml-2">Year:</label>
|
||||
<select
|
||||
id="year"
|
||||
class="border border-gray-300 px-2 py-1 rounded text-sm"
|
||||
bind:value={selectedYear}
|
||||
>
|
||||
<option value="" disabled selected>Select</option>
|
||||
{#each Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i) as y}
|
||||
<option value={y}>{y}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
<!-- Export Button -->
|
||||
<button
|
||||
class="bg-green-600 text-white px-3 py-1 rounded hover:bg-green-700 text-sm"
|
||||
on:click={exportTimesheets}
|
||||
>
|
||||
⬇️ Export
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<button
|
||||
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
|
||||
on:click={() => goToPage(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
{#each pageRange(totalPages, currentPage) as page}
|
||||
{#if page === "..."}
|
||||
<span class="px-2">...</span>
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => changePage(page as number)}
|
||||
class="px-2 py-1 border rounded {page === currentPage
|
||||
? 'bg-blue-600 text-white border-blue-600'
|
||||
: 'bg-white border-gray-300 hover:bg-gray-100'}"
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
<button
|
||||
class="px-3 py-1 rounded border border-gray-300 bg-white hover:bg-gray-100 disabled:opacity-50"
|
||||
on:click={() => goToPage(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user