4444 <i class =" fa fa-print" ></i >
4545 Cetak
4646 </button >
47+ <button id =" download-excel" type =" button" class =" btn btn-success btn-sm" >
48+ <i class =" fa fa-file-excel" ></i >
49+ Excel
50+ </button >
4751 </div >
4852 </div >
4953 </div >
9195@include (' components.wilayah_filter_js' )
9296@push (' js' )
9397 <script src =" {{ asset (' assets/progressive-image/progressive-image.js' ) } }" ></script >
98+ <script src =" https://cdn.jsdelivr.net/npm/sweetalert2@11" ></script >
9499@endpush
95100
96101@section (' js' )
202207
203208 }
204209 return json .data ;
205-
206- // if (json.data.length) {
207-
208- // // jika chart di akses dari halaman statistik penduduk
209- // if(chart && chart.view){
210- // getDataset(json.data, chart)
211- // grafik()
212- // }
213-
214-
215- // return json.data;
216- // }
217-
218- // return false;
219210 },
220211 },
221212 columnDefs: [{
@@ -381,6 +372,10 @@ className: 'text-nowrap',
381372 window .open (` {{ url (' penduduk/cetak' ) } } ?${ $ .param (penduduk .ajax .params ())} ` , ' _blank' );
382373 });
383374
375+ $ (' #download-excel' ).on (' click' , function () {
376+ downloadExcel ();
377+ });
378+
384379 $ (' select.select2-filter' ).each (function () {
385380 $ (this ).select2 ({
386381 width: ' 100%' ,
@@ -393,8 +388,6 @@ className: 'text-nowrap',
393388 for (let i in filterDefault) {
394389 $ (` #${ i} ` ).val (filterDefault[i]).trigger (' change' );
395390 }
396-
397-
398391 });
399392
400393 function getDataset (data , chart ) {
@@ -495,89 +488,148 @@ function getDataset(data, chart) {
495488 }));
496489 }
497490
498- // function getDataset(data, chart) {
499- // const kategori = chart.kategori;
500- // data_grafik = [];
501- // const grouped = {};
502- // judul = chart.nama
503-
504- // const getLabel = {
505- // 'rentang-umur': attr => parseInt(attr.umur),
506- // 'kategori-umur': attr => attr.kategori_umur?.nama,
507- // 'pendidikan-dalam-kk': attr => attr.pendidikan_k_k?.nama,
508- // 'pendidikan-sedang-ditempuh': attr => attr.pendidikan?.nama,
509- // 'agama': attr => attr.agama?.nama,
510- // 'jenis-kelamin': attr => attr.jenis_kelamin?.nama,
511- // 'pekerjaan': attr => attr.pekerjaan?.nama,
512- // 'status-perkawinan': attr => attr.status_kawin?.nama,
513- // 'hubungan-dalam-kk': attr => attr.penduduk_hubungan?.nama,
514- // 'warga-negara': attr => attr.warga_negara?.nama,
515- // 'status-penduduk': attr => attr.penduduk_status?.nama,
516- // 'golongan-darah': attr => attr.golongan_darah?.nama,
517- // 'penyandang-cacat': attr => attr.cacat?.nama,
518- // 'penyakit-menahun': attr => attr.namaSakitMenahun,
519- // 'akseptor-kb': attr => attr.kb?.nama,
520- // 'akta-kelahiran': attr => parseInt(attr.umur),
521- // 'ktp': attr => attr.status_rekam_ktp?.nama,
522- // 'asuransi-kesehatan': attr => attr.namaAsuransi,
523- // 'status-covid': attr => null,
524- // 'suku': attr => attr.suku,
525- // 'bpjs-ketenagakerjaan': attr => attr.bpjs_ketenagakerjaan,
526- // 'status-kehamilan': attr => attr.statusHamil,
527- // };
528-
529- // const isMatch = {
530- // 'rentang-umur': (label) => {
531- // const [awal, akhir] = judul.match(/\d+/g).map(Number);
532- // return label >= awal && label <= akhir;
533- // },
534- // 'kategori-umur': (label) => label === judul,
535- // 'pendidikan-dalam-kk': (label) => label === judul,
536- // 'pendidikan-sedang-ditempuh': (label) => label === judul,
537- // 'agama': (label) => label === judul,
538- // 'jenis-kelamin': (label) => label === judul,
539- // 'pekerjaan': (label) => label === judul,
540- // 'status-perkawinan': (label) => label === judul,
541- // 'hubungan-dalam-kk': (label) => label === judul,
542- // 'warga-negara': (label) => label === judul,
543- // 'status-penduduk': (label) => label === judul,
544- // 'golongan-darah': (label) => label === judul,
545- // 'penyandang-cacat': (label) => label === judul,
546- // 'penyakit-menahun': (label) => label === judul,
547- // 'akseptor-kb': (label) => label === judul,
548- // 'akta-kelahiran': (label) => {
549- // const [awal, akhir] = judul.match(/\d+/g).map(Number);
550- // return label >= awal && label <= akhir;
551- // },
552- // 'ktp': (label) => label === judul,
553- // 'asuransi-kesehatan': (label) => label === judul,
554- // 'status-covid': (label) => label === judul,
555- // 'suku': (label) => label === judul,
556- // 'bpjs-ketenagakerjaan': (label) => label === judul,
557- // 'status-kehamilan': (label) => label === judul,
558- // };
559-
560- // data.forEach(item => {
561- // const attr = item.attributes;
562- // const config = attr.config || {};
563- // const kode = config.kode_kecamatan;
564- // const nama = config.nama_kecamatan;
565-
566- // if (!grouped[kode]) {
567- // grouped[kode] = { nama: nama, total: 0 };
568- // }
569-
570- // const label = getLabel[kategori]?.(attr);
571-
572- // if (label !== undefined && isMatch[kategori]?.(label)) {
573- // grouped[kode].total += 1;
574- // }
575- // });
576-
577- // data_grafik = Object.entries(grouped).map(([kode, val]) => ({
578- // label: val.nama,
579- // value: val.total
580- // }));
581- // }
491+ // Function to download Excel
492+ async function downloadExcel () {
493+ try {
494+ const header = @include (' layouts.components.header_bearer_api_gabungan' );
495+ // Check if there's data to download
496+ const tableData = $ (' #penduduk' ).DataTable ();
497+ const info = tableData .page .info ();
498+ const totalData = info .recordsTotal ;
499+ if (totalData === 0 ) {
500+ Swal .fire ({
501+ icon: ' warning' ,
502+ title: ' Tidak Ada Data' ,
503+ text: ' Tidak ada data penduduk untuk diunduh. Silakan periksa filter Anda.' ,
504+ confirmButtonText: ' OK'
505+ });
506+ return ;
507+ }
508+
509+ // Show loading state
510+ const $btnExcel = $ (' #download-excel' );
511+ $btnExcel .prop (' disabled' , true ).html (
512+ ' <i class="fa fa-spinner fa-spin"></i> Downloading...' );
513+
514+ // Prepare URL for download
515+ const downloadUrl = new URL (
516+ ` {{ config (' app.databaseGabunganUrl' ) } } /api/v1/penduduk/download` );
517+
518+ // Gunakan fungsi data yang sama persis dengan DataTable untuk konsistensi
519+ const filterParams = tableData .ajax .params ();
520+
521+ // Remove pagination parameters since we want all data
522+ delete filterParams[' page[size]' ];
523+ delete filterParams[' page[number]' ];
524+
525+ // Handle umur filter - convert object to separate min/max parameters for backend
526+ if (filterParams[' filter[umur]' ] && typeof filterParams[' filter[umur]' ] === ' object' ) {
527+ const umurObj = filterParams[' filter[umur]' ];
528+
529+ // Create separate parameters for min and max
530+ if (umurObj .min && umurObj .min !== ' ' ) {
531+ filterParams[' filter[umur][min]' ] = umurObj .min ;
532+ }
533+ if (umurObj .max && umurObj .max !== ' ' ) {
534+ filterParams[' filter[umur][max]' ] = umurObj .max ;
535+ }
536+ if (umurObj .satuan ) {
537+ filterParams[' filter[umur][satuan]' ] = umurObj .satuan ;
538+ }
539+
540+ // Remove the original object parameter
541+ delete filterParams[' filter[umur]' ];
542+ }
543+
544+ // Convert filterParams to URLSearchParams for proper encoding
545+ const urlParams = new URLSearchParams ();
546+ Object .keys (filterParams).forEach (key => {
547+ const value = filterParams[key];
548+ if (value !== null && value !== undefined && value !== ' ' && value !== ' null' ) {
549+ urlParams .append (key, value);
550+ }
551+ });
552+
553+ urlParams .append (' totalData' , totalData);
554+
555+ // Make fetch request
556+ const response = await fetch (downloadUrl, {
557+ method: ' POST' ,
558+ headers: {
559+ ... header,
560+ ' Content-Type' : ' application/x-www-form-urlencoded' ,
561+ ' Accept' : ' application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
562+ },
563+ body: urlParams
564+ });
565+
566+ if (! response .ok ) {
567+ const errorText = await response .text ();
568+ throw new Error (` HTTP ${ response .status } : ${ errorText} ` );
569+ }
570+
571+ // Check if response is actually a file
572+ const contentType = response .headers .get (' content-type' );
573+ if (! contentType || (! contentType .includes (
574+ ' application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) && !
575+ contentType .includes (' application/vnd.ms-excel' ))) {
576+ throw new Error (' Response is not a valid Excel file' );
577+ }
578+
579+ // Get filename from response headers or generate one
580+ const contentDisposition = response .headers .get (' content-disposition' );
581+ let filename = ' data_penduduk.xlsx' ;
582+ if (contentDisposition) {
583+ const matches = / filename[^ ;=\n ] * =((['"] ). *? \2 | [^ ;\n ] * )/ .exec (contentDisposition);
584+ if (matches != null && matches[1 ]) {
585+ filename = matches[1 ].replace (/ ['"] / g , ' ' );
586+ }
587+ } else {
588+ // Generate filename with timestamp
589+ const now = new Date ();
590+ const timestamp = now .toISOString ().slice (0 , 19 ).replace (/ [-:T] / g , ' ' );
591+ filename = ` data_penduduk_${ timestamp} .xlsx` ;
592+ }
593+
594+ // Create blob and download
595+ const blob = await response .blob ();
596+ const url = window .URL .createObjectURL (blob);
597+ const a = document .createElement (' a' );
598+ a .href = url;
599+ a .download = filename;
600+ document .body .appendChild (a);
601+ a .click ();
602+ window .URL .revokeObjectURL (url);
603+ document .body .removeChild (a);
604+
605+ // Show success message
606+ Swal .fire ({
607+ icon: ' success' ,
608+ title: ' Berhasil!' ,
609+ text: ` File Excel "${ filename} " berhasil diunduh` ,
610+ timer: 3000 ,
611+ showConfirmButton: false
612+ });
613+
614+ } catch (error) {
615+ console .error (' Download error:' , error);
616+
617+ // Show error message with SweetAlert
618+ Swal .fire ({
619+ icon: ' error' ,
620+ title: ' Gagal Download!' ,
621+ html: `
622+ <p>Terjadi kesalahan saat mengunduh file Excel:</p>
623+ <p><small>${ error .message } </small></p>
624+ <p>Silakan coba lagi atau hubungi administrator.</p>
625+ ` ,
626+ confirmButtonText: ' OK'
627+ });
628+ } finally {
629+ // Reset button state
630+ const $btnExcel = $ (' #download-excel' );
631+ $btnExcel .prop (' disabled' , false ).html (' <i class="fa fa-file-excel"></i> Excel' );
632+ }
633+ }
582634 < / script>
583635@endsection
0 commit comments