727 lines
26 KiB
Dart
727 lines
26 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
import '../../../services/auth_service.dart';
|
|
|
|
class DataPetugasScreen extends StatefulWidget {
|
|
const DataPetugasScreen({super.key});
|
|
|
|
@override
|
|
State<DataPetugasScreen> createState() => _DataPetugasScreenState();
|
|
}
|
|
|
|
class _DataPetugasScreenState extends State<DataPetugasScreen> {
|
|
List petugas = [];
|
|
List filteredPetugas = [];
|
|
bool isLoading = true;
|
|
bool isSubmitting = false;
|
|
String? userRole;
|
|
|
|
final nameController = TextEditingController();
|
|
final emailController = TextEditingController();
|
|
final passwordController = TextEditingController();
|
|
final noHpController = TextEditingController();
|
|
final searchController = TextEditingController();
|
|
|
|
String searchQuery = "";
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
getUserRole();
|
|
fetchPetugas();
|
|
}
|
|
|
|
Future<void> getUserRole() async {
|
|
userRole = await AuthService.getRole();
|
|
if (mounted) setState(() {});
|
|
}
|
|
|
|
// ================= ROLE BADGE =================
|
|
Widget roleBadge(String role) {
|
|
Color color;
|
|
if (role == "super_admin") {
|
|
color = Colors.red;
|
|
} else if (role == "admin") {
|
|
color = Colors.blue;
|
|
} else {
|
|
color = Colors.green;
|
|
}
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Text(
|
|
role.toUpperCase(),
|
|
style: TextStyle(
|
|
color: color,
|
|
fontSize: 9,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ================= SEARCH =================
|
|
void filterPetugas(String query) {
|
|
searchQuery = query;
|
|
if (query.isEmpty) {
|
|
filteredPetugas = petugas;
|
|
} else {
|
|
filteredPetugas = petugas.where((p) {
|
|
final name = (p['name'] ?? "").toLowerCase();
|
|
final email = (p['email'] ?? "").toLowerCase();
|
|
final q = query.toLowerCase();
|
|
return name.contains(q) || email.contains(q);
|
|
}).toList();
|
|
}
|
|
|
|
// Tetap sorted setelah filter
|
|
filteredPetugas.sort((a, b) {
|
|
const roleOrder = {'super_admin': 0, 'admin': 1, 'petugas': 2};
|
|
final roleA = roleOrder[a['role']] ?? 3;
|
|
final roleB = roleOrder[b['role']] ?? 3;
|
|
if (roleA != roleB) return roleA.compareTo(roleB);
|
|
return (a['name'] ?? '').toLowerCase()
|
|
.compareTo((b['name'] ?? '').toLowerCase());
|
|
});
|
|
|
|
setState(() {});
|
|
}
|
|
|
|
// ================= API CALLS =================
|
|
Future<void> fetchPetugas() async {
|
|
setState(() => isLoading = true);
|
|
String? token = await AuthService.getToken();
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse("${AuthService.baseUrl}/petugas"),
|
|
headers: {
|
|
"Accept": "application/json",
|
|
"Authorization": "Bearer $token",
|
|
},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
|
|
// Sorting: role dulu (super_admin > admin > petugas), lalu nama abjad
|
|
final sorted = List.from(data)..sort((a, b) {
|
|
const roleOrder = {'super_admin': 0, 'admin': 1, 'petugas': 2};
|
|
final roleA = roleOrder[a['role']] ?? 3;
|
|
final roleB = roleOrder[b['role']] ?? 3;
|
|
if (roleA != roleB) return roleA.compareTo(roleB);
|
|
return (a['name'] ?? '').toLowerCase()
|
|
.compareTo((b['name'] ?? '').toLowerCase());
|
|
});
|
|
|
|
setState(() {
|
|
petugas = sorted;
|
|
filteredPetugas = sorted;
|
|
isLoading = false;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> tambahPetugas() async {
|
|
String? token = await AuthService.getToken();
|
|
final response = await http.post(
|
|
Uri.parse("${AuthService.baseUrl}/petugas"),
|
|
headers: {"Accept": "application/json", "Authorization": "Bearer $token"},
|
|
body: {
|
|
"name": nameController.text,
|
|
"email": emailController.text,
|
|
"password": passwordController.text,
|
|
"no_hp": noHpController.text,
|
|
},
|
|
);
|
|
|
|
Navigator.pop(context);
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
_showSnackBar("Petugas berhasil ditambahkan", Colors.green);
|
|
fetchPetugas();
|
|
}
|
|
}
|
|
|
|
Future<void> editPetugas(int id) async {
|
|
String? token = await AuthService.getToken();
|
|
final response = await http.put(
|
|
Uri.parse("${AuthService.baseUrl}/petugas/$id"),
|
|
headers: {"Accept": "application/json", "Authorization": "Bearer $token"},
|
|
body: {
|
|
"name": nameController.text,
|
|
"email": emailController.text,
|
|
"no_hp": noHpController.text,
|
|
},
|
|
);
|
|
|
|
Navigator.pop(context);
|
|
if (response.statusCode == 200) {
|
|
_showSnackBar("Data petugas berhasil diupdate", Colors.blue);
|
|
fetchPetugas();
|
|
}
|
|
}
|
|
|
|
Future<void> hapusPetugas(int id) async {
|
|
String? token = await AuthService.getToken();
|
|
final response = await http.delete(
|
|
Uri.parse("${AuthService.baseUrl}/petugas/$id"),
|
|
headers: {"Accept": "application/json", "Authorization": "Bearer $token"},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
_showSnackBar("Petugas berhasil dihapus", Colors.red);
|
|
fetchPetugas();
|
|
}
|
|
}
|
|
|
|
Future<void> jadikanAdmin(int id) async {
|
|
String? token = await AuthService.getToken();
|
|
final response = await http.put(
|
|
Uri.parse("${AuthService.baseUrl}/jadikan-admin/$id"),
|
|
headers: {"Accept": "application/json", "Authorization": "Bearer $token"},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
_showSnackBar("User berhasil dijadikan Admin", Colors.indigo);
|
|
fetchPetugas();
|
|
}
|
|
}
|
|
|
|
// ================= BARU: TURUNKAN ADMIN KE PETUGAS =================
|
|
Future<void> jadikanPetugas(int id) async {
|
|
String? token = await AuthService.getToken();
|
|
final response = await http.put(
|
|
Uri.parse("${AuthService.baseUrl}/jadikan-petugas/$id"),
|
|
headers: {"Accept": "application/json", "Authorization": "Bearer $token"},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
_showSnackBar("Admin berhasil diturunkan menjadi Petugas", Colors.orange);
|
|
fetchPetugas();
|
|
}
|
|
}
|
|
|
|
void _showSnackBar(String msg, Color color) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(msg),
|
|
backgroundColor: color,
|
|
behavior: SnackBarBehavior.floating,
|
|
),
|
|
);
|
|
}
|
|
|
|
// ================= HIGHLIGHT TEXT =================
|
|
Widget highlightText(String text, {bool isTitle = false}) {
|
|
if (searchQuery.isEmpty) {
|
|
return Text(
|
|
text,
|
|
style: TextStyle(
|
|
fontSize: isTitle ? 16 : 13,
|
|
fontWeight: isTitle ? FontWeight.bold : FontWeight.normal,
|
|
color: isTitle ? Colors.black87 : Colors.grey[600],
|
|
),
|
|
);
|
|
}
|
|
|
|
final lowerText = text.toLowerCase();
|
|
final lowerQuery = searchQuery.toLowerCase();
|
|
|
|
if (!lowerText.contains(lowerQuery)) {
|
|
return Text(
|
|
text,
|
|
style: TextStyle(
|
|
fontSize: isTitle ? 16 : 13,
|
|
fontWeight: isTitle ? FontWeight.bold : FontWeight.normal,
|
|
color: isTitle ? Colors.black87 : Colors.grey[600],
|
|
),
|
|
);
|
|
}
|
|
|
|
final start = lowerText.indexOf(lowerQuery);
|
|
final end = start + lowerQuery.length;
|
|
|
|
return RichText(
|
|
text: TextSpan(
|
|
style: TextStyle(
|
|
fontSize: isTitle ? 16 : 13,
|
|
fontWeight: isTitle ? FontWeight.bold : FontWeight.normal,
|
|
color: isTitle ? Colors.black87 : Colors.grey[600],
|
|
),
|
|
children: [
|
|
TextSpan(text: text.substring(0, start)),
|
|
TextSpan(
|
|
text: text.substring(start, end),
|
|
style: const TextStyle(
|
|
backgroundColor: Colors.yellow,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
TextSpan(text: text.substring(end)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ================= DIALOGS =================
|
|
void konfirmasiHapus(int id) {
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(_) => AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
),
|
|
title: const Text("Hapus Petugas?"),
|
|
content: const Text("Data yang dihapus tidak dapat dikembalikan."),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text("Batal"),
|
|
),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
hapusPetugas(id);
|
|
},
|
|
child: const Text(
|
|
"Hapus",
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void konfirmasiJadikanAdmin(int id, String name) {
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(_) => AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
),
|
|
title: const Text("Jadikan Admin?"),
|
|
content: Text(
|
|
"Apakah Anda yakin ingin memberikan akses Admin kepada $name?",
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text("Batal"),
|
|
),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
jadikanAdmin(id);
|
|
},
|
|
child: const Text(
|
|
"Ya, Setujui",
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ================= BARU: KONFIRMASI TURUNKAN KE PETUGAS =================
|
|
void konfirmasiJadikanPetugas(int id, String name) {
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(_) => AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
),
|
|
title: const Text("Turunkan ke Petugas?"),
|
|
content: Text(
|
|
"Apakah Anda yakin ingin mencabut akses Admin dari $name dan menjadikannya Petugas?",
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text("Batal"),
|
|
),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.orange),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
jadikanPetugas(id);
|
|
},
|
|
child: const Text(
|
|
"Ya, Turunkan",
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void showForm({Map? data}) {
|
|
if (data != null) {
|
|
nameController.text = data['name'];
|
|
emailController.text = data['email'];
|
|
noHpController.text = data['no_hp'] ?? "";
|
|
} else {
|
|
nameController.clear();
|
|
emailController.clear();
|
|
passwordController.clear();
|
|
noHpController.clear();
|
|
}
|
|
|
|
bool _obscurePassword = true;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(_) => StatefulBuilder(
|
|
builder:
|
|
(context, setStateDialog) => AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
title: Text(data == null ? "Tambah Petugas" : "Edit Petugas"),
|
|
content: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextField(
|
|
controller: nameController,
|
|
decoration: InputDecoration(
|
|
labelText: "Nama",
|
|
prefixIcon: const Icon(Icons.person),
|
|
filled: true,
|
|
fillColor: Colors.grey[100],
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: emailController,
|
|
decoration: InputDecoration(
|
|
labelText: "Email",
|
|
prefixIcon: const Icon(Icons.email),
|
|
filled: true,
|
|
fillColor: Colors.grey[100],
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
),
|
|
),
|
|
if (data == null) ...[
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: passwordController,
|
|
obscureText: _obscurePassword,
|
|
decoration: InputDecoration(
|
|
labelText: "Password",
|
|
prefixIcon: const Icon(Icons.lock),
|
|
filled: true,
|
|
fillColor: Colors.grey[100],
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
suffixIcon: IconButton(
|
|
icon: Icon(
|
|
_obscurePassword
|
|
? Icons.visibility_off
|
|
: Icons.visibility,
|
|
color: Colors.grey,
|
|
),
|
|
onPressed: () => setStateDialog(
|
|
() => _obscurePassword = !_obscurePassword,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: noHpController,
|
|
decoration: InputDecoration(
|
|
labelText: "No HP",
|
|
prefixIcon: const Icon(Icons.phone),
|
|
filled: true,
|
|
fillColor: Colors.grey[100],
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF2F5BEA),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
),
|
|
onPressed:
|
|
isSubmitting
|
|
? null
|
|
: () {
|
|
if (data == null) {
|
|
tambahPetugas();
|
|
} else {
|
|
editPetugas(data['id']);
|
|
}
|
|
},
|
|
child:
|
|
isSubmitting
|
|
? const SizedBox(
|
|
height: 20,
|
|
width: 20,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: Colors.white,
|
|
),
|
|
)
|
|
: const Text(
|
|
"Simpan",
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFF8F9FD),
|
|
appBar: AppBar(
|
|
title: const Text(
|
|
"Data Petugas",
|
|
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
|
|
),
|
|
backgroundColor: const Color(0xFF2F5BEA),
|
|
elevation: 0,
|
|
centerTitle: true,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)),
|
|
),
|
|
),
|
|
floatingActionButton: FloatingActionButton(
|
|
backgroundColor: const Color(0xFF2F5BEA),
|
|
onPressed: () => showForm(),
|
|
child: const Icon(Icons.add, color: Colors.white),
|
|
),
|
|
body:
|
|
isLoading
|
|
? const Center(
|
|
child: CircularProgressIndicator(color: Color(0xFF2F5BEA)),
|
|
)
|
|
: Column(
|
|
children: [
|
|
/// --- SEARCH BAR ---
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: TextField(
|
|
controller: searchController,
|
|
onChanged: filterPetugas,
|
|
decoration: InputDecoration(
|
|
hintText: "Cari nama atau email...",
|
|
prefixIcon: const Icon(
|
|
Icons.search,
|
|
color: Color(0xFF2F5BEA),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
contentPadding: EdgeInsets.zero,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
/// --- LIST PETUGAS ---
|
|
Expanded(
|
|
child: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
itemCount: filteredPetugas.length,
|
|
itemBuilder: (context, index) {
|
|
final p = filteredPetugas[index];
|
|
final role = p['role'] ?? "petugas";
|
|
final isSuperAdmin = userRole == "super_admin";
|
|
final isAdmin = role == "admin";
|
|
final isTargetSuperAdmin = role == "super_admin";
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.04),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Row(
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 25,
|
|
backgroundColor: const Color.fromARGB(
|
|
255,
|
|
31,
|
|
57,
|
|
141,
|
|
).withOpacity(0.1),
|
|
backgroundImage: _getFoto(p['foto']),
|
|
child:
|
|
_getFoto(p['foto']) == null
|
|
? Text(
|
|
p['name'] != null
|
|
? p['name'][0].toUpperCase()
|
|
: "?",
|
|
style: const TextStyle(
|
|
color: Color(0xFF2F5BEA),
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 18,
|
|
),
|
|
)
|
|
: null,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
highlightText(
|
|
p['name'] ?? "-",
|
|
isTitle: true,
|
|
),
|
|
const SizedBox(height: 2),
|
|
highlightText(p['email'] ?? "-"),
|
|
const SizedBox(height: 2),
|
|
highlightText(p['no_hp'] ?? "-"),
|
|
const SizedBox(height: 8),
|
|
roleBadge(role),
|
|
],
|
|
),
|
|
),
|
|
Row(
|
|
children: [
|
|
// Tombol Edit: semua bisa edit kecuali super_admin tidak bisa edit super_admin lain
|
|
if (!isTargetSuperAdmin)
|
|
_actionIcon(
|
|
Icons.edit_outlined,
|
|
Colors.orange,
|
|
() => showForm(data: p),
|
|
),
|
|
|
|
// Tombol Hapus:
|
|
// - super_admin bisa hapus siapa saja (kecuali super_admin lain)
|
|
// - admin/petugas hanya bisa hapus petugas (bukan admin/super_admin)
|
|
if (!isTargetSuperAdmin &&
|
|
(isSuperAdmin || (!isAdmin)))
|
|
_actionIcon(
|
|
Icons.delete_outline,
|
|
Colors.red,
|
|
() => konfirmasiHapus(p['id']),
|
|
),
|
|
|
|
// Tombol Jadikan Admin (hanya super_admin, untuk role petugas)
|
|
if (isSuperAdmin &&
|
|
role == "petugas")
|
|
_actionIcon(
|
|
Icons.admin_panel_settings_outlined,
|
|
Colors.blue,
|
|
() => konfirmasiJadikanAdmin(
|
|
p['id'],
|
|
p['name'] ?? "Petugas",
|
|
),
|
|
),
|
|
|
|
// ===== BARU: Tombol Turunkan ke Petugas (hanya super_admin, untuk role admin) =====
|
|
if (isSuperAdmin && isAdmin)
|
|
_actionIcon(
|
|
Icons.person_remove_outlined,
|
|
Colors.orange,
|
|
() => konfirmasiJadikanPetugas(
|
|
p['id'],
|
|
p['name'] ?? "Admin",
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _actionIcon(IconData icon, Color color, VoidCallback onTap) {
|
|
return Container(
|
|
margin: const EdgeInsets.only(left: 6),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: IconButton(
|
|
constraints: const BoxConstraints(),
|
|
padding: const EdgeInsets.all(8),
|
|
icon: Icon(icon, color: color, size: 20),
|
|
onPressed: onTap,
|
|
),
|
|
);
|
|
}
|
|
|
|
ImageProvider? _getFoto(String? foto) {
|
|
if (foto == null || foto.isEmpty) return null;
|
|
|
|
if (foto.startsWith("http")) {
|
|
return NetworkImage(foto);
|
|
}
|
|
|
|
return NetworkImage("${AuthService.baseUrl}/$foto");
|
|
}
|
|
} |