This commit is contained in:
athul rd
2026-08-10 17:01:30 +05:30
commit 19a79d6126
16 changed files with 1790 additions and 0 deletions
+214
View File
@@ -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;
}