add new function in diagnosaController

This commit is contained in:
unknown 2025-06-09 14:46:59 +07:00
parent c012b08a5f
commit d35f64b87e
11 changed files with 92 additions and 34 deletions

View File

@ -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({

View File

@ -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

View File

@ -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)}%';
}
}
}

View File

@ -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),

View File

@ -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),

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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',
),
),
),

View File

@ -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