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