From 20a79a130d8d617dd2b4514dd84a74d98a213951 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 19 May 2026 11:24:41 +0800 Subject: [PATCH] feat: implement multi-branch biometric IP configuration and auto-sync --- .../BiometricAttendanceController.php | 336 ++++++++++++++---- app/Http/Controllers/BranchController.php | 2 + .../Settings/SettingsController.php | 11 +- .../Controllers/ZektoSettingsController.php | 96 ----- app/Models/Branch.php | 1 + ...025935_add_zkteco_ip_to_branches_table.php | 28 ++ .../pages/hr/biometric-attendance/index.tsx | 26 +- resources/js/pages/hr/branches/index.tsx | 6 + .../settings/components/zekto-settings.tsx | 234 ------------ resources/js/pages/settings/index.tsx | 26 +- routes/settings.php | 5 +- routes/web.php | 1 + 12 files changed, 337 insertions(+), 435 deletions(-) delete mode 100644 app/Http/Controllers/ZektoSettingsController.php create mode 100644 database/migrations/2026_05_19_025935_add_zkteco_ip_to_branches_table.php delete mode 100755 resources/js/pages/settings/components/zekto-settings.tsx diff --git a/app/Http/Controllers/BiometricAttendanceController.php b/app/Http/Controllers/BiometricAttendanceController.php index d4b1f6c4a..ba3cdb112 100644 --- a/app/Http/Controllers/BiometricAttendanceController.php +++ b/app/Http/Controllers/BiometricAttendanceController.php @@ -25,7 +25,8 @@ class BiometricAttendanceController extends Controller $token = !empty($company_setting['zkteco_auth_token']) ? $company_setting['zkteco_auth_token'] : ''; $isZktecoSync = !empty($company_setting['isZktecoSync']) && $company_setting['isZktecoSync'] == '1'; - $configurationMissing = empty($api_urls) || empty($username) || empty($password) || empty($token) || !$isZktecoSync; + $isDirectIP = filter_var($api_urls, FILTER_VALIDATE_IP); + $configurationMissing = empty($api_urls) || (!$isDirectIP && (empty($username) || empty($password) || empty($token) || !$isZktecoSync)); if (!empty($request->start_date) && !empty($request->end_date)) { $start_date = date('Y-m-d H:i:s', strtotime($request->start_date)); @@ -37,38 +38,74 @@ class BiometricAttendanceController extends Controller $attendances = []; - if (!empty($token) && !empty($api_urls)) { - $api_url = rtrim($api_urls, '/'); - $url = $api_url . '/iclock/api/transactions/?' . http_build_query([ - 'start_time' => $start_date, - 'end_time' => $end_date, - 'page_size' => 10000, - ]); - - $curl = curl_init(); - try { - curl_setopt_array($curl, array( - CURLOPT_URL => $url, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_HTTPHEADER => array( - 'Content-Type: application/json', - 'Authorization: Token ' . $token - ), - )); - - $response = curl_exec($curl); - curl_close($curl); - - $json_attendance = json_decode($response, true); - $attendances = $json_attendance['data'] ?? []; - } catch (\Throwable $th) { - $attendances = []; + $branches = \App\Models\Branch::whereNotNull('zkteco_ip')->whereIn('created_by', getCompanyAndUsersId())->get(); + if ($branches->isNotEmpty()) { + foreach ($branches as $branch) { + try { + $zk = new \Rats\Zkteco\Lib\Zkteco($branch->zkteco_ip); + if ($zk->connect()) { + $raw_attendance = $zk->getAttendance(); + $startTs = strtotime(strlen($start_date) == 10 ? $start_date . ' 00:00:00' : $start_date); + $endTs = strtotime(strlen($end_date) == 10 ? $end_date . ' 23:59:59' : $end_date); + foreach ($raw_attendance as $record) { + $punchTs = strtotime($record['timestamp']); + if ($punchTs >= $startTs && $punchTs <= $endTs) { + $attendances[] = [ + 'id' => $record['uid'], + 'emp_code' => (string) $record['id'], + 'punch_time' => $record['timestamp'], + 'punch_state_display' => $record['type'] == 0 ? 'Clock In' : 'Clock Out', + 'terminal_alias' => $branch->name, + 'branch_id' => $branch->id + ]; + } + } + $zk->disconnect(); + } + } catch (\Throwable $th) {} + } + } else if (!empty($api_urls)) { + if ($isDirectIP) { + try { + $zk = new \Rats\Zkteco\Lib\Zkteco($api_urls); + if ($zk->connect()) { + $raw_attendance = $zk->getAttendance(); + $startTs = strtotime(strlen($start_date) == 10 ? $start_date . ' 00:00:00' : $start_date); + $endTs = strtotime(strlen($end_date) == 10 ? $end_date . ' 23:59:59' : $end_date); + foreach ($raw_attendance as $record) { + $punchTs = strtotime($record['timestamp']); + if ($punchTs >= $startTs && $punchTs <= $endTs) { + $attendances[] = [ + 'id' => $record['uid'], + 'emp_code' => (string) $record['id'], + 'punch_time' => $record['timestamp'], + 'punch_state_display' => $record['type'] == 0 ? 'Clock In' : 'Clock Out', + 'terminal_alias' => 'Local ZKTeco' + ]; + } + } + $zk->disconnect(); + } + } catch (\Throwable $th) {} + } else if (!empty($token)) { + $api_url = rtrim($api_urls, '/'); + $url = $api_url . '/iclock/api/transactions/?' . http_build_query([ + 'start_time' => $start_date, + 'end_time' => $end_date, + 'page_size' => 10000, + ]); + $curl = curl_init(); + try { + curl_setopt_array($curl, array( + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Content-Type: application/json', 'Authorization: Token ' . $token) + )); + $response = curl_exec($curl); + curl_close($curl); + $json = json_decode($response, true); + $attendances = $json['data'] ?? []; + } catch (\Throwable $th) {} } } @@ -87,7 +124,12 @@ class BiometricAttendanceController extends Controller $sorted = $dayEntries->sortBy('punch_time'); $firstEntry = $sorted->first(); $lastEntry = $sorted->last(); - $employee = Employee::with('user')->where('biometric_emp_id', $firstEntry['emp_code'])->first(); + $q = Employee::with('user')->where('biometric_emp_id', $firstEntry['emp_code']); + if (isset($firstEntry['branch_id'])) { + $q->where('branch_id', $firstEntry['branch_id']); + } + $employee = $q->first(); + if (!$employee) return null; return [ 'id' => $firstEntry['id'], @@ -97,9 +139,10 @@ class BiometricAttendanceController extends Controller 'clock_in' => date('H:i:s', strtotime($firstEntry['punch_time'])), 'clock_out' => $sorted->count() > 1 ? date('H:i:s', strtotime($lastEntry['punch_time'])) : null, 'total_entries' => $sorted->count(), + 'terminal' => $firstEntry['terminal_alias'] ?? 'Unknown' ]; - }); - $query = $groupedAttendances->values(); + })->filter()->values(); + $query = $groupedAttendances; // Handle search if ($request->has('search') && !empty($request->search)) { @@ -180,48 +223,76 @@ class BiometricAttendanceController extends Controller $token = $company_setting['zkteco_auth_token'] ?? ''; $api_urls = $company_setting['zkteco_api_url'] ?? ''; - if (empty($token) || empty($api_urls)) { + $isDirectIP = filter_var($api_urls, FILTER_VALIDATE_IP); + + if (empty($api_urls) || (!$isDirectIP && empty($token))) { return response()->json([ 'success' => false, 'message' => 'ZKTeco API configuration missing' ], 400); } - $start_date = $date . ' 00:00:00'; - $end_date = $date . ' 23:59:59'; + if ($isDirectIP) { + try { + $zk = new \Rats\Zkteco\Lib\Zkteco($api_urls); + if ($zk->connect()) { + $raw_attendance = $zk->getAttendance(); + foreach ($raw_attendance as $record) { + if ((string)$record['id'] === (string)$employeeCode && str_starts_with($record['timestamp'], $date)) { + $attendances[] = [ + 'id' => $record['uid'], + 'punch_time' => $record['timestamp'], + 'punch_state_display' => $record['type'] == 0 ? 'Clock In' : 'Clock Out', + 'verify_type_display' => 'Unknown', + 'terminal_alias' => 'Local ZKTeco' + ]; + } + } + $zk->disconnect(); + } + } catch (\Throwable $th) { + return response()->json([ + 'success' => false, + 'message' => 'Failed to connect to local ZKTeco' + ], 500); + } + } else { + $start_date = $date . ' 00:00:00'; + $end_date = $date . ' 23:59:59'; - $api_url = rtrim($api_urls, '/'); - $url = $api_url . '/iclock/api/transactions/?' . http_build_query([ - 'emp_code' => $employeeCode, - 'start_time' => $start_date, - 'end_time' => $end_date, - 'page_size' => 1000, - ]); + $api_url = rtrim($api_urls, '/'); + $url = $api_url . '/iclock/api/transactions/?' . http_build_query([ + 'emp_code' => $employeeCode, + 'start_time' => $start_date, + 'end_time' => $end_date, + 'page_size' => 1000, + ]); - $curl = curl_init(); - curl_setopt_array($curl, [ - CURLOPT_URL => $url, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 30, - CURLOPT_HTTPHEADER => [ - 'Content-Type: application/json', - 'Authorization: Token ' . $token - ], - ]); + $curl = curl_init(); + curl_setopt_array($curl, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Authorization: Token ' . $token + ], + ]); - $response = curl_exec($curl); - $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); + $response = curl_exec($curl); + $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); - if ($httpCode !== 200 || !$response) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch data from ZKTeco API' - ], 500); + if ($httpCode !== 200 || !$response) { + return response()->json([ + 'success' => false, + 'message' => 'Failed to fetch data from ZKTeco API' + ], 500); + } + + $json_attendance = json_decode($response, true); + $attendances = $json_attendance['data'] ?? []; } - - $json_attendance = json_decode($response, true); - $attendances = $json_attendance['data'] ?? []; } @@ -263,6 +334,133 @@ class BiometricAttendanceController extends Controller } } + public function syncAll(Request $request) + { + if (Auth::user()->can('sync-biometric-attendance')) { + $company_setting = settings(); + $api_urls = !empty($company_setting['zkteco_api_url']) ? $company_setting['zkteco_api_url'] : ''; + $token = !empty($company_setting['zkteco_auth_token']) ? $company_setting['zkteco_auth_token'] : ''; + + $start_date = date('Y-m-d', strtotime('-7 days')); + $end_date = date('Y-m-d'); + + $isDirectIP = filter_var($api_urls, FILTER_VALIDATE_IP); + $attendances = []; + + $branches = \App\Models\Branch::whereNotNull('zkteco_ip')->whereIn('created_by', getCompanyAndUsersId())->get(); + if ($branches->isNotEmpty()) { + foreach ($branches as $branch) { + try { + $zk = new \Rats\Zkteco\Lib\Zkteco($branch->zkteco_ip); + if ($zk->connect()) { + $raw_attendance = $zk->getAttendance(); + $startTs = strtotime($start_date . ' 00:00:00'); + $endTs = strtotime($end_date . ' 23:59:59'); + foreach ($raw_attendance as $record) { + $punchTs = strtotime($record['timestamp']); + if ($punchTs >= $startTs && $punchTs <= $endTs) { + $attendances[] = [ + 'id' => $record['uid'], + 'emp_code' => (string) $record['id'], + 'punch_time' => $record['timestamp'], + 'branch_id' => $branch->id + ]; + } + } + $zk->disconnect(); + } + } catch (\Throwable $th) {} + } + } else if (!empty($api_urls)) { + if ($isDirectIP) { + try { + $zk = new \Rats\Zkteco\Lib\Zkteco($api_urls); + if ($zk->connect()) { + $raw_attendance = $zk->getAttendance(); + $startTs = strtotime($start_date . ' 00:00:00'); + $endTs = strtotime($end_date . ' 23:59:59'); + foreach ($raw_attendance as $record) { + $punchTs = strtotime($record['timestamp']); + if ($punchTs >= $startTs && $punchTs <= $endTs) { + $attendances[] = [ + 'id' => $record['uid'], + 'emp_code' => (string) $record['id'], + 'punch_time' => $record['timestamp'], + ]; + } + } + $zk->disconnect(); + } + } catch (\Throwable $th) {} + } else if (!empty($token)) { + $api_url = rtrim($api_urls, '/'); + $url = $api_url . '/iclock/api/transactions/?' . http_build_query([ + 'start_time' => date('Y-m-d H:i:s', strtotime($start_date . ' 00:00:00')), + 'end_time' => date('Y-m-d H:i:s', strtotime($end_date . ' 23:59:59')), + 'page_size' => 10000, + ]); + $curl = curl_init(); + try { + curl_setopt_array($curl, array( + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Content-Type: application/json', 'Authorization: Token ' . $token) + )); + $response = curl_exec($curl); + curl_close($curl); + $json = json_decode($response, true); + $attendances = $json['data'] ?? []; + } catch (\Throwable $th) {} + } + } + + $groupedAttendances = collect($attendances)->groupBy(function ($item) { + return $item['emp_code'] . '_' . date('Y-m-d', strtotime($item['punch_time'])); + }); + + $syncedCount = 0; + foreach ($groupedAttendances as $key => $dayEntries) { + $sorted = $dayEntries->sortBy('punch_time'); + $firstEntry = $sorted->first(); + $lastEntry = $sorted->last(); + + $q = Employee::with('user')->whereIn('created_by', getCompanyAndUsersId())->where('biometric_emp_id', $firstEntry['emp_code']); + if (isset($firstEntry['branch_id'])) { + $q->where('branch_id', $firstEntry['branch_id']); + } + $employee = $q->first(); + if ($employee && $sorted->count() > 1) { + $attedanceDate = date('Y-m-d', strtotime($firstEntry['punch_time'])); + $clockInTime = date('H:i:s', strtotime($firstEntry['punch_time'])); + $clockOutTime = date('H:i:s', strtotime($lastEntry['punch_time'])); + + $exists = AttendanceRecord::where('employee_id', $employee->user_id)->where('date', $attedanceDate)->whereIn('created_by', getCompanyAndUsersId())->exists(); + if (!$exists) { + $shift = Shift::where('id', $employee->shift_id)->where('status', 'active')->first() ?? Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first(); + $policy = AttendancePolicy::where('id', $employee->attendance_policy_id)->where('status', 'active')->first() ?? AttendancePolicy::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first(); + + $attendance = new AttendanceRecord(); + $attendance->employee_id = $employee->user_id; + $attendance->biometric_id = $firstEntry['id']; + $attendance->shift_id = $shift?->id; + $attendance->attendance_policy_id = $policy?->id; + $attendance->date = $attedanceDate; + $attendance->clock_in = $clockInTime; + $attendance->clock_out = $clockOutTime; + $attendance->created_by = creatorId(); + $attendance->save(); + + $attendance->fresh(); + $attendance->processAttendance(); + $syncedCount++; + } + } + } + return redirect()->back()->with('success', __("Bulk sync completed. Synced {$syncedCount} new records.")); + } + return redirect()->back()->with('error', __('Permission Denied.')); + } + public function sync(Request $request, $id) { if (Auth::user()->can('sync-biometric-attendance')) { @@ -275,7 +473,9 @@ class BiometricAttendanceController extends Controller $company_setting = settings(); - if (empty($company_setting['zkteco_auth_token'])) { + $isDirectIP = filter_var($company_setting['zkteco_api_url'] ?? '', FILTER_VALIDATE_IP); + + if (!$isDirectIP && empty($company_setting['zkteco_auth_token'])) { return redirect()->back()->with('error', __('Create the Auth Token From the Setting page.')); } diff --git a/app/Http/Controllers/BranchController.php b/app/Http/Controllers/BranchController.php index 0d36e7082..967fa88f8 100644 --- a/app/Http/Controllers/BranchController.php +++ b/app/Http/Controllers/BranchController.php @@ -66,6 +66,7 @@ class BranchController extends Controller 'zip_code' => 'nullable|string|max:20', 'phone' => 'nullable|string|max:20', 'email' => 'nullable|email|max:255', + 'zkteco_ip' => 'nullable|string|max:255', 'status' => 'nullable|in:active,inactive', ]); @@ -111,6 +112,7 @@ class BranchController extends Controller 'zip_code' => 'nullable|string|max:20', 'phone' => 'nullable|string|max:20', 'email' => 'nullable|email|max:255', + 'zkteco_ip' => 'nullable|string|max:255', 'status' => 'nullable|in:active,inactive', ]); diff --git a/app/Http/Controllers/Settings/SettingsController.php b/app/Http/Controllers/Settings/SettingsController.php index 3a26c2936..65efc99f8 100644 --- a/app/Http/Controllers/Settings/SettingsController.php +++ b/app/Http/Controllers/Settings/SettingsController.php @@ -30,15 +30,6 @@ class SettingsController extends Controller $webhooks = Webhook::where('user_id', auth()->id())->get(); $ipRestrictions = IpRestriction::whereIn('created_by', getCompanyAndUsersId())->orderBy('id', 'desc')->get(); - // Get Zekto settings for company users - $zektoSettings = []; - $zektoSettings = [ - 'zkteco_api_url' => isset($systemSettings['zkteco_api_url']) ? $systemSettings['zkteco_api_url'] : '', - 'zkteco_username' => isset($systemSettings['zkteco_username']) ? $systemSettings['zkteco_username'] : '', - 'zkteco_password' => isset($systemSettings['zkteco_password']) ? $systemSettings['zkteco_password'] : '', - 'zkteco_auth_token' => isset($systemSettings['zkteco_auth_token']) ? $systemSettings['zkteco_auth_token'] : '', - ]; - // Get NOC templates for company users $nocTemplates = NocTemplate::where('created_by', Auth::user()->id)->get(); @@ -58,7 +49,7 @@ class SettingsController extends Controller 'timeFormats' => config('timeformat'), 'paymentSettings' => $paymentSettings, 'webhooks' => $webhooks, - 'zektoSettings' => $zektoSettings, + 'ipRestrictions' => $ipRestrictions, 'nocTemplates' => $nocTemplates, 'joiningLetterTemplates' => $joiningLetterTemplates, diff --git a/app/Http/Controllers/ZektoSettingsController.php b/app/Http/Controllers/ZektoSettingsController.php deleted file mode 100644 index 4d3a1f84d..000000000 --- a/app/Http/Controllers/ZektoSettingsController.php +++ /dev/null @@ -1,96 +0,0 @@ -can('manage-biomatric-attedance-settings')) { - $validator = Validator::make($request->all(), [ - 'zkteco_api_url' => 'required|url', - 'zkteco_username' => 'required|string|max:255', - 'zkteco_password' => 'required|string|max:255', - ]); - - if ($validator->fails()) { - return redirect()->back()->withErrors($validator)->withInput(); - } - - // Update settings - updateSetting('zkteco_api_url', $request->zkteco_api_url); - updateSetting('zkteco_username', $request->zkteco_username); - updateSetting('zkteco_password', $request->zkteco_password); - updateSetting('isZktecoSync', 0); - - return redirect()->back()->with('success', __('ZKTeco settings updated successfully')); - } else { - return redirect()->back()->with('error', __('Permission Denied.')); - } - } - - /** - * Generate auth token from ZKTeco API - */ - public function generateToken(Request $request) - { - if (Auth::user()->can('manage-biomatric-attedance-settings')) { - $validator = Validator::make($request->all(), [ - 'zkteco_api_url' => 'required|url', - 'zkteco_username' => 'required|string', - 'zkteco_password' => 'required|string', - ]); - - if ($validator->fails()) { - return redirect()->back()->withErrors($validator)->withInput(); - } - - try { - $url = "$request->zkteco_api_url" . '/api-token-auth/'; - $headers = array( - "Content-Type: application/json" - ); - $data = array( - "username" => $request->zkteco_username, - "password" => $request->zkteco_password - ); - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - $response = curl_exec($ch); - curl_close($ch); - $auth_token = json_decode($response, true); - if (isset($auth_token['token'])) { - // Store the generated token using existing settings structure - updateSetting('isZktecoSync', 1); - updateSetting('zkteco_auth_token', $auth_token['token']); - - return redirect()->back()->with([ - 'success' => __('Auth token generated successfully'), - 'token' => $auth_token['token'] - ]); - } else { - throw new \Exception(isset($auth_token['non_field_errors']) ? $auth_token['non_field_errors'][0] : "Something went wrong please try again"); - } - } catch (\Exception $e) { - - return redirect()->back()->with('error', $e->getMessage()); - } - } else { - return redirect()->back()->with('error', __('Permission Denied.')); - } - } -} diff --git a/app/Models/Branch.php b/app/Models/Branch.php index 70e3efbef..3a01b9462 100644 --- a/app/Models/Branch.php +++ b/app/Models/Branch.php @@ -11,6 +11,7 @@ class Branch extends BaseModel protected $fillable = [ 'name', + 'zkteco_ip', 'address', 'city', 'state', diff --git a/database/migrations/2026_05_19_025935_add_zkteco_ip_to_branches_table.php b/database/migrations/2026_05_19_025935_add_zkteco_ip_to_branches_table.php new file mode 100644 index 000000000..b82d7d5f7 --- /dev/null +++ b/database/migrations/2026_05_19_025935_add_zkteco_ip_to_branches_table.php @@ -0,0 +1,28 @@ +string('zkteco_ip')->nullable()->after('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('branches', function (Blueprint $table) { + $table->dropColumn('zkteco_ip'); + }); + } +}; diff --git a/resources/js/pages/hr/biometric-attendance/index.tsx b/resources/js/pages/hr/biometric-attendance/index.tsx index a467bc903..5df11c4d0 100755 --- a/resources/js/pages/hr/biometric-attendance/index.tsx +++ b/resources/js/pages/hr/biometric-attendance/index.tsx @@ -269,11 +269,35 @@ export default function BiometricAttendance() { {/* Content section */}
{/* Header with info */} -
+
{t('Biometric attendance data from device')}
+
{configurationMissing ? ( diff --git a/resources/js/pages/hr/branches/index.tsx b/resources/js/pages/hr/branches/index.tsx index dd485a468..9f6a11285 100755 --- a/resources/js/pages/hr/branches/index.tsx +++ b/resources/js/pages/hr/branches/index.tsx @@ -256,6 +256,11 @@ export default function Branches() { return contact.join(' | ') || '-'; } }, + { + key: 'zkteco_ip', + label: t('ZKTeco IP'), + render: (value: string) => value || '-' + }, { key: 'status', label: t('Status'), @@ -388,6 +393,7 @@ export default function Branches() { { name: 'zip_code', label: t('ZIP/Postal Code'), type: 'text' }, { name: 'phone', label: t('Phone'), type: 'text' }, { name: 'email', label: t('Email'), type: 'email' }, + { name: 'zkteco_ip', label: t('ZKTeco Biometric IP'), type: 'text' }, { name: 'status', label: t('Status'), diff --git a/resources/js/pages/settings/components/zekto-settings.tsx b/resources/js/pages/settings/components/zekto-settings.tsx deleted file mode 100755 index e664b698d..000000000 --- a/resources/js/pages/settings/components/zekto-settings.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import { Button } from '@/components/ui/button'; -import { Label } from '@/components/ui/label'; -import { Input } from '@/components/ui/input'; -import { Textarea } from '@/components/ui/textarea'; -import { useState, useEffect } from 'react'; -import { Save, Key, AlertCircle } from 'lucide-react'; -import { SettingsSection } from '@/components/settings-section'; -import { Card, CardContent } from '@/components/ui/card'; -import { Alert, AlertDescription } from '@/components/ui/alert'; -import { useTranslation } from 'react-i18next'; -import { router, usePage } from '@inertiajs/react'; -import { toast } from '@/components/custom-toast'; -import { hasPermission } from '@/utils/permissions'; - -interface ZektoSettingsProps { - settings?: Record; -} - -export default function ZektoSettings({ settings = {} }: ZektoSettingsProps) { - const { t } = useTranslation(); - const { globalSettings } = usePage().props as any; - const canManageBiometric = hasPermission('manage-biomatric-attedance-settings'); - - const [zektoSettings, setZektoSettings] = useState({ - zkteco_api_url: '', - zkteco_username: '', - zkteco_password: '', - zkteco_auth_token: '', - }); - - const [isGeneratingToken, setIsGeneratingToken] = useState(false); - - useEffect(() => { - setZektoSettings({ - zkteco_api_url: settings.zkteco_api_url || '', - zkteco_username: settings.zkteco_username || '', - zkteco_password: settings.zkteco_password || '', - zkteco_auth_token: settings.zkteco_auth_token || '', - }); - }, [settings]); - - const handleInputChange = (field: string, value: string) => { - setZektoSettings(prev => ({ - ...prev, - [field]: value - })); - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - if (!globalSettings?.is_demo) { - toast.loading(t('Saving ZKTeco settings...')); - } - - router.post(route('settings.zekto.update'), zektoSettings, { - preserveScroll: true, - onSuccess: (page) => { - if (!globalSettings?.is_demo) { - toast.dismiss(); - } - const successMessage = page.props.flash?.success; - const errorMessage = page.props.flash?.error; - - if (successMessage) { - toast.success(successMessage); - } else if (errorMessage) { - toast.error(errorMessage); - } else { - toast.success(t('ZKTeco settings saved successfully')); - } - }, - onError: (errors) => { - if (!globalSettings?.is_demo) { - toast.dismiss(); - } - const errorMessage = errors.error || Object.values(errors).join(', ') || t('Failed to save ZKTeco settings'); - toast.error(errorMessage); - } - }); - }; - - const handleGenerateToken = () => { - if (!zektoSettings.zkteco_api_url || !zektoSettings.zkteco_username || !zektoSettings.zkteco_password) { - toast.error(t('Please fill in API URL, Username, and Password before generating token')); - return; - } - - setIsGeneratingToken(true); - if (!globalSettings?.is_demo) { - toast.loading(t('Generating auth token...')); - } - - router.post(route('settings.zekto.generate-token'), { - zkteco_api_url: zektoSettings.zkteco_api_url, - zkteco_username: zektoSettings.zkteco_username, - zkteco_password: zektoSettings.zkteco_password, - }, { - preserveScroll: true, - onSuccess: (page) => { - setIsGeneratingToken(false); - if (!globalSettings?.is_demo) { - toast.dismiss(); - } - - const successMessage = page.props.flash?.success; - const errorMessage = page.props.flash?.error; - const token = page.props.flash?.token; - - if (successMessage && token) { - setZektoSettings(prev => ({ - ...prev, - zkteco_auth_token: token - })); - toast.success(successMessage); - } else if (errorMessage) { - toast.error(errorMessage); - } - }, - onError: (errors) => { - setIsGeneratingToken(false); - if (!globalSettings?.is_demo) { - toast.dismiss(); - } - const errorMessage = errors.error || Object.values(errors).join(', ') || t('Failed to generate auth token'); - toast.error(errorMessage); - } - }); - }; - - return ( - - - {t("Save Changes")} - - } - > - - - - - -
{t("Note that you can use the biometric attendance system only if you are using the ZKTeco machine for biometric attendance.")}
-
{t("If an employee has multiple entries in a single day, the first entry will be considered as clock-in time and the last entry will be considered as clock-out time.")}
-
-
- -
-
-
- - handleInputChange('zkteco_api_url', e.target.value)} - required - /> -

- {t("Example")}: http://110.78.645.123:8080 -

-
- -
-
- - handleInputChange('zkteco_username', e.target.value)} - required - /> -
- -
- - handleInputChange('zkteco_password', e.target.value)} - required - /> -
-
- -
- -