Flutter / Mobile
This commit is contained in:
parent
9fe873ccb2
commit
dbcd1755f5
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"cmake.sourceDirectory": "C:/Users/Romi/Documents/GitHub/absen/linux"
|
||||
"cmake.sourceDirectory": "C:/Users/Romi/Documents/TA/mobileabsen/linux"
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
import java.util.Properties
|
||||
import java.io.FileInputStream
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
|
@ -5,6 +8,12 @@ plugins {
|
|||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
val keystoreProperties = Properties()
|
||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.absen"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
|
@ -30,11 +39,21 @@ android {
|
|||
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 {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// 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 {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// ...
|
||||
implementation("com.google.android.material:material:1.8.0")
|
||||
// ...
|
||||
}
|
|
@ -1,54 +1,49 @@
|
|||
<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_COARSE_LOCATION"/>
|
||||
<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
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
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>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
|
||||
<!-- Flutter embedding marker -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</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>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
|
|
|
@ -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>
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:absen/config/config.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -70,7 +71,7 @@ class _CheckinPhotoScreenState extends State<CheckinPhotoScreen> {
|
|||
if (!_isPhotoReady()) return;
|
||||
setState(() => _isLoading = true);
|
||||
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 =
|
||||
http.MultipartRequest('POST', uri)
|
||||
..headers['Authorization'] = 'Bearer $token'
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'dart:convert';
|
||||
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:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:absen/models/attendance_area.dart';
|
||||
|
||||
import 'checkin_photo_screen.dart';
|
||||
|
||||
|
@ -47,7 +48,6 @@ class _CheckinScreenState extends State<CheckinScreen> {
|
|||
);
|
||||
return;
|
||||
}
|
||||
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
|
@ -64,7 +64,6 @@ class _CheckinScreenState extends State<CheckinScreen> {
|
|||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Position? pos;
|
||||
try {
|
||||
pos = await Geolocator.getCurrentPosition(
|
||||
|
@ -98,9 +97,7 @@ class _CheckinScreenState extends State<CheckinScreen> {
|
|||
}
|
||||
|
||||
Future<void> _fetchAttendanceAreas() async {
|
||||
final uri = Uri.parse(
|
||||
'http://localhost:8000/api/employee/attendance/areas',
|
||||
);
|
||||
final uri = Uri.parse('${AppConfig.baseUrl}/api/employee/attendance/areas');
|
||||
final token = widget.token;
|
||||
try {
|
||||
final resp = await http.get(
|
||||
|
@ -154,7 +151,7 @@ class _CheckinScreenState extends State<CheckinScreen> {
|
|||
if (_position == null) return;
|
||||
setState(() => _isLoading = true);
|
||||
final uri = Uri.parse(
|
||||
'http://localhost:8000/api/employee/attendance/check-location',
|
||||
'${AppConfig.baseUrl}/api/employee/attendance/check-location',
|
||||
);
|
||||
try {
|
||||
final response = await http.post(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:absen/config/config.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -70,7 +71,7 @@ class _CheckoutPhotoScreenState extends State<CheckoutPhotoScreen> {
|
|||
if (!_isPhotoReady()) return;
|
||||
setState(() => _isLoading = true);
|
||||
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 =
|
||||
http.MultipartRequest('POST', uri)
|
||||
..headers['Authorization'] = 'Bearer $token'
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'dart:convert';
|
||||
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:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:absen/models/attendance_area.dart';
|
||||
|
||||
import 'checkout_photo_screen.dart';
|
||||
|
||||
|
@ -43,7 +44,7 @@ class _CheckoutScreenState extends State<CheckoutScreen> {
|
|||
Future<void> _checkTodayStatus() async {
|
||||
setState(() => _checkedStatus = false);
|
||||
final url = Uri.parse(
|
||||
'http://localhost:8000/api/employee/attendance/today-status',
|
||||
'${AppConfig.baseUrl}/api/employee/attendance/today-status',
|
||||
);
|
||||
try {
|
||||
final resp = await http.get(
|
||||
|
@ -69,9 +70,7 @@ class _CheckoutScreenState extends State<CheckoutScreen> {
|
|||
}
|
||||
|
||||
Future<void> _fetchAttendanceAreas() async {
|
||||
final uri = Uri.parse(
|
||||
'http://localhost:8000/api/employee/attendance/areas',
|
||||
);
|
||||
final uri = Uri.parse('${AppConfig.baseUrl}/api/employee/attendance/areas');
|
||||
// Asumsikan endpoint mengembalikan JSON array: [ { center_lat, center_lng, radius }, ... ]
|
||||
try {
|
||||
final resp = await http.get(
|
||||
|
@ -203,7 +202,7 @@ class _CheckoutScreenState extends State<CheckoutScreen> {
|
|||
}
|
||||
setState(() => _isLoading = true);
|
||||
final uri = Uri.parse(
|
||||
'http://localhost:8000/api/employee/attendance/check-location',
|
||||
'${AppConfig.baseUrl}/api/employee/attendance/check-location',
|
||||
);
|
||||
try {
|
||||
final resp = await http.post(
|
||||
|
@ -374,7 +373,6 @@ class _CheckoutScreenState extends State<CheckoutScreen> {
|
|||
}
|
||||
},
|
||||
myLocationEnabled: true,
|
||||
myLocationButtonEnabled: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
class AppConfig {
|
||||
static const String baseUrl = 'http://192.168.1.6:8000';
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:absen/config/config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.dart';
|
||||
|
@ -18,7 +19,7 @@ class DashboardScreen extends StatelessWidget {
|
|||
|
||||
Future<Map<String, dynamic>> fetchTodayStatus(String token) async {
|
||||
final url = Uri.parse(
|
||||
'http://localhost:8000/api/employee/attendance/today-status',
|
||||
'${AppConfig.baseUrl}/api/employee/attendance/today-status',
|
||||
);
|
||||
final response = await http.get(
|
||||
url,
|
||||
|
@ -33,7 +34,7 @@ class DashboardScreen extends StatelessWidget {
|
|||
|
||||
Future<List<Map<String, dynamic>>> fetchLastActivities(String token) async {
|
||||
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(
|
||||
url,
|
||||
|
@ -54,7 +55,7 @@ class DashboardScreen extends StatelessWidget {
|
|||
final todayStr =
|
||||
"${today.year.toString().padLeft(4, '0')}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}";
|
||||
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(
|
||||
url,
|
||||
|
@ -237,14 +238,14 @@ class DashboardScreen extends StatelessWidget {
|
|||
now.month,
|
||||
now.day,
|
||||
8,
|
||||
30,
|
||||
00,
|
||||
);
|
||||
final endTime = DateTime(
|
||||
now.year,
|
||||
now.month,
|
||||
now.day,
|
||||
17,
|
||||
30,
|
||||
00,
|
||||
);
|
||||
DateTime? checkInTime;
|
||||
DateTime? checkOutTime;
|
||||
|
@ -433,20 +434,43 @@ class DashboardScreen extends StatelessWidget {
|
|||
type == 'in' ? 'Check-in' : 'Check-out';
|
||||
final date = item['date'] ?? '';
|
||||
final time = item['time'] ?? '';
|
||||
|
||||
final status = item['status'] ?? '-';
|
||||
final statusColor =
|
||||
status == 'accepted'
|
||||
? const Color(0xFF1BCFB4)
|
||||
: (status == 'late'
|
||||
? const Color(0xFFFFC542)
|
||||
: (status == 'pending'
|
||||
? const Color(0xFFFFC542)
|
||||
: const Color(0xFFE57373)));
|
||||
final statusText =
|
||||
status == 'late'
|
||||
? 'Terlambat'
|
||||
: (status[0].toUpperCase() +
|
||||
status.substring(1));
|
||||
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 _ActivityTile(
|
||||
icon: icon,
|
||||
title: title,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:absen/config/config.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
@ -34,6 +35,7 @@ class _IzinScreenState extends State<IzinScreen> {
|
|||
'Cuti Tahunan',
|
||||
'Izin Sakit',
|
||||
'Izin Setengah Hari',
|
||||
'Dinas di Luar',
|
||||
];
|
||||
|
||||
// Pick start/end dates
|
||||
|
@ -95,7 +97,7 @@ class _IzinScreenState extends State<IzinScreen> {
|
|||
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
final uri = Uri.parse('http://localhost:8000/api/employee/permission');
|
||||
final uri = Uri.parse('${AppConfig.baseUrl}/api/employee/permission');
|
||||
final req =
|
||||
http.MultipartRequest('POST', uri)
|
||||
..headers['Authorization'] = 'Bearer ${widget.token}'
|
||||
|
@ -323,7 +325,7 @@ class _IzinScreenState extends State<IzinScreen> {
|
|||
|
||||
// Photo proof
|
||||
const Text(
|
||||
'Foto Bukti (Optional)',
|
||||
'Foto Bukti',
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:absen/config/config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'dashboard_screen.dart';
|
||||
import 'register_screen.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({Key? key}) : super(key: key);
|
||||
|
@ -23,17 +23,29 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
// final url = Uri.parse('http://localhost:8000/api/login');
|
||||
final url = Uri.parse('http://localhost:8000/api/login');
|
||||
final url = Uri.parse('${AppConfig.baseUrl}/api/login');
|
||||
try {
|
||||
final response = await http.post(
|
||||
url,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Platform': 'mobile', // ✅ penting!
|
||||
},
|
||||
body: jsonEncode({
|
||||
'email': _emailController.text.trim(),
|
||||
'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);
|
||||
if (response.statusCode == 200) {
|
||||
// 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 {
|
||||
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(
|
||||
url,
|
||||
headers: {'Authorization': 'Bearer $token', 'Accept': 'application/json'},
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
'Accept': 'application/json',
|
||||
'X-Platform': 'mobile',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
return data['data']?['name'] ?? '';
|
||||
|
@ -214,30 +245,30 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text("Don't have an account? "),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const RegisterScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
'Sign up',
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// const Text("Don't have an account? "),
|
||||
// GestureDetector(
|
||||
// onTap: () {
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => const RegisterScreen(),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// child: const Text(
|
||||
// 'Sign up',
|
||||
// style: TextStyle(
|
||||
// color: Colors.blue,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:absen/config/config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
@ -13,7 +14,7 @@ import 'riwayat_screen.dart';
|
|||
// - iOS simulator: 'http://localhost:8000'
|
||||
// - Perangkat nyata: 'http://<IP_PC>:8000'
|
||||
// Untuk Web, gunakan host yang bisa diakses browser.
|
||||
const String baseUrl = 'http://localhost:8000';
|
||||
const String baseUrl = '${AppConfig.baseUrl}';
|
||||
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
final String token;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:absen/config/config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
|
@ -24,7 +25,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
final url = Uri.parse('http://localhost:8000/api/register');
|
||||
final url = Uri.parse('${AppConfig.baseUrl}/api/register');
|
||||
// Ganti jika base URL berbeda
|
||||
try {
|
||||
final response = await http.post(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:absen/config/config.dart';
|
||||
import 'package:absen/profile_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
@ -32,7 +33,7 @@ class _RiwayatScreenState extends State<RiwayatScreen>
|
|||
|
||||
Future<List<Map<String, dynamic>>> fetchAbsensi() async {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
urlStr += '&start_date=${DateFormat('yyyy-MM-dd').format(_startDate!)}';
|
||||
}
|
||||
|
@ -294,6 +295,41 @@ class _RiwayatScreenState extends State<RiwayatScreen>
|
|||
itemCount: data.length,
|
||||
itemBuilder: (context, 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(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
|
@ -317,29 +353,13 @@ class _RiwayatScreenState extends State<RiwayatScreen>
|
|||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
item['status'] == 'accepted'
|
||||
? const Color(
|
||||
0xFF1BCFB4,
|
||||
).withOpacity(0.15)
|
||||
: (item['status'] == 'pending'
|
||||
? const Color(
|
||||
0xFFFFC542,
|
||||
).withOpacity(0.15)
|
||||
: const Color(
|
||||
0xFFE57373,
|
||||
).withOpacity(0.15)),
|
||||
color: statusColor.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
item['status'] ?? '-',
|
||||
statusText,
|
||||
style: TextStyle(
|
||||
color:
|
||||
item['status'] == 'accepted'
|
||||
? const Color(0xFF1BCFB4)
|
||||
: (item['status'] == 'pending'
|
||||
? const Color(0xFFFFC542)
|
||||
: const Color(0xFFE57373)),
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
|
104
pubspec.lock
104
pubspec.lock
|
@ -1,6 +1,22 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -25,6 +41,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -158,6 +190,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -204,10 +244,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: "114072db5d1dce0ec0b36af2697f55c133bc89a2c8dd513e137c0afe59696ed4"
|
||||
sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.1+1"
|
||||
version: "5.0.2"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -260,10 +300,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: google_maps_flutter_android
|
||||
sha256: ab83128296fbeaa52e8f2b3bf53bcd895e64778edddcdc07bc8f33f4ea78076c
|
||||
sha256: "356ee9c65f38a104f7c4988e6952e52addb3b6cb1601839dd2010d7a502afcf0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.16.1"
|
||||
version: "2.16.2"
|
||||
google_maps_flutter_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -284,10 +324,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: google_maps_flutter_web
|
||||
sha256: a9822dbf31a3f76f239f6bda346511b051d3edf739806e9091be2729e8b645de
|
||||
sha256: ce2cac714e5462bf761ff2fdfc3564c7e5d7ed0578268dccb0a54dbdb1e6214e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.12+1"
|
||||
version: "0.5.12+2"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -312,6 +352,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -384,6 +432,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -436,10 +492,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: logger
|
||||
sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1
|
||||
sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
version: "2.6.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -528,6 +584,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -552,6 +616,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
proj4dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -765,6 +837,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
|
|
@ -51,6 +51,7 @@ dependencies:
|
|||
# web
|
||||
|
||||
dev_dependencies:
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
|
@ -62,6 +63,10 @@ dev_dependencies:
|
|||
flutter_lints: ^5.0.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
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
|
@ -73,6 +78,7 @@ flutter:
|
|||
uses-material-design: true
|
||||
assets:
|
||||
- assets/logom.png
|
||||
- assets/logom2.jpg
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
|
|
Loading…
Reference in New Issue