TTK_E32222585_flutter/lib/profile_screen.dart

420 lines
14 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'dashboard_screen.dart';
import 'login_screen.dart'; // pastikan ada LoginScreen
import 'riwayat_screen.dart';
// Sesuaikan baseUrl sesuai environment:
// - Android emulator: 'http://10.0.2.2:8000'
// - 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';
class ProfileScreen extends StatefulWidget {
final String token;
const ProfileScreen({Key? key, required this.token}) : super(key: key);
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
late Future<Map<String, dynamic>?> futureProfile;
@override
void initState() {
super.initState();
futureProfile = fetchProfile();
}
Future<Map<String, dynamic>?> fetchProfile() async {
final url = Uri.parse('$baseUrl/api/employee/profile');
try {
final response = await http.get(
url,
headers: {
'Authorization': 'Bearer ${widget.token}',
'Accept': 'application/json',
},
);
if (response.statusCode == 200) {
final body = jsonDecode(response.body);
return body['data'] as Map<String, dynamic>?;
} else {
return null;
}
} catch (_) {
return null;
}
}
void _refresh() {
setState(() {
futureProfile = fetchProfile();
});
}
Future<void> _logout() async {
final uri = Uri.parse('$baseUrl/api/logout');
try {
final resp = await http.post(
uri,
headers: {
'Authorization': 'Bearer ${widget.token}',
'Accept': 'application/json',
},
);
if (resp.statusCode == 200) {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('token');
if (!mounted) return;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const LoginScreen()),
(route) => false,
);
} else {
String msg = 'Gagal logout';
try {
final b = jsonDecode(resp.body);
msg = b['message'] ?? msg;
} catch (_) {}
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(msg)));
}
} catch (e) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error saat logout: $e')));
}
}
@override
Widget build(BuildContext context) {
return FutureBuilder<Map<String, dynamic>?>(
future: futureProfile,
builder: (context, snapshot) {
// Loading
if (snapshot.connectionState == ConnectionState.waiting) {
return Scaffold(
backgroundColor: const Color(0xFFF7F9FB),
appBar: AppBar(
title: const Text('Profil Saya'),
backgroundColor: const Color(0xFF4F8DFD),
elevation: 0,
centerTitle: true,
),
body: const Center(child: CircularProgressIndicator()),
);
}
// Error atau null
if (snapshot.hasError || snapshot.data == null) {
return Scaffold(
backgroundColor: const Color(0xFFF7F9FB),
appBar: AppBar(
title: const Text('Profil Saya'),
backgroundColor: const Color(0xFF4F8DFD),
elevation: 0,
centerTitle: true,
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Gagal memuat data profil'),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _refresh,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF4F8DFD),
),
child: const Text('Coba Lagi'),
),
],
),
),
);
}
// Data tersedia
final user = snapshot.data!;
final profile = user['profile'] as Map<String, dynamic>? ?? {};
final name = (user['name'] ?? '-').toString();
final email = (user['email'] ?? '-').toString();
final phone = (profile['phone_number'] ?? '-').toString();
final nip = (profile['nip'] ?? '-').toString();
final position = (profile['position'] ?? '-').toString();
final photoPath = profile['profile_photo'];
final photoUrl =
photoPath != null ? '$baseUrl/storage/$photoPath' : null;
return Scaffold(
backgroundColor: const Color(0xFFF7F9FB),
appBar: AppBar(
title: const Text('Profil Saya'),
backgroundColor: const Color(0xFF4F8DFD),
elevation: 0,
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
// Header: avatar + nama (tanpa tombol upload)
Container(
width: double.infinity,
decoration: const BoxDecoration(
color: Color(0xFF4F8DFD),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
child: Column(
children: [
const SizedBox(height: 40),
CircleAvatar(
radius: 40,
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 36,
backgroundImage:
photoUrl != null
? NetworkImage(photoUrl)
: const AssetImage('assets/logom.png')
as ImageProvider,
),
),
const SizedBox(height: 12),
Text(
name,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: Colors.white,
),
),
const SizedBox(height: 24),
],
),
),
const SizedBox(height: 18),
// Informasi Pribadi tanpa edit
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Informasi Pribadi',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
const SizedBox(height: 12),
_ProfileInfoRow(label: 'Nama', value: name),
_ProfileInfoRow(label: 'Email', value: email),
_ProfileInfoRow(label: 'No. Telepon', value: phone),
_ProfileInfoRow(label: 'NIP', value: nip),
_ProfileInfoRow(label: 'Jabatan', value: position),
],
),
),
),
const SizedBox(height: 18),
// Menu hanya logout
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
children: [
_ProfileMenuItem(
icon: Icons.logout,
label: 'Keluar',
color: Colors.red,
onTap: () async {
final confirmed =
await showDialog<bool>(
context: context,
builder:
(_) => AlertDialog(
title: const Text('Logout'),
content: const Text(
'Yakin ingin logout?',
),
actions: [
TextButton(
onPressed:
() =>
Navigator.pop(context, false),
child: const Text('Batal'),
),
TextButton(
onPressed:
() =>
Navigator.pop(context, true),
child: const Text('Ya'),
),
],
),
) ??
false;
if (confirmed) {
await _logout();
}
},
),
],
),
),
const SizedBox(height: 24),
],
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: 2,
selectedItemColor: const Color(0xFF4F8DFD),
unselectedItemColor: Colors.black38,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home_rounded),
label: 'Beranda',
),
BottomNavigationBarItem(
icon: Icon(Icons.history),
label: 'Riwayat',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profil',
),
],
type: BottomNavigationBarType.fixed,
onTap: (index) {
if (index == 2) return;
if (index == 0) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder:
(context) => DashboardScreen(
userName: name,
token: widget.token,
),
),
(route) => false,
);
}
if (index == 1) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder:
(context) =>
RiwayatScreen(userName: name, token: widget.token),
),
(route) => false,
);
}
},
),
);
},
);
}
}
class _ProfileInfoRow extends StatelessWidget {
final String label;
final String value;
const _ProfileInfoRow({required this.label, required this.value, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
label,
style: const TextStyle(color: Colors.black54, fontSize: 13),
),
),
Expanded(
child: Text(
value,
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 13),
),
),
],
),
);
}
}
class _ProfileMenuItem extends StatelessWidget {
final IconData icon;
final String label;
final VoidCallback onTap;
final Color? color;
const _ProfileMenuItem({
required this.icon,
required this.label,
required this.onTap,
this.color,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: Color(0xFFF0F0F0))),
),
child: Row(
children: [
Icon(icon, color: color ?? const Color(0xFF4F8DFD)),
const SizedBox(width: 16),
Expanded(
child: Text(
label,
style: TextStyle(
color: color ?? Colors.black87,
fontWeight: FontWeight.w500,
fontSize: 15,
),
),
),
const Icon(Icons.chevron_right, color: Color(0xFFB0B0B0)),
],
),
),
);
}
}