add new function in diagnosaController
This commit is contained in:
parent
c012b08a5f
commit
d35f64b87e
|
@ -40,7 +40,9 @@ exports.register = async (req, res) => {
|
|||
res.status(500).json({ message: 'Error creating user', error });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Penyimpanan sesi login (in-memory)
|
||||
const activeSessions = {}; // key: user.id, value: true/false
|
||||
|
||||
// Login
|
||||
exports.login = async (req, res) => {
|
||||
|
@ -53,6 +55,11 @@ exports.login = async (req, res) => {
|
|||
return res.status(401).json({ message: "Email atau password salah" });
|
||||
}
|
||||
|
||||
// 🔹 Cek apakah user sudah login di device lain
|
||||
if (activeSessions[user.id]) {
|
||||
return res.status(403).json({ message: "Akun ini sedang digunakan di perangkat lain." });
|
||||
}
|
||||
|
||||
// 🔹 Verifikasi password
|
||||
const isPasswordValid = await argon2.verify(user.password, password);
|
||||
if (!isPasswordValid) {
|
||||
|
@ -66,19 +73,32 @@ exports.login = async (req, res) => {
|
|||
{ expiresIn: process.env.JWT_EXPIRES_IN || '1d' }
|
||||
);
|
||||
|
||||
// 🔹 Tandai user sedang login (aktif)
|
||||
activeSessions[user.id] = true;
|
||||
|
||||
console.log("User ID dari backend:", user.id);
|
||||
|
||||
// 🔹 Kirim response dengan token dan role
|
||||
res.status(200).json({
|
||||
message: "Login berhasil",
|
||||
token,
|
||||
role: user.role // Ini penting untuk Flutter agar bisa menentukan halaman tujuan
|
||||
role: user.role
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Terjadi kesalahan", error });
|
||||
}
|
||||
};
|
||||
|
||||
exports.logout = (req, res) => {
|
||||
const userId = req.user.id; // Ambil dari JWT yang sudah diverifikasi
|
||||
|
||||
// Hapus sesi aktif
|
||||
delete activeSessions[userId];
|
||||
|
||||
res.status(200).json({ message: "Logout berhasil" });
|
||||
};
|
||||
|
||||
// Buat transporter Nodemailer dengan Gmail
|
||||
const createGmailTransporter = () => {
|
||||
return nodemailer.createTransport({
|
||||
|
|
|
@ -76,6 +76,8 @@ router.post('/register', authController.register);
|
|||
* description: Login berhasil
|
||||
* 401:
|
||||
* description: Password salah
|
||||
* 403:
|
||||
* description: Akun sedang digunakan di perangkat lain
|
||||
* 404:
|
||||
* description: User tidak ditemukan
|
||||
* 500:
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 289 KiB |
|
@ -34,17 +34,24 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
});
|
||||
|
||||
// Dapatkan semua histori terlebih dahulu
|
||||
final allHistori = await apiService.getAllHistori();
|
||||
final allHistori = await apiService.getAllHistori(); // Kumpulkan semua userIds yang unik
|
||||
Set<String> uniqueUserIds = allHistori
|
||||
.where((histori) => histori['userId'] != null)
|
||||
.map((histori) => histori['userId'].toString())
|
||||
.toSet();
|
||||
|
||||
// Kumpulkan semua hasil fetchHistoriDenganDetail untuk setiap user
|
||||
// Jalankan semua fetchHistoriDenganDetail secara paralel
|
||||
List<Future<List<Map<String, dynamic>>>> futures = uniqueUserIds
|
||||
.map((userId) => apiService.fetchHistoriDenganDetail(userId))
|
||||
.toList();
|
||||
|
||||
// Tunggu semua futures selesai
|
||||
List<List<Map<String, dynamic>>> results = await Future.wait(futures);
|
||||
|
||||
// Gabungkan semua hasil
|
||||
List<Map<String, dynamic>> detailedHistori = [];
|
||||
for (var histori in allHistori) {
|
||||
if (histori['userId'] != null) {
|
||||
final userHistori = await apiService.fetchHistoriDenganDetail(
|
||||
histori['userId'].toString(),
|
||||
);
|
||||
detailedHistori.addAll(userHistori);
|
||||
}
|
||||
for (var result in results) {
|
||||
detailedHistori.addAll(result);
|
||||
}
|
||||
|
||||
// Kelompokkan data berdasarkan user, diagnosa, dan waktu yang sama
|
||||
|
@ -263,15 +270,20 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Pagination controls
|
||||
), // Pagination controls
|
||||
Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.first_page),
|
||||
icon: Icon(Icons.first_page, size: 18),
|
||||
padding: EdgeInsets.all(4),
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 32,
|
||||
minHeight: 32,
|
||||
),
|
||||
onPressed:
|
||||
_currentPage > 0
|
||||
? () {
|
||||
|
@ -282,7 +294,12 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.chevron_left),
|
||||
icon: Icon(Icons.chevron_left, size: 18),
|
||||
padding: EdgeInsets.all(4),
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 32,
|
||||
minHeight: 32,
|
||||
),
|
||||
onPressed:
|
||||
_currentPage > 0
|
||||
? () {
|
||||
|
@ -292,14 +309,22 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
}
|
||||
: null,
|
||||
),
|
||||
SizedBox(width: 20),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Halaman ${_currentPage + 1} dari $_totalPages',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
'${_currentPage + 1} / $_totalPages',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 20),
|
||||
SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: Icon(Icons.chevron_right),
|
||||
icon: Icon(Icons.chevron_right, size: 18),
|
||||
padding: EdgeInsets.all(4),
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 32,
|
||||
minHeight: 32,
|
||||
),
|
||||
onPressed:
|
||||
_currentPage < _totalPages - 1
|
||||
? () {
|
||||
|
@ -310,7 +335,12 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.last_page),
|
||||
icon: Icon(Icons.last_page, size: 18),
|
||||
padding: EdgeInsets.all(4),
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 32,
|
||||
minHeight: 32,
|
||||
),
|
||||
onPressed:
|
||||
_currentPage < _totalPages - 1
|
||||
? () {
|
||||
|
@ -322,16 +352,21 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Rows per page selector
|
||||
), // Rows per page selector
|
||||
Container(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Rows per page: '),
|
||||
Text(
|
||||
'Rows per page: ',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
DropdownButton<int>(
|
||||
value: _rowsPerPage,
|
||||
isDense: true,
|
||||
menuMaxHeight: 200,
|
||||
items:
|
||||
[10, 20, 50, 100].map((value) {
|
||||
return DropdownMenuItem<int>(
|
||||
|
@ -370,4 +405,4 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
|||
double hasilValue = double.tryParse(hasil.toString()) ?? 0.0;
|
||||
return '${(hasilValue * 100).toStringAsFixed(2)}%';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -80,7 +80,7 @@ class _HamaPageState extends State<HamaPage> {
|
|||
: hamaList.length;
|
||||
List currentPageData = hamaList.sublist(start, end);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Halaman Hama')),
|
||||
appBar: AppBar(title: Text('Halaman Hama'), backgroundColor: Color(0xFF9DC08D)),
|
||||
body: Column(
|
||||
children: [
|
||||
SizedBox(height: 20),
|
||||
|
|
|
@ -81,7 +81,7 @@ class _PenyakitPageState extends State<PenyakitPage> {
|
|||
: penyakitList.length;
|
||||
List currentPageData = penyakitList.sublist(start, end);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Halaman Penyakit')),
|
||||
appBar: AppBar(title: Text('Halaman Penyakit'), backgroundColor: Color(0xFF9DC08D)),
|
||||
body: Column(
|
||||
children: [
|
||||
SizedBox(height: 20),
|
||||
|
|
|
@ -150,7 +150,7 @@ class _RulePageState extends State<RulePage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Data Rules')),
|
||||
appBar: AppBar(title: const Text('Data Rules'), backgroundColor: Color(0xFF9DC08D)),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
|
|
|
@ -416,7 +416,7 @@ class _UserListPageState extends State<UserListPage> {
|
|||
DataColumn(label: Text('Nama')),
|
||||
DataColumn(label: Text('Email')),
|
||||
DataColumn(label: Text('Alamat')),
|
||||
DataColumn(label: Text('No. Telepon')),
|
||||
// DataColumn(label: Text('No. Telepon')),
|
||||
DataColumn(label: Text('Role')),
|
||||
DataColumn(label: Text('Aksi')),
|
||||
],
|
||||
|
@ -427,7 +427,7 @@ class _UserListPageState extends State<UserListPage> {
|
|||
DataCell(Text(user['name'] ?? '-')),
|
||||
DataCell(Text(user['email'] ?? '-')),
|
||||
DataCell(Text(user['alamat'] ?? '-')),
|
||||
DataCell(Text(user['nomorTelepon'] ?? '-')),
|
||||
// DataCell(Text(user['nomorTelepon'] ?? '-')),
|
||||
DataCell(
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
|
|
|
@ -61,7 +61,7 @@ class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Hasil Diagnosa'),
|
||||
backgroundColor: Color(0xFFEDF1D6),
|
||||
backgroundColor: Color(0xFF9DC08D),
|
||||
foregroundColor: Color(0xFF40513B),
|
||||
),
|
||||
bottomNavigationBar: Padding(
|
||||
|
|
|
@ -97,7 +97,7 @@ class ProfilPakarPage extends StatelessWidget {
|
|||
radius: 60,
|
||||
backgroundColor: Colors.grey[200],
|
||||
backgroundImage: AssetImage(
|
||||
'assets/images/expert_photo.jpg',
|
||||
'assets/images/pak_gallyndra.jpg',
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -79,6 +79,7 @@ flutter:
|
|||
- assets/images/Virus.png
|
||||
- assets/images/Caterpillar.png
|
||||
- assets/images/karat putih.jpeg
|
||||
- assets/images/pak_gallyndra.jpg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
|
Loading…
Reference in New Issue