├── App Android - React Native webView
├── AndroidManifest.xml
├── App.js
├── README.md
├── gradle.properties
└── styles.xml
├── LICENSE
├── README.md
├── Versi 01
├── Kode.gs
├── absensi.xlsx
└── index.html
├── Versi 02
├── Absensi.xlsx
├── Kode.gs
├── Pegawai.xlsx
└── index.html
├── Versi 03
├── firebase
│ └── index.html
└── google apps script
│ ├── Data Absensi
│ └── 2021-06.xlsx
│ ├── Data Pegawai.xlsx
│ ├── index.html
│ ├── kode.gs
│ └── logo.png
└── logo.png
/App Android - React Native webView/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/App Android - React Native webView/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { WebView } from 'react-native-webview';
3 |
4 | class App extends Component {
5 | render() {
6 | return (
7 |
13 |
14 | );
15 | }
16 | }
17 |
18 | export default App;
19 |
--------------------------------------------------------------------------------
/App Android - React Native webView/README.md:
--------------------------------------------------------------------------------
1 | # Aplikasi android webView - React Native
2 |
3 | Aplikasi lanjutan untuk absensi dengan Google Apps Script.
4 | Membuat aplikasi android webView dengan react native.
5 | - Akses lokasi (geolocation)
6 | - Menghapus banner google
7 |
8 | ## Video tutorial
9 | [](https://youtu.be/jl2anXE-ixY)
10 |
11 | ## Donasi
12 | Dukung saya
13 | - [Traktir kopi](https://sociabuzz.com/fahroniganteng/tribe)
14 | - [atau Es krim](https://trakteer.id/fahroniganteng/tip)
15 |
--------------------------------------------------------------------------------
/App Android - React Native webView/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 | # Automatically convert third-party libraries to use AndroidX
25 | android.enableJetifier=true
26 |
27 | # Version of flipper SDK to use with React Native
28 | FLIPPER_VERSION=0.93.0
29 |
--------------------------------------------------------------------------------
/App Android - React Native webView/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 fahroniganteng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Absensi-google-script
2 | Web absensi pada google script dan spreadsheets dengan login dan menyimpan lokasi
3 |
4 |
5 |
6 | ## VERSI 03
7 |
8 | #### Fitur Web Absensi Versi 03
9 | 1. Fitur versi 01 dan versi 02 masih digunakan.
10 | 2. Metode absensi :
11 | - Absen masuk tidak boleh lebih dari sekali dalam sehari.
12 | - Absen pulang bisa diulang berkali2 (data absen pulang yang lama akan ditimpa)
13 | Jika salah absen pulang pada waktu berangkat, masih bisa absen pulang lagi di sore hari.
14 | - Bisa absen pulang tanpa absen masuk
15 | Ini untuk jika lupa absen masuk, maka masih bisa absen pulang (dihitung potongan tidak absen)
16 | 3. Perbaikan data rekap absensi :
17 | - Recording absensi dalam file spreadsheet per bulan ⇒ mempermudah untuk rekap bulanan (1 file spreadsheet tiap 1 bulan)
18 | - Penggabungan record absen masuk & pulang ⇒ mengurangi jumlah record
19 | 4. Perhitungan potongan terlambat, pulang lebih awal, tidak absen masuk/pulang.
20 | 5. Setting timezone (di setting pada config/variable code)
21 |
22 | #### Aplikasi yang digunakan
23 | - Aplikasi web menggunakan google script
24 | - Penyimpanan data pada google drive
25 | - Recording pada google spreadsheet
26 | - Sub domain pada google firebase (cek pada video)
27 | - Report menggunakan google data studio (cek pada video)
28 |
29 | #### Video
30 | - Video 01. Demo & fitur aplikasi
31 | https://youtu.be/jJtDMGuq6dQ
32 | - Video 02. Instalasi aplikasi pada google apps script
33 | https://youtu.be/FUkhfHXj8jo
34 | - Video 03. Menghilangkan banner google dan sub domain google firebase
35 | https://youtu.be/neY8G5oCjFM
36 | - Video 04. Membuat report spreadsheet dan google data studio
37 | https://youtu.be/pz6Ld-8P8i4
38 |
39 | #### Testing & demo aplikasi
40 | > user : github
41 | > pass : github
42 | - Link sub domain (tidak ada top banner google)
43 | https://demoabsensi.web.app/
44 | - Link google apps script
45 | https://script.google.com/macros/s/AKfycbx2vOrvFQ4ZyQEtefQR5I2At105yEeMR6HoxQ0RkcheBp7AgG0B_0xrs26y4z45OSc/exec
46 |
47 | #### Testing & demo reporting (google data studio)
48 | - Link google studio
49 | https://datastudio.google.com/s/ldMer2ZUTiY
50 |
51 |
52 | ### NOTE :
53 | Ditemukan bug pada penggunaan ID Pegawai dengan nol di depan,
54 | `misal : 0054321`
55 | pada saat submit absensi, ID tersebut di convert menjadi angka oleh spreadsheet, sehingga yang terekam pada data absensi adalah :
56 | `54321`
57 | pada aplikasi `0054321 != 54321`, sehingga dianggap user yang berbeda dan masih bisa melakukan absensi di hari yang sama.
58 | Solusi Sementara
59 | Tambahkan huruf pada ID, misal:
60 | `ABC0054321`
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | ## VERSI 02
75 | Tambahan fitur dari versi 01:
76 | - Membatasi absensi WFO (Work From Office) / WFH (Work From Home) berdasarkankan jarak.
77 | - Link ke google map untuk melihat jarak dan rute lokasi absen menuju kantor.
78 | - Simpan jarak absensi dengan kantor
79 | - Kegiatan harian
80 | #### Video demo & instalasi
81 | [](https://youtu.be/Sf83RYbiwo0)
82 |
83 | #### Testing & demo aplikasi
84 | https://script.google.com/macros/s/AKfycbzXCF2kUJl72pl42FGQ81FMzTg1axb_UFpVC7HRzhTtME_LbgMyrgGIkqZaTiuAIFnPfg/exec
85 | - user : github
86 | - pass : github
87 |
88 | > NOTE :
89 | > Jika menggunakan browser chrome di android, jika link diatas tidak bisa dibuka coba logout dari akun google yang di chrome.
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | ## VERSI 01
99 | Fitur :
100 | - Login dengan password
101 | - Simpan waktu, koordinat dan lokasi absensi
102 | #### Video demo & instalasi
103 | [](https://youtu.be/l8oBqwMrlaE)
104 |
105 | #### Testing & demo aplikasi
106 | https://script.google.com/macros/s/AKfycbzyp4ubIEPShU69QxBH_i-Yek9LLISezFKAS89DOXs0eZWAC4XlE6opgVT_Y3cDPIKS/exec
107 | - user : github
108 | - pass : github
109 |
110 | > NOTE :
111 | > Jika menggunakan browser chrome di android, jika link diatas tidak bisa dibuka coba logout dari akun google yang di chrome.
112 |
113 |
114 |
115 | ## License and credits
116 | Lisensi kode saya adalah MIT, untuk libraries yang lain mengikuti lisensi masing-masing.
117 | - Jquery.js
118 | - Bootstrap
119 | - Google script
120 | - Google spreadsheet
121 | - etc...
122 |
123 |
124 |
125 | ## Donasi
126 | Dukung saya
127 | - [Traktir kopi](https://sociabuzz.com/fahroniganteng/tribe)
128 | - [atau Es krim](https://trakteer.id/fahroniganteng/tip)
129 |
130 |
--------------------------------------------------------------------------------
/Versi 01/Kode.gs:
--------------------------------------------------------------------------------
1 | /*
2 | * ABSENSI GOOGLE SCRIPT
3 | * ***********************************************************************************
4 | * Code by : fahroni|ganteng
5 | * contact me : fahroniganteng@gmail.com
6 | * Date : Mar 2021
7 | * License : MIT
8 | *
9 | */
10 |
11 | //spreadsheet url
12 | var database = "https://docs.google.com/spreadsheets/d/[id-dokumen]/edit#gid=0";
13 |
14 | function doGet(){
15 | return HtmlService
16 | .createTemplateFromFile('index')
17 | .evaluate()
18 | .setSandboxMode(HtmlService.SandboxMode.NATIVE)
19 | .addMetaTag('viewport', 'width=device-width, initial-scale=1');
20 | }
21 |
22 | function submitAbsensi(data){
23 | // Buka Spreadsheet
24 | let ss = SpreadsheetApp.openByUrl(database);
25 | let ws = ss.getSheetByName('pegawai');
26 | let list = ws.getRange(2,1,ws.getRange("A2").getDataRegion().getLastRow() - 1, 5).getValues();
27 | let userId = list.map(function(r){ return r[1].toString(); });
28 | let pass = list.map(function(r){ return r[3].toString(); });
29 |
30 | // Verifikasi data yang dikirim
31 | data.idPegawai = data['idPegawai'] !== undefined?data.idPegawai.toString():'';
32 | data.password = data['password'] !== undefined?data.password.toString():'';
33 | data.position = data['position'] !== undefined?data.position:[0,0];
34 |
35 | // Verifikasi user dan password
36 | let indexData = userId.indexOf(data.idPegawai);
37 | if(indexData > -1 && pass[indexData] == data.password){
38 | let nama = list.map(function(r){ return r[2]});
39 |
40 | // Get Alamat dari koordinat
41 | let koordinat = data.position[0] +', '+ data.position[1];
42 | let response = Maps.newGeocoder().reverseGeocode(data.position[0], data.position[1]);
43 | let lokasi = response.results[0].formatted_address;
44 |
45 | //buka sheet absensi dan simpan data
46 | ws = ss.getSheetByName("absensi");
47 | ws.appendRow([data.idPegawai, nama[indexData], new Date(),koordinat, lokasi]);
48 | return nama[indexData];
49 | }
50 | else
51 | return false;
52 | }
53 |
--------------------------------------------------------------------------------
/Versi 01/absensi.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fahroniganteng/Absensi-google-script/86f49f64c77f592ad4ccec2abb582fbcb4ad52c1/Versi 01/absensi.xlsx
--------------------------------------------------------------------------------
/Versi 01/index.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Fahroni Ganteng co, ltd
21 |
22 |
46 |
49 |
50 |
51 |
52 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/Versi 02/Absensi.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fahroniganteng/Absensi-google-script/86f49f64c77f592ad4ccec2abb582fbcb4ad52c1/Versi 02/Absensi.xlsx
--------------------------------------------------------------------------------
/Versi 02/Kode.gs:
--------------------------------------------------------------------------------
1 | /*
2 | * ABSENSI GOOGLE SCRIPT
3 | * ***********************************************************************************
4 | * Code by : fahroni|ganteng
5 | * contact me : fahroniganteng@gmail.com
6 | * Date : Apr 2021
7 | * License : MIT
8 | *
9 | */
10 |
11 | var idGambarLogo = '-------------------id file-------------------'; // id file logo di google drive
12 | var idDataPegawai = '------------------id file-------------------'; // id file spreadsheet
13 | var idDataAbsensi = '------------------id file-------------------'; // id file spreadsheet
14 | var namaPerusahaan = 'Fahroni Ganteng co, ltd';
15 | var lokasiPerusahaan = [-6.088664, 106.996309]; // koordinat [lat,lon]
16 | var jarakMaxWFO = 1; // max jarak (dari koordinat perusahaan) yang diperbolehkan absensi WFO (dalam km)
17 | var jarakMaxWFH = 50; // max jarak WFH
18 |
19 | function doGet() {
20 | let templateIndex = HtmlService.createTemplateFromFile('index');
21 | templateIndex.dt = {
22 | logo: getImage(idGambarLogo),
23 | perusahaan: namaPerusahaan
24 | };
25 | return templateIndex
26 | .evaluate()
27 | .setSandboxMode(HtmlService.SandboxMode.IFRAME)
28 | .addMetaTag('viewport', 'width=device-width, initial-scale=1');
29 | }
30 |
31 | class absensi {
32 | constructor(dt) {
33 | this.dt = dt;
34 | // validasi data
35 | this.dt.idPegawai = this.dt['idPegawai'] !== undefined ? this.dt.idPegawai.toString() : '';
36 | this.dt.password = this.dt['password'] !== undefined ? this.dt.password.toString() : '';
37 | this.dt.kegiatan = this.dt['kegiatan'] !== undefined ? this.dt.kegiatan.toString() : '';
38 | this.dt.absensi = this.dt['absensi'] !== undefined ? this.dt.absensi.toString() : '';
39 | this.dt.workFrom = this.dt['workFrom'] !== undefined ? this.dt.workFrom.toString() : '';
40 | this.dt.position = this.dt['position'] !== undefined ? this.dt.position : [0, 0];
41 |
42 | // get list pegawai --> from spreadsheet
43 | this.indexUser = -1;
44 | let user = SpreadsheetApp.openById(idDataPegawai);
45 | let userSheet = user.getSheetByName('Pegawai');//sheet
46 | this.user = userSheet.getRange(2, 1, userSheet.getRange("A1").getDataRegion().getLastRow() - 1, 10).getValues();
47 | }
48 | saveAbsensi() {
49 | let abs = SpreadsheetApp.openById(idDataAbsensi);
50 | let absensi = abs.getSheetByName('Absensi');//sheet
51 | let location = Maps.newGeocoder().reverseGeocode(this.dt.position[0], this.dt.position[1]); // get alamat dari koordinat
52 | let user = this.user[this.indexUser];
53 | absensi.appendRow([
54 | new Date(),
55 | user[1],
56 | user[2],
57 | user[3],
58 | this.dt['absensi'],
59 | this.dt['workFrom'],
60 | this.dt['kegiatan'],
61 | this.distance,
62 | this.dt.position[0] + ',' + this.dt.position[1],
63 | location.results[0].formatted_address
64 | ]);
65 | return user[2];
66 | }
67 | validUser() {//login
68 | let id = this.user.map(function (r) { return r[1].toString().toUpperCase(); });
69 | let pass = this.user.map(function (r) { return r[4].toString(); });
70 | this.indexUser = id.indexOf(this.dt.idPegawai.toUpperCase());
71 | return (this.indexUser >= 0 && pass[this.indexUser] == this.dt.password) ? true : false;
72 | }
73 | validDistance() {// jarak dari perusahaan
74 | if (this.dt.position[0] == 0 && this.dt.position[1] == 0) {
75 | return 'Lokasi anda tidak terekam...';
76 | }
77 | else {
78 | this.distance = this.getDistanceBetween(lokasiPerusahaan[0], lokasiPerusahaan[1], this.dt.position[0], this.dt.position[1]);
79 | if (this.distance > jarakMaxWFO && this.dt.workFrom == 'WFO')
80 | return 'Lokasi anda terlalu jauh ' + this.distance + 'km dari perusahaan Maksimal jarak untuk WFO adalah ' + jarakMaxWFO + 'km.';
81 | else if (this.distance > jarakMaxWFH && this.dt.workFrom == 'WFH')
82 | return 'Lokasi anda terlalu jauh. ' + this.distance + 'km dari perusahaan Maksimal jarak untuk WFH adalah ' + jarakMaxWFH + 'km.';
83 | else return 'confirm';
84 | }
85 | }
86 | getDistanceBetween(originLat, originLong, destLat, destLong) {
87 | var directions, route;
88 |
89 | // Create a new Direction finder using Maps API available on Google Apps Script
90 | directions = Maps.newDirectionFinder()
91 | .setOrigin(originLat, originLong)
92 | .setDestination(destLat, destLong)
93 | .setMode(Maps.DirectionFinder.Mode.WALKING)
94 | .getDirections(); // Direction Object
95 |
96 | // The path may have some segments so it sum it all
97 | if (directions.routes[0]) {
98 | route = directions.routes[0].legs.reduce(function (acc, currentRow) {
99 | acc.dist += currentRow.distance.value / 1000;
100 | acc.dur += currentRow.duration.value / 60;
101 | return acc;
102 | }, { dist: 0, dur: 0 });
103 | return route.dist;
104 | } else {
105 | return 0;
106 | }
107 | }
108 | }
109 |
110 | function submitAbsensi(dt) {
111 | let a = new absensi(dt);
112 | let ret = {
113 | success: false,
114 | msg: ''
115 | }
116 |
117 | if (!a.validUser()) ret.msg = 'Anda tidak terdaftar';
118 | else {
119 | let cekJarak = a.validDistance();
120 | if (cekJarak != 'confirm') ret.msg = cekJarak;
121 | else {
122 | ret.msg = a.saveAbsensi();
123 | ret.success = true;
124 | }
125 | }
126 | return ret;
127 | }
128 | function getImage(fileId) {
129 | var img = DriveApp.getFileById(fileId).getBlob().getBytes();
130 | return Utilities.base64Encode(img);
131 | }
132 |
--------------------------------------------------------------------------------
/Versi 02/Pegawai.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fahroniganteng/Absensi-google-script/86f49f64c77f592ad4ccec2abb582fbcb4ad52c1/Versi 02/Pegawai.xlsx
--------------------------------------------------------------------------------
/Versi 02/index.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |