From f480216e5c6e926a8ff9499ea4d43e83da4ecfcc Mon Sep 17 00:00:00 2001 From: ='fauz <='fauzgabriel@gmail.com> Date: Mon, 30 Jun 2025 12:47:53 +0700 Subject: [PATCH] dropdown action --- app/__pycache__/apiController.cpython-313.pyc | Bin 39874 -> 39664 bytes app/__pycache__/routes.cpython-313.pyc | Bin 16199 -> 16459 bytes app/apiController.py | 72 +++++------ app/routes.py | 11 +- templates/index.html | 112 +++++++++++++++++- 5 files changed, 154 insertions(+), 41 deletions(-) diff --git a/app/__pycache__/apiController.cpython-313.pyc b/app/__pycache__/apiController.cpython-313.pyc index 59b07c19055cbfbb94e32592dfb622ece23bf9b0..0a060d4c336eae34cf5b7894063e6bc9a82a0f95 100644 GIT binary patch delta 2429 zcmb7EdrVVT7{BM<+Y5b{wzRaoZh1_3SVZv+h+@?N0_t!>!LB^*%4??;8DhZKQrN_? zPr>JAjQ^OpMRVhvE{lt9S@w@xoMy&ln{ipP#LZMmW=z!WoLj(FO&0GTzx#dP`Of*i z-|svwonu31S>3o+t76FaPkO^9_+~_hS%w|ik-o&SmbWu+p*eJnd|0UC*DCDHlpjNU zA;OO(eoTa~9C>7OtLk_XRz3|+O;?k+>S-Knh_8USL2qRmpaHLE*$J-I3= zj}L0G84Rtqm)7Ix^LpsEc-!2*=0>7Vz|$eh(!|X+IbTi{xTBbBnq;r?c^Z730na4) zIYRnck&yR4uYl%73e;cM<-_l>d9X~G1Ruv&zz=H_P^^lFVN1NEwO&O~Q{{-Ed%vV|84PR?wJqMZKr?P` zayJn9Qq1Osl^k4-%`tX^cb=;3Q54wdRfZOOynIYw1S#IR%CG{jp`|+ z%fC~);+r_I;w={FH zU4Z*GGi-D%M1R6TSBXr*2FHfGCGYaDc~lF|@)|S^c9j>AMlO|K;Ee0&3iJ-2WN{X? zb6~j2?wCQ$D7ImhX~IkjPN--|0KVD)_o~uS0OG4V!o{Sc)yH{3q&X?zs>wiMaqjxm zFkF*@-j_b9xrkJcScVDTbU0Hn3r@%KaAi>pTr`;AaYZ^jG#J4-B*OAl%jB`4Rka|l zPliXUw9si4A+yo}r>%cMT2=OkJK~{4se}u!#eowG^;3K>COw=Ku)Iz5`NdP)h&k%Y z6l24uX$uWJr6|ivdEQh9$mMBscQs>oJ0bn1&JIDWg^s$6)GLI8e;-LA4~fw-mNSwA zGsyG7>f~1>MLJX0t4Jy$g9UCU$ zQ7D2m)lyomlt2i)K%WD8`+fUH*(g?{G%G?H`QpzVTg{RK+|ic}(wLL%-|ewC`sszn z85Hs<6i}E)VIhTL3UWyowaO@XDYQ}Oq~NE}Ltz_*?G$!X7^1L;!Uq(9!Wf0a6h5MG zoS9F~ZoJ#=4|oFJMz<#rz}|+(wiNw11Oe_rw4$d~GPUMZxrpX1w4VA#wgHPfT6>c=K0yRVP;mkoBoH+E^Cb=}N zn#E%``Q21{y1Ja31Ku`&_+rW(MR$2Kwy?dkX>(h1F+N46Alj;xK!`&K{miI;VNxEj N8x6>EpP?wz`ww=hYt{e& delta 1989 zcmZvdT~HHO6vy}OZZ?6CKrkT?g@gbipn{-Mz7a5LL52D;I-oH^6dDlNgz6MW1s#P} zOW{<+T4tnuQXks7T03p0ooc6#ZHI^2q2u_`@j*x12%TD;T6@m&VQ8C&ALrbAbMF6t z_G}1V=C5AijS~h#IENpT$J4m;+kT_W|Hb;dlgb4fw~W^DBTIGS1~DYpVy+3vb(l*b zd03r@E86}Caa(mSmS@W8aWm}2x*UOdct~!*TpyAfF^>qzBQZB9+gS=@M&$^<#N9GR_xXB#Z?^OZKN-t=gj+g2+_c-61)kcE!;jkK zc-{gfI;Y-BUd;rHa!I#Z$Lz|ps8NA!gdfaj@O-nSpU;xux#cdru__6Eiz$aNeKh=H zwLqpd8|wA3YP`+b4A1puHBO7L!9}YTY9rRDc`!nRFC*r`ll>+*dq4-jW8CUAL0h4y z{AnF!5MLP$1(CH->J0;5Wh7MCa#=Whq_s%?fG5!5hZ>tj>!?_{p}fEWH3M^?&!%N| zc(^haKD4cH`cb_5J_-?X>%N;rspx8;z_@vk&+mS z$cB+lGh}B?Uv?+!Q-S5fnWY6&w>K;+6j?Ql*Hg0i~`|wB6;}pb@uB?>w%|Ve=rPst9+Qsy-9eZlWrwh;zKW zfZ}vSnr6~Hy*FxOWPzfEv=!PSBdw|}%A{x!ba~Biq&k;1!57tgCflr3)VwcBYDeSY z!KP$J-7I5W%ErxKF&2RSo=xCb7o)t=azTLacf{g=%!mB;v~4rW&^<-$+X<)87PP8lSzauLJna$A&*c* zP_L|@Xbqu};3MoI$b`Lw{e(k=BZOYUaY7$qfG|jSmoQ8i;a$*qN1&s(-pR~Hgq(5i{%UW75bg?)4!ykz!>|P)8F6{9`jqGSj;#_RQ>$}hv)bZ diff --git a/app/__pycache__/routes.cpython-313.pyc b/app/__pycache__/routes.cpython-313.pyc index 2943fe907dfa065189ac99f9b74d6fa8d8aa5fd8..eee3129b53fe145b6f868cbb24b0877d788838cb 100644 GIT binary patch delta 3394 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 H@z4AZxO3W! diff --git a/app/apiController.py b/app/apiController.py index e5154e2..294f933 100644 --- a/app/apiController.py +++ b/app/apiController.py @@ -13,18 +13,37 @@ class apiController: return result @staticmethod - def kabkota(conn): + def kabkota(conn, provinsi_id=None): cur = conn.cursor() - SQL = "SELECT id,nama FROM wil_kabupatenkota ORDER BY nama" + if provinsi_id: + SQL = """ + SELECT id, nama + FROM wil_kabupatenkota + WHERE provinsi_id = %s + ORDER BY nama + """ + cur.execute(SQL, (provinsi_id,)) + else: + SQL = "SELECT id,nama FROM wil_kabupatenkota ORDER BY nama" + cur.execute(SQL) - cur.execute(SQL) result = cur.fetchall() return result @staticmethod - def kecamatan(conn): + def kecamatan(conn, kabkota_id): cur = conn.cursor() - SQL = "SELECT id,nama FROM wil_kecamatan ORDER BY nama" - cur.execute(SQL) + if kabkota_id: + SQL = """ + SELECT id, nama + FROM wil_kecamatan + WHERE kabupatenkota_id = %s + ORDER BY nama + """ + cur.execute(SQL, (kabkota_id,)) + else: + SQL = "SELECT id,nama FROM wil_kecamatan ORDER BY nama" + cur.execute(SQL) + result = cur.fetchall() return result @@ -147,20 +166,13 @@ class apiController: kode_provinsi, kabupaten_kota_kode, kabupaten_kota_nama, - COUNT(DISTINCT kelurahan_desa_id) AS "COUNT_DISTINCT(kelurahan_desa_id)" + COUNT(DISTINCT kelurahan_desa_id) AS jumlah_desa FROM ( SELECT - wp.id AS provinsi_id, wp.kode AS kode_provinsi, - wkk.id AS kabupaten_kota_id, wkk.kode AS kabupaten_kota_kode, wkk.nama AS kabupaten_kota_nama, - wk.id AS kecamatan_id, - wk.kode AS kecamatan_kode, - wk.nama AS kecamatan_nama, wkd.id AS kelurahan_desa_id, - wkd.kode AS kelurahan_desa_kode, - wkd.nama AS kelurahan_desa_nama, mid.status, mid.tahun FROM metrik_indeks_desa mid @@ -169,11 +181,10 @@ class apiController: INNER JOIN wil_kabupatenkota wkk ON wk.kabupatenkota_id = wkk.id INNER JOIN wil_provinsi wp ON wkk.provinsi_id = wp.id WHERE mid.tahun = (SELECT MAX(tahun) FROM metrik_indeks_desa) - ) AS virtual_table - WHERE kode_provinsi = %s - AND kabupaten_kota_kode = %s - GROUP BY tahun, status, kode_provinsi, kabupaten_kota_kode, kabupaten_kota_nama - ORDER BY status, kabupaten_kota_kode DESC; + AND wp.kode = %s + AND wkk.kode = %s + ) AS filtered_data + GROUP BY tahun, status, kode_provinsi, kabupaten_kota_kode, kabupaten_kota_nama; """ cur.execute(SQL, (kode_prov, kode_kabkota)) result = cur.fetchall() @@ -708,24 +719,19 @@ class apiController: def getTotalSerapanKab(conn, kode_kab): cur = conn.cursor() SQL = """ - SELECT - SUM(serapan) / 1000000000 AS serapan_per_1M, - (SUM(serapan) / NULLIF(SUM(pagu), 0)) * 100 AS persentase_serapan - FROM ( + WITH latest_year AS ( + SELECT MAX(tahun) AS tahun FROM metrik_dana_desa + ) SELECT - wp.id provinsi_id, wp.kode provinsi_kode, wp.nama provinsi_nama, - wkk.id kabupaten_kota_id, wkk.kode kabupaten_kota_kode, wkk.nama kabupaten_kota_nama, - wk.id kecamatan_id, wk.kode kecamatan_kode, wk.nama kecamatan_nama, - wkd.id kelurahan_desa_id, wkd.kode kelurahan_desa_kode, wkd.nama kelurahan_desa_nama, - mdd.pagu, salur_pagu, mdd.persen_serapan, mdd.tahun_anggaran - FROM metrik_dana_desa mdd - INNER JOIN wil_desa wkd ON mdd.kelurahan_desa_id = wkd.id + SUM(mdd.salur_pagu) / 1000000000 AS serapan_per_1M, + (SUM(mdd.salur_pagu) / NULLIF(SUM(mdd.pagu), 0)) * 100 AS persentase_serapan + FROM metrik_dana_desa mdd + INNER JOIN wil_desa wkd ON mdd.desa_id = wkd.id INNER JOIN wil_kecamatan wk ON wkd.kecamatan_id = wk.id INNER JOIN wil_kabupatenkota wkk ON wk.kabupatenkota_id = wkk.id INNER JOIN wil_provinsi wp ON wkk.provinsi_id = wp.id - WHERE wkk.kode = %s - ) AS virtual_table - WHERE tahun_anggaran = 2024; + WHERE mdd.tahun = (SELECT tahun FROM latest_year) AND + wkk.kode = %s """ try: cur.execute(SQL, (kode_kab,)) diff --git a/app/routes.py b/app/routes.py index 5042c5b..de5b45d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,5 +1,4 @@ -from flask import Blueprint, jsonify, render_template -import psycopg2 +from flask import Blueprint, jsonify, render_template, request from app import mapController from app.apiController import apiController from app.utils import toRupiah @@ -272,14 +271,16 @@ def get_master_provinsi(): data = apiController.provinsi(conn) return data -@main.route('/api/master/kabkota') +@main.route('/api/master/kabkota/') def get_master_kabkota(): + provinsi_id = request.args.get('provinsi_id') with get_connection() as conn: - data = apiController.kabkota + data = apiController.kabkota(conn, provinsi_id) return jsonify(data) @main.route('/api/master/kecamatan') def get_master_kecamatan(): + kabkota_id = request.args.get('kabkota_id') with get_connection() as conn: - data = apiController.kecamatan(conn) + data = apiController.kecamatan(conn,kabkota_id) return jsonify(data) diff --git a/templates/index.html b/templates/index.html index f341295..713b6bf 100644 --- a/templates/index.html +++ b/templates/index.html @@ -583,7 +583,7 @@ } $('#provinsiDropdown').on('change', function (e) { - const kodeProvinsi = $(this).val(); // lebih aman daripada e.target.value + const kodeProvinsi = $(this).val(); console.log("Provinsi dipilih:", kodeProvinsi); if (!kodeProvinsi) { @@ -593,6 +593,29 @@ localStorage.setItem('kode_provinsi', kodeProvinsi); + $('#kabkotaDropdown').empty().trigger('change'); + + // Ambil kabupaten berdasarkan provinsi + const kabkotaURL = "{{ url_for('main.get_master_kabkota') }}"; + $.ajax({ + url: kabkotaURL, + data: { provinsi_id: kodeProvinsi }, + dataType: 'json', + success: function (data) { + const results = data.map(kabupaten => ({ + id: kabupaten[0], + text: kabupaten[1] + })); + + // Tambahkan ke dropdown kabupaten + $('#kabkotaDropdown').select2({ + data: results, + placeholder: 'Pilih Kabupaten/Kota', + allowClear: true + }); + } + }); + console.log(`Memuat kabupaten dari /geojson/kabupaten/${kodeProvinsi}`); loadLayer(`/geojson/kabupaten/${kodeProvinsi}`, () => { console.log("Layer kabupaten dimuat."); @@ -600,9 +623,36 @@ }); $('#kabkotaDropdown').on('change', function (e) { - const kodeKabupaten = e.target.value; - + const kodeKabupaten = $(this).val(); + if (!kodeKabupaten) { + console.warn("Tidak ada kode provinsi yang dipilih!"); + return; + } localStorage.setItem('kode_kabupatenkota', kodeKabupaten); + + $('#kecamatanDropdown').empty().trigger('change'); + + // Ambil kabupaten berdasarkan provinsi + const kabkotaURL = "{{ url_for('main.get_master_kecamatan') }}"; + $.ajax({ + url: kabkotaURL, + data: { kabkota_id: kodeKabupaten }, + dataType: 'json', + success: function (data) { + const results = data.map(kecamatan => ({ + id: kecamatan[0], + text: kecamatan[1] + })); + + // Tambahkan ke dropdown kabupaten + $('#kecamatanDropdown').select2({ + data: results, + placeholder: 'Pilih Kabupaten/Kota', + allowClear: true + }); + } + }); + loadLayer(`/geojson/kecamatan/${kodeKabupaten}`, () => {}, 'kecamatan'); }).select2(); @@ -611,10 +661,66 @@ localStorage.setItem('kode_kecamatan', kodeKecamatan); loadLayer(`/geojson/desa/${kodeKecamatan}`, () => {}, 'desa'); }); + + +