Flutter / Mobile

This commit is contained in:
rmyrv 2025-07-25 14:37:04 +07:00
parent 9fe873ccb2
commit dbcd1755f5
18 changed files with 327 additions and 127 deletions

View File

@ -1,3 +1,3 @@
{ {
"cmake.sourceDirectory": "C:/Users/Romi/Documents/GitHub/absen/linux" "cmake.sourceDirectory": "C:/Users/Romi/Documents/TA/mobileabsen/linux"
} }

View File

@ -1,3 +1,6 @@
import java.util.Properties
import java.io.FileInputStream
plugins { plugins {
id("com.android.application") id("com.android.application")
id("kotlin-android") id("kotlin-android")
@ -5,6 +8,12 @@ plugins {
id("dev.flutter.flutter-gradle-plugin") id("dev.flutter.flutter-gradle-plugin")
} }
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android { android {
namespace = "com.example.absen" namespace = "com.example.absen"
compileSdk = flutter.compileSdkVersion compileSdk = flutter.compileSdkVersion
@ -30,11 +39,21 @@ android {
versionName = flutter.versionName versionName = flutter.versionName
} }
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String
}
}
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. // TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works. // Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug") // signingConfig = signingConfigs.getByName("debug")
signingConfig = signingConfigs.getByName("release")
} }
} }
} }
@ -42,3 +61,9 @@ android {
flutter { flutter {
source = "../.." source = "../.."
} }
dependencies {
// ...
implementation("com.google.android.material:material:1.8.0")
// ...
}

View File

@ -1,54 +1,49 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Izin yang dibutuhkan -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application
android:label="absen"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<application
android:name="${applicationName}"
android:label="Abensiku"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config">
<!-- Google Maps API key harus di sini -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCj4UPomrFAUt61rUNOus0Rx2D_Cxm1hM8"/>
<!-- Theme normal flutter -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"/>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCj4UPomrFAUt61rUNOus0Rx2D_Cxm1hM8"/>
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> <!-- Flutter embedding marker -->
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
</application> </application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> <!-- Jika kamu pakai clear-text HTTP -->
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.PROCESS_TEXT"/> <action android:name="android.intent.action.PROCESS_TEXT"/>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Mengizinkan HTTP (cleartext) ke IP lokal 192.168.1.33 -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.1.6</domain>
</domain-config>
</network-security-config>

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:absen/config/config.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -70,7 +71,7 @@ class _CheckinPhotoScreenState extends State<CheckinPhotoScreen> {
if (!_isPhotoReady()) return; if (!_isPhotoReady()) return;
setState(() => _isLoading = true); setState(() => _isLoading = true);
final token = widget.token; final token = widget.token;
final uri = Uri.parse('http://localhost:8000/api/employee/attendance'); final uri = Uri.parse('${AppConfig.baseUrl}/api/employee/attendance');
final request = final request =
http.MultipartRequest('POST', uri) http.MultipartRequest('POST', uri)
..headers['Authorization'] = 'Bearer $token' ..headers['Authorization'] = 'Bearer $token'

View File

@ -1,13 +1,14 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:absen/config/config.dart';
import 'package:absen/models/attendance_area.dart';
import 'package:absen/widgets/google_map_web_view.dart'; import 'package:absen/widgets/google_map_web_view.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:absen/models/attendance_area.dart';
import 'checkin_photo_screen.dart'; import 'checkin_photo_screen.dart';
@ -47,7 +48,6 @@ class _CheckinScreenState extends State<CheckinScreen> {
); );
return; return;
} }
LocationPermission permission = await Geolocator.checkPermission(); LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) { if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission(); permission = await Geolocator.requestPermission();
@ -64,7 +64,6 @@ class _CheckinScreenState extends State<CheckinScreen> {
); );
return; return;
} }
Position? pos; Position? pos;
try { try {
pos = await Geolocator.getCurrentPosition( pos = await Geolocator.getCurrentPosition(
@ -98,9 +97,7 @@ class _CheckinScreenState extends State<CheckinScreen> {
} }
Future<void> _fetchAttendanceAreas() async { Future<void> _fetchAttendanceAreas() async {
final uri = Uri.parse( final uri = Uri.parse('${AppConfig.baseUrl}/api/employee/attendance/areas');
'http://localhost:8000/api/employee/attendance/areas',
);
final token = widget.token; final token = widget.token;
try { try {
final resp = await http.get( final resp = await http.get(
@ -154,7 +151,7 @@ class _CheckinScreenState extends State<CheckinScreen> {
if (_position == null) return; if (_position == null) return;
setState(() => _isLoading = true); setState(() => _isLoading = true);
final uri = Uri.parse( final uri = Uri.parse(
'http://localhost:8000/api/employee/attendance/check-location', '${AppConfig.baseUrl}/api/employee/attendance/check-location',
); );
try { try {
final response = await http.post( final response = await http.post(

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:absen/config/config.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -70,7 +71,7 @@ class _CheckoutPhotoScreenState extends State<CheckoutPhotoScreen> {
if (!_isPhotoReady()) return; if (!_isPhotoReady()) return;
setState(() => _isLoading = true); setState(() => _isLoading = true);
final token = widget.token; final token = widget.token;
final uri = Uri.parse('http://localhost:8000/api/employee/attendance'); final uri = Uri.parse('${AppConfig.baseUrl}/api/employee/attendance');
final request = final request =
http.MultipartRequest('POST', uri) http.MultipartRequest('POST', uri)
..headers['Authorization'] = 'Bearer $token' ..headers['Authorization'] = 'Bearer $token'

View File

@ -1,13 +1,14 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:absen/config/config.dart';
import 'package:absen/models/attendance_area.dart';
import 'package:absen/widgets/google_map_web_view.dart'; import 'package:absen/widgets/google_map_web_view.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:absen/models/attendance_area.dart';
import 'checkout_photo_screen.dart'; import 'checkout_photo_screen.dart';
@ -43,7 +44,7 @@ class _CheckoutScreenState extends State<CheckoutScreen> {
Future<void> _checkTodayStatus() async { Future<void> _checkTodayStatus() async {
setState(() => _checkedStatus = false); setState(() => _checkedStatus = false);
final url = Uri.parse( final url = Uri.parse(
'http://localhost:8000/api/employee/attendance/today-status', '${AppConfig.baseUrl}/api/employee/attendance/today-status',
); );
try { try {
final resp = await http.get( final resp = await http.get(
@ -69,9 +70,7 @@ class _CheckoutScreenState extends State<CheckoutScreen> {
} }
Future<void> _fetchAttendanceAreas() async { Future<void> _fetchAttendanceAreas() async {
final uri = Uri.parse( final uri = Uri.parse('${AppConfig.baseUrl}/api/employee/attendance/areas');
'http://localhost:8000/api/employee/attendance/areas',
);
// Asumsikan endpoint mengembalikan JSON array: [ { center_lat, center_lng, radius }, ... ] // Asumsikan endpoint mengembalikan JSON array: [ { center_lat, center_lng, radius }, ... ]
try { try {
final resp = await http.get( final resp = await http.get(
@ -203,7 +202,7 @@ class _CheckoutScreenState extends State<CheckoutScreen> {
} }
setState(() => _isLoading = true); setState(() => _isLoading = true);
final uri = Uri.parse( final uri = Uri.parse(
'http://localhost:8000/api/employee/attendance/check-location', '${AppConfig.baseUrl}/api/employee/attendance/check-location',
); );
try { try {
final resp = await http.post( final resp = await http.post(
@ -374,7 +373,6 @@ class _CheckoutScreenState extends State<CheckoutScreen> {
} }
}, },
myLocationEnabled: true, myLocationEnabled: true,
myLocationButtonEnabled: false,
); );
} }
} }

3
lib/config/config.dart Normal file
View File

@ -0,0 +1,3 @@
class AppConfig {
static const String baseUrl = 'http://192.168.1.6:8000';
}

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:absen/config/config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -18,7 +19,7 @@ class DashboardScreen extends StatelessWidget {
Future<Map<String, dynamic>> fetchTodayStatus(String token) async { Future<Map<String, dynamic>> fetchTodayStatus(String token) async {
final url = Uri.parse( final url = Uri.parse(
'http://localhost:8000/api/employee/attendance/today-status', '${AppConfig.baseUrl}/api/employee/attendance/today-status',
); );
final response = await http.get( final response = await http.get(
url, url,
@ -33,7 +34,7 @@ class DashboardScreen extends StatelessWidget {
Future<List<Map<String, dynamic>>> fetchLastActivities(String token) async { Future<List<Map<String, dynamic>>> fetchLastActivities(String token) async {
final url = Uri.parse( final url = Uri.parse(
'http://localhost:8000/api/employee/attendance/history?length=3&start=0', '${AppConfig.baseUrl}/api/employee/attendance/history?length=3&start=0',
); );
final response = await http.get( final response = await http.get(
url, url,
@ -54,7 +55,7 @@ class DashboardScreen extends StatelessWidget {
final todayStr = final todayStr =
"${today.year.toString().padLeft(4, '0')}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}"; "${today.year.toString().padLeft(4, '0')}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}";
final url = Uri.parse( final url = Uri.parse(
'http://localhost:8000/api/employee/permission/history?start_date=$todayStr&length=5', '${AppConfig.baseUrl}/api/employee/permission/history?start_date=$todayStr&length=5',
); );
final response = await http.get( final response = await http.get(
url, url,
@ -237,14 +238,14 @@ class DashboardScreen extends StatelessWidget {
now.month, now.month,
now.day, now.day,
8, 8,
30, 00,
); );
final endTime = DateTime( final endTime = DateTime(
now.year, now.year,
now.month, now.month,
now.day, now.day,
17, 17,
30, 00,
); );
DateTime? checkInTime; DateTime? checkInTime;
DateTime? checkOutTime; DateTime? checkOutTime;
@ -433,20 +434,43 @@ class DashboardScreen extends StatelessWidget {
type == 'in' ? 'Check-in' : 'Check-out'; type == 'in' ? 'Check-in' : 'Check-out';
final date = item['date'] ?? ''; final date = item['date'] ?? '';
final time = item['time'] ?? ''; final time = item['time'] ?? '';
final status = item['status'] ?? '-'; final status = item['status'] ?? '-';
final statusColor = late Color statusColor;
status == 'accepted' late String statusText;
? const Color(0xFF1BCFB4)
: (status == 'late' switch (status) {
? const Color(0xFFFFC542) case 'pending':
: (status == 'pending' statusColor = const Color(0xFFFFC542);
? const Color(0xFFFFC542) statusText = 'Menunggu';
: const Color(0xFFE57373))); break;
final statusText = case 'accepted':
status == 'late' statusColor = const Color(0xFF1BCFB4);
? 'Terlambat' statusText = 'Diterima';
: (status[0].toUpperCase() + break;
status.substring(1)); case 'jam_kerja_kurang':
statusColor = const Color(0xFFE57373);
statusText = 'Jam Kerja Kurang';
break;
case 'lembur':
statusColor = const Color(0xFF4F8DFD);
statusText = 'Lembur';
break;
case 'late':
statusColor = const Color(0xFFFF7043);
statusText = 'Terlambat';
break;
case 'rejected':
statusColor = const Color(0xFFD32F2F);
statusText = 'Ditolak';
break;
default:
statusColor = const Color(0xFF9E9E9E);
statusText =
status[0].toUpperCase() +
status.substring(1);
}
return _ActivityTile( return _ActivityTile(
icon: icon, icon: icon,
title: title, title: title,

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:absen/config/config.dart';
import 'package:dotted_border/dotted_border.dart'; import 'package:dotted_border/dotted_border.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -34,6 +35,7 @@ class _IzinScreenState extends State<IzinScreen> {
'Cuti Tahunan', 'Cuti Tahunan',
'Izin Sakit', 'Izin Sakit',
'Izin Setengah Hari', 'Izin Setengah Hari',
'Dinas di Luar',
]; ];
// Pick start/end dates // Pick start/end dates
@ -95,7 +97,7 @@ class _IzinScreenState extends State<IzinScreen> {
setState(() => _isLoading = true); setState(() => _isLoading = true);
final uri = Uri.parse('http://localhost:8000/api/employee/permission'); final uri = Uri.parse('${AppConfig.baseUrl}/api/employee/permission');
final req = final req =
http.MultipartRequest('POST', uri) http.MultipartRequest('POST', uri)
..headers['Authorization'] = 'Bearer ${widget.token}' ..headers['Authorization'] = 'Bearer ${widget.token}'
@ -323,7 +325,7 @@ class _IzinScreenState extends State<IzinScreen> {
// Photo proof // Photo proof
const Text( const Text(
'Foto Bukti (Optional)', 'Foto Bukti',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),

View File

@ -1,10 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'package:absen/config/config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dashboard_screen.dart'; import 'dashboard_screen.dart';
import 'register_screen.dart';
class LoginScreen extends StatefulWidget { class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key); const LoginScreen({Key? key}) : super(key: key);
@ -23,17 +23,29 @@ class _LoginScreenState extends State<LoginScreen> {
setState(() { setState(() {
_isLoading = true; _isLoading = true;
}); });
// final url = Uri.parse('http://localhost:8000/api/login'); final url = Uri.parse('${AppConfig.baseUrl}/api/login');
final url = Uri.parse('http://localhost:8000/api/login');
try { try {
final response = await http.post( final response = await http.post(
url, url,
headers: {'Content-Type': 'application/json'}, headers: {
'Content-Type': 'application/json',
'X-Platform': 'mobile', // penting!
},
body: jsonEncode({ body: jsonEncode({
'email': _emailController.text.trim(), 'email': _emailController.text.trim(),
'password': _passwordController.text, 'password': _passwordController.text,
}), }),
); );
// final response = await http.post(
// url,
// headers: {'Content-Type': 'application/json'},
// body: jsonEncode({
// 'email': _emailController.text.trim(),
// 'password': _passwordController.text,
// }),
// );
final data = jsonDecode(response.body); final data = jsonDecode(response.body);
if (response.statusCode == 200) { if (response.statusCode == 200) {
// Login berhasil // Login berhasil
@ -79,12 +91,31 @@ class _LoginScreenState extends State<LoginScreen> {
} }
} }
// Future<String> fetchUserNameByToken(String token) async {
// final url = Uri.parse('http://192.168.1.130:8000/api/user-by-token/$token');
// final response = await http.get(
// url,
// headers: {'Authorization': 'Bearer $token', 'Accept': 'application/json'},
// );
// if (response.statusCode == 200) {
// final data = jsonDecode(response.body);
// return data['data']?['name'] ?? '';
// } else {
// return '';
// }
// }
Future<String> fetchUserNameByToken(String token) async { Future<String> fetchUserNameByToken(String token) async {
final url = Uri.parse('http://localhost:8000/api/user-by-token/$token'); final url = Uri.parse('${AppConfig.baseUrl}/api/user-by-token/$token');
final response = await http.get( final response = await http.get(
url, url,
headers: {'Authorization': 'Bearer $token', 'Accept': 'application/json'}, headers: {
'Authorization': 'Bearer $token',
'Accept': 'application/json',
'X-Platform': 'mobile',
},
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
final data = jsonDecode(response.body); final data = jsonDecode(response.body);
return data['data']?['name'] ?? ''; return data['data']?['name'] ?? '';
@ -214,30 +245,30 @@ class _LoginScreenState extends State<LoginScreen> {
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Row( // Row(
mainAxisAlignment: MainAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center,
children: [ // children: [
const Text("Don't have an account? "), // const Text("Don't have an account? "),
GestureDetector( // GestureDetector(
onTap: () { // onTap: () {
Navigator.push( // Navigator.push(
context, // context,
MaterialPageRoute( // MaterialPageRoute(
builder: (context) => const RegisterScreen(), // builder: (context) => const RegisterScreen(),
), // ),
); // );
}, // },
child: const Text( // child: const Text(
'Sign up', // 'Sign up',
style: TextStyle( // style: TextStyle(
color: Colors.blue, // color: Colors.blue,
fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
), // ),
), // ),
), // ),
], // ],
), // ),
const SizedBox(height: 24), // const SizedBox(height: 24),
], ],
), ),
), ),

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:absen/config/config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -13,7 +14,7 @@ import 'riwayat_screen.dart';
// - iOS simulator: 'http://localhost:8000' // - iOS simulator: 'http://localhost:8000'
// - Perangkat nyata: 'http://<IP_PC>:8000' // - Perangkat nyata: 'http://<IP_PC>:8000'
// Untuk Web, gunakan host yang bisa diakses browser. // Untuk Web, gunakan host yang bisa diakses browser.
const String baseUrl = 'http://localhost:8000'; const String baseUrl = '${AppConfig.baseUrl}';
class ProfileScreen extends StatefulWidget { class ProfileScreen extends StatefulWidget {
final String token; final String token;

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:absen/config/config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -24,7 +25,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
setState(() { setState(() {
_isLoading = true; _isLoading = true;
}); });
final url = Uri.parse('http://localhost:8000/api/register'); final url = Uri.parse('${AppConfig.baseUrl}/api/register');
// Ganti jika base URL berbeda // Ganti jika base URL berbeda
try { try {
final response = await http.post( final response = await http.post(

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:absen/config/config.dart';
import 'package:absen/profile_screen.dart'; import 'package:absen/profile_screen.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -32,7 +33,7 @@ class _RiwayatScreenState extends State<RiwayatScreen>
Future<List<Map<String, dynamic>>> fetchAbsensi() async { Future<List<Map<String, dynamic>>> fetchAbsensi() async {
String urlStr = String urlStr =
'http://localhost:8000/api/employee/attendance/history?length=20&start=0'; '${AppConfig.baseUrl}/api/employee/attendance/history?length=20&start=0';
if (_startDate != null) { if (_startDate != null) {
urlStr += '&start_date=${DateFormat('yyyy-MM-dd').format(_startDate!)}'; urlStr += '&start_date=${DateFormat('yyyy-MM-dd').format(_startDate!)}';
} }
@ -57,7 +58,7 @@ class _RiwayatScreenState extends State<RiwayatScreen>
Future<List<Map<String, dynamic>>> fetchPermission() async { Future<List<Map<String, dynamic>>> fetchPermission() async {
String urlStr = String urlStr =
'http://localhost:8000/api/employee/permission/history?length=20&start=0'; '${AppConfig.baseUrl}/api/employee/permission/history?length=20&start=0';
if (_startDate != null) { if (_startDate != null) {
urlStr += '&start_date=${DateFormat('yyyy-MM-dd').format(_startDate!)}'; urlStr += '&start_date=${DateFormat('yyyy-MM-dd').format(_startDate!)}';
} }
@ -294,6 +295,41 @@ class _RiwayatScreenState extends State<RiwayatScreen>
itemCount: data.length, itemCount: data.length,
itemBuilder: (context, i) { itemBuilder: (context, i) {
final item = data[i]; final item = data[i];
final status = item['status'] ?? '-';
late Color statusColor;
late String statusText;
switch (status) {
case 'pending':
statusColor = const Color(0xFFFFC542);
statusText = 'Menunggu';
break;
case 'accepted':
statusColor = const Color(0xFF1BCFB4);
statusText = 'Diterima';
break;
case 'jam_kerja_kurang':
statusColor = const Color(0xFFE57373);
statusText = 'Jam Kerja Kurang';
break;
case 'lembur':
statusColor = const Color(0xFF4F8DFD);
statusText = 'Lembur';
break;
case 'late':
statusColor = const Color(0xFFFF7043);
statusText = 'Terlambat';
break;
case 'rejected':
statusColor = const Color(0xFFD32F2F);
statusText = 'Ditolak';
break;
default:
statusColor = const Color(0xFF9E9E9E);
statusText =
status[0].toUpperCase() + status.substring(1);
}
return Card( return Card(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -317,29 +353,13 @@ class _RiwayatScreenState extends State<RiwayatScreen>
vertical: 6, vertical: 6,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: statusColor.withOpacity(0.15),
item['status'] == 'accepted'
? const Color(
0xFF1BCFB4,
).withOpacity(0.15)
: (item['status'] == 'pending'
? const Color(
0xFFFFC542,
).withOpacity(0.15)
: const Color(
0xFFE57373,
).withOpacity(0.15)),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Text( child: Text(
item['status'] ?? '-', statusText,
style: TextStyle( style: TextStyle(
color: color: statusColor,
item['status'] == 'accepted'
? const Color(0xFF1BCFB4)
: (item['status'] == 'pending'
? const Color(0xFFFFC542)
: const Color(0xFFE57373)),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),

View File

@ -1,6 +1,22 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
archive:
dependency: transitive
description:
name: archive
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
url: "https://pub.dev"
source: hosted
version: "4.0.7"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -25,6 +41,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -158,6 +190,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
url: "https://pub.dev"
source: hosted
version: "0.14.4"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -204,10 +244,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: geolocator_android name: geolocator_android
sha256: "114072db5d1dce0ec0b36af2697f55c133bc89a2c8dd513e137c0afe59696ed4" sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.1+1" version: "5.0.2"
geolocator_apple: geolocator_apple:
dependency: transitive dependency: transitive
description: description:
@ -260,10 +300,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_android name: google_maps_flutter_android
sha256: ab83128296fbeaa52e8f2b3bf53bcd895e64778edddcdc07bc8f33f4ea78076c sha256: "356ee9c65f38a104f7c4988e6952e52addb3b6cb1601839dd2010d7a502afcf0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.16.1" version: "2.16.2"
google_maps_flutter_ios: google_maps_flutter_ios:
dependency: transitive dependency: transitive
description: description:
@ -284,10 +324,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_web name: google_maps_flutter_web
sha256: a9822dbf31a3f76f239f6bda346511b051d3edf739806e9091be2729e8b645de sha256: ce2cac714e5462bf761ff2fdfc3564c7e5d7ed0578268dccb0a54dbdb1e6214e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.12+1" version: "0.5.12+2"
html: html:
dependency: transitive dependency: transitive
description: description:
@ -312,6 +352,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
image:
dependency: transitive
description:
name: image
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
url: "https://pub.dev"
source: hosted
version: "4.5.4"
image_picker: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -384,6 +432,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0" version: "0.19.0"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
latlong2: latlong2:
dependency: "direct main" dependency: "direct main"
description: description:
@ -436,10 +492,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: logger name: logger
sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.6.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -528,6 +584,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -552,6 +616,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
posix:
dependency: transitive
description:
name: posix
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
proj4dart: proj4dart:
dependency: transitive dependency: transitive
description: description:
@ -765,6 +837,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks: sdks:
dart: ">=3.7.0 <4.0.0" dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.0" flutter: ">=3.27.0"

View File

@ -51,6 +51,7 @@ dependencies:
# web # web
dev_dependencies: dev_dependencies:
flutter_launcher_icons: ^0.14.4
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@ -62,6 +63,10 @@ dev_dependencies:
flutter_lints: ^5.0.0 flutter_lints: ^5.0.0
http: ^1.4.0 http: ^1.4.0
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/logom2.png"
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
@ -73,6 +78,7 @@ flutter:
uses-material-design: true uses-material-design: true
assets: assets:
- assets/logom.png - assets/logom.png
- assets/logom2.jpg
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: # assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg