init
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'settings.dart';
|
||||
|
||||
class ApiException implements Exception {
|
||||
final String message;
|
||||
final int? status;
|
||||
ApiException(this.message, [this.status]);
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
class Profile {
|
||||
final String gid, fullName, role;
|
||||
final String? employeeCode, departmentName, designationTitle, reportingManagerName;
|
||||
final bool isManager;
|
||||
Profile.fromJson(Map<String, dynamic> j)
|
||||
: gid = j['gid'] ?? '',
|
||||
fullName = j['fullName'] ?? '',
|
||||
role = j['role'] ?? 'EMPLOYEE',
|
||||
employeeCode = j['employeeCode'],
|
||||
departmentName = j['departmentName'],
|
||||
designationTitle = j['designationTitle'],
|
||||
reportingManagerName = j['reportingManagerName'],
|
||||
isManager = j['isManager'] == true;
|
||||
}
|
||||
|
||||
class Today {
|
||||
final String? status, checkIn, checkOut, date;
|
||||
final num? hours, otHours;
|
||||
final int? lateMinutes;
|
||||
final bool checkedIn;
|
||||
Today.fromJson(Map<String, dynamic> j)
|
||||
: status = j['status'],
|
||||
checkIn = j['checkIn'],
|
||||
checkOut = j['checkOut'],
|
||||
date = j['date'],
|
||||
hours = j['hours'],
|
||||
otHours = j['otHours'],
|
||||
lateMinutes = j['lateMinutes'],
|
||||
checkedIn = j['checkedIn'] == true;
|
||||
}
|
||||
|
||||
class LeaveBalance {
|
||||
final String leaveTypeGid, leaveTypeName;
|
||||
final bool paid;
|
||||
final num allocated, used, available;
|
||||
LeaveBalance.fromJson(Map<String, dynamic> j)
|
||||
: leaveTypeGid = j['leaveTypeGid'] ?? '',
|
||||
leaveTypeName = j['leaveTypeName'] ?? '',
|
||||
paid = j['paid'] == true,
|
||||
allocated = j['allocated'] ?? 0,
|
||||
used = j['used'] ?? 0,
|
||||
available = j['available'] ?? 0;
|
||||
}
|
||||
|
||||
class LeaveType {
|
||||
final String gid, name;
|
||||
final bool paid;
|
||||
LeaveType.fromJson(Map<String, dynamic> j)
|
||||
: gid = j['gid'] ?? '',
|
||||
name = j['name'] ?? '',
|
||||
paid = j['paid'] == true;
|
||||
}
|
||||
|
||||
class LeaveRequest {
|
||||
final String gid, status;
|
||||
final String? leaveTypeName, fromDate, toDate, reason, employeeName;
|
||||
final num? days;
|
||||
LeaveRequest.fromJson(Map<String, dynamic> j)
|
||||
: gid = j['gid'] ?? '',
|
||||
status = j['status'] ?? '',
|
||||
leaveTypeName = j['leaveTypeName'],
|
||||
fromDate = j['fromDate'],
|
||||
toDate = j['toDate'],
|
||||
reason = j['reason'],
|
||||
employeeName = j['employeeName'],
|
||||
days = j['days'];
|
||||
}
|
||||
|
||||
class Payslip {
|
||||
final String gid, periodMonth, status;
|
||||
final num gross, totalDeductions, netPay;
|
||||
Payslip.fromJson(Map<String, dynamic> j)
|
||||
: gid = j['gid'] ?? '',
|
||||
periodMonth = j['periodMonth'] ?? '',
|
||||
status = j['status'] ?? '',
|
||||
gross = j['gross'] ?? 0,
|
||||
totalDeductions = j['totalDeductions'] ?? 0,
|
||||
netPay = j['netPay'] ?? 0;
|
||||
}
|
||||
|
||||
class TeamMember {
|
||||
final String employeeGid, employeeName, status;
|
||||
final String? employeeCode, checkIn, checkOut;
|
||||
final num? hours;
|
||||
final bool checkedIn;
|
||||
TeamMember.fromJson(Map<String, dynamic> j)
|
||||
: employeeGid = j['employeeGid'] ?? '',
|
||||
employeeName = j['employeeName'] ?? '',
|
||||
status = j['status'] ?? '—',
|
||||
employeeCode = j['employeeCode'],
|
||||
checkIn = j['checkIn'],
|
||||
checkOut = j['checkOut'],
|
||||
hours = j['hours'],
|
||||
checkedIn = j['checkedIn'] == true;
|
||||
}
|
||||
|
||||
class ApiService {
|
||||
final Settings settings;
|
||||
ApiService(this.settings);
|
||||
|
||||
Uri _uri(String p, [Map<String, String>? q]) =>
|
||||
Uri.parse('${settings.baseUrl}$p').replace(queryParameters: q);
|
||||
|
||||
Map<String, String> get _h => {
|
||||
'Content-Type': 'application/json',
|
||||
if ((settings.token ?? '').isNotEmpty) 'Authorization': 'Bearer ${settings.token}',
|
||||
};
|
||||
|
||||
// ---- auth ----
|
||||
Future<Profile> login(String identifier, String pin) async {
|
||||
http.Response res;
|
||||
try {
|
||||
res = await http
|
||||
.post(_uri('/api/v1/employee/auth/login'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({'identifier': identifier.trim(), 'pin': pin}))
|
||||
.timeout(const Duration(seconds: 20));
|
||||
} catch (_) {
|
||||
throw ApiException('Cannot reach server. Check the URL and connection.');
|
||||
}
|
||||
final body = _json(res);
|
||||
if (res.statusCode != 200 || body is! Map) {
|
||||
throw ApiException(_msg(body) ?? 'Invalid email or PIN', res.statusCode);
|
||||
}
|
||||
final p = Profile.fromJson(body['profile'] as Map<String, dynamic>);
|
||||
await settings.saveSession(token: body['accessToken'] as String, name: p.fullName, role: p.role);
|
||||
return p;
|
||||
}
|
||||
|
||||
// ---- me ----
|
||||
Future<Profile> profile() async => Profile.fromJson(await _get('/api/v1/me/profile'));
|
||||
Future<Today> today() async => Today.fromJson(await _get('/api/v1/me/attendance/today'));
|
||||
|
||||
Future<void> checkIn({double? lat, double? lng, String? selfie}) =>
|
||||
_post('/api/v1/me/attendance/check-in', _geo(lat, lng, selfie));
|
||||
Future<void> checkOut({double? lat, double? lng, String? selfie}) =>
|
||||
_post('/api/v1/me/attendance/check-out', _geo(lat, lng, selfie));
|
||||
Map<String, dynamic> _geo(double? lat, double? lng, String? selfie) => {
|
||||
if (lat != null) 'lat': lat,
|
||||
if (lng != null) 'lng': lng,
|
||||
if (selfie != null) 'selfie': selfie,
|
||||
};
|
||||
|
||||
Future<List<LeaveType>> leaveTypes() async =>
|
||||
(await _get('/api/v1/me/leave/types') as List).map((e) => LeaveType.fromJson(e)).toList();
|
||||
Future<List<LeaveBalance>> leaveBalances() async =>
|
||||
(await _get('/api/v1/me/leave/balances') as List).map((e) => LeaveBalance.fromJson(e)).toList();
|
||||
Future<List<LeaveRequest>> myLeave() async =>
|
||||
(await _get('/api/v1/me/leave/requests') as List).map((e) => LeaveRequest.fromJson(e)).toList();
|
||||
Future<void> applyLeave(String typeGid, String from, String to, String reason) =>
|
||||
_post('/api/v1/me/leave/requests', {'leaveTypeGid': typeGid, 'fromDate': from, 'toDate': to, 'reason': reason});
|
||||
|
||||
Future<List<Payslip>> payslips() async =>
|
||||
(await _get('/api/v1/me/payslips') as List).map((e) => Payslip.fromJson(e)).toList();
|
||||
|
||||
Future<List<TeamMember>> team() async =>
|
||||
(await _get('/api/v1/me/team') as List).map((e) => TeamMember.fromJson(e)).toList();
|
||||
Future<List<LeaveRequest>> teamLeave() async =>
|
||||
(await _get('/api/v1/me/team/leave') as List).map((e) => LeaveRequest.fromJson(e)).toList();
|
||||
Future<void> approve(String gid) => _post('/api/v1/me/team/leave/$gid/approve', {});
|
||||
Future<void> reject(String gid) => _post('/api/v1/me/team/leave/$gid/reject', {});
|
||||
|
||||
// ---- helpers ----
|
||||
Future<dynamic> _get(String p, [Map<String, String>? q]) async {
|
||||
http.Response r;
|
||||
try {
|
||||
r = await http.get(_uri(p, q), headers: _h).timeout(const Duration(seconds: 20));
|
||||
} catch (_) {
|
||||
throw ApiException('Network error');
|
||||
}
|
||||
return _handle(r);
|
||||
}
|
||||
|
||||
Future<dynamic> _post(String p, Map<String, dynamic> body) async {
|
||||
http.Response r;
|
||||
try {
|
||||
r = await http.post(_uri(p), headers: _h, body: jsonEncode(body)).timeout(const Duration(seconds: 20));
|
||||
} catch (_) {
|
||||
throw ApiException('Network error');
|
||||
}
|
||||
return _handle(r);
|
||||
}
|
||||
|
||||
dynamic _handle(http.Response r) {
|
||||
if (r.statusCode == 401) throw ApiException('Session expired. Please sign in again.', 401);
|
||||
if (r.statusCode == 204 || r.body.isEmpty) return null;
|
||||
final body = _json(r);
|
||||
if (r.statusCode >= 400) throw ApiException(_msg(body) ?? 'Request failed (${r.statusCode})', r.statusCode);
|
||||
if (body is Map && body.containsKey('data') && body.containsKey('success')) return body['data'];
|
||||
return body;
|
||||
}
|
||||
|
||||
dynamic _json(http.Response r) {
|
||||
try {
|
||||
return jsonDecode(r.body);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? _msg(dynamic b) => (b is Map && b['message'] is String) ? b['message'] as String : null;
|
||||
}
|
||||
Reference in New Issue
Block a user