Merge remote-tracking branch 'origin/staging' into origin/production

This commit is contained in:
Linksehat Staging Server
2024-05-28 10:56:28 +07:00
51 changed files with 3643 additions and 377 deletions

View File

@@ -14,6 +14,9 @@ use Modules\Internal\Emails\SendVerifyEmail;
use Modules\Internal\Events\ForgetPassword; use Modules\Internal\Events\ForgetPassword;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Modules\HospitalPortal\Helpers\ApiResponse; use Modules\HospitalPortal\Helpers\ApiResponse;
use Illuminate\Support\Facades\DB;
use App\Helpers\Helper;
use Illuminate\Support\Facades\View;
class AuthController extends Controller class AuthController extends Controller
{ {
@@ -27,9 +30,9 @@ class AuthController extends Controller
'email' => 'required|email', 'email' => 'required|email',
'password' => 'required' 'password' => 'required'
], [ ], [
'email.required' => trans('validation.required',['attribute' => 'Email']), 'email.required' => trans('Validation.required',['attribute' => 'Email']),
'email.email' => trans('validation.email'), 'email.email' => trans('Validation.email'),
'password.required' => trans('validation.required',['attribute' => 'Password']), 'password.required' => trans('Validation.required',['attribute' => 'Password']),
]); ]);
if ($validator->fails()) if ($validator->fails())
@@ -40,11 +43,11 @@ class AuthController extends Controller
{ {
$user = User::where('email', $request->email)->first(); $user = User::where('email', $request->email)->first();
if (!$user) { if (!$user) {
return ApiResponse::apiResponse('Not Found', $data, trans('message.not_found'), 404); return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404);
} }
if (!Hash::check($request->password, $user->password)) { if (!Hash::check($request->password, $user->password)) {
return ApiResponse::apiResponse('Bad Request', $data, trans('message.password'), 400); return ApiResponse::apiResponse('Bad Request', $data, trans('Message.password'), 400);
} }
$res_data = [ $res_data = [
@@ -52,16 +55,15 @@ class AuthController extends Controller
'token' => $user->createToken('app')->plainTextToken 'token' => $user->createToken('app')->plainTextToken
]; ];
return ApiResponse::apiResponse("Success", $res_data, trans('message.success'), 200); return ApiResponse::apiResponse("Success", $res_data, trans('Message.success'), 200);
} }
} }
public function logout(Request $request) public function logout(Request $request)
{ {
$token = $request->bearerToken(); $request->user()->tokens()->delete();
Auth::user()->tokens()->where('id', $token)->delete();
return response(['message' => 'Berhasil Logout.']); return ApiResponse::apiResponse('Success', [], trans('Message.logout'), 200);
} }
public function resetPassword(Request $request) public function resetPassword(Request $request)
@@ -75,12 +77,12 @@ class AuthController extends Controller
]); ]);
if (!Hash::check($request['old_password'], $user->password)) { if (!Hash::check($request['old_password'], $user->password)) {
return response(['message' => 'Password Salah'], 403); return response(['Message' => 'Password Salah'], 403);
} }
if ($request["new_password"] != $request["confirm_new_password"]) { if ($request["new_password"] != $request["confirm_new_password"]) {
return response([ return response([
'message' => "Password Tidak Sama" 'Message' => "Password Tidak Sama"
]); ]);
} }
@@ -92,52 +94,202 @@ class AuthController extends Controller
public function verifyEmail(Request $request) public function verifyEmail(Request $request)
{ {
$request->validate([ $data = [
'email' => $request->email,
];
$validator = Validator::make($request->all(), [
'email' => 'required|email', 'email' => 'required|email',
], [
'email.required' => trans('Validation.required',['attribute' => 'Email']),
'email.email' => trans('Validation.email'),
]); ]);
$user = User::query() if ($validator->fails())
->where('email', $request->email) {
return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400);
}
else
{
$user = User::where('email', $request->email)->first();
if (!$user) {
return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404);
}
//send email
// Insert data notifications
$emailTo = $request->email;
$dataNotif = [
'user_id' => $user->id,
'email' => $emailTo,
'title' => 'Forgot Password',
'description' => 'Request forgot password from Hospital Portal',
'type' => 1,
'isUnRead' => true,
'created_by' => auth()->check() ? auth()->user()->id : null,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
];
$sendNotif = Helper::insertNotification($dataNotif);
//Insert data password reset
$token = mt_rand(100000, 999999); // Menghasilkan angka acak antara 100000 dan 999999
$p_resets = DB::table('password_resets')
->insert([
'email' => $request->email,
'token' => $token,
'created_at' => date('Y-m-d H:i:s'),
]);
// Send Email after insert notifications
if($sendNotif && $p_resets)
{
//send to alarm
$nameTo = 'User';
$dataEmail = [
'email' => $emailTo,
'name' => $nameTo,
'subject' => 'Request Forgot Password from Hospital Portal Date '. date('Y-m-d H:i:s'),
'body' => View::make('email/forgot_password', ['token' => $token])->render(),
];
Helper::sendEmail($dataEmail);
$res = DB::table('password_resets')
->where('email', '=', $request->email)
->where('token', '=', $token)
->first();
return ApiResponse::apiResponse("Success", $res, trans('Message.success'), 200);
}
else
{
return ApiResponse::apiResponse("Internal Server Error", $data, trans('Message.server_error'), 500);
}
}
}
public function verifCode(Request $request)
{
$data = [
'email' => $request->email,
'token' => $request->token,
];
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'token' => 'required|numeric',
], [
'email.required' => trans('Validation.required',['attribute' => 'Email']),
'email.email' => trans('Validation.email'),
'token.required' => trans('Validation.required',['attribute' => 'Token']),
'token.numeric' => trans('Validation.required',['attribute' => 'Code Numeric']),
]);
if ($validator->fails())
{
return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400);
}
else
{
//Check Time
$check = DB::table('password_resets')
->where('email', '=', $request->email)
->where('token', '=', $request->token)
->select('created_at')
->first(); ->first();
if (!$user) { if($check)
return response(['message' => 'User Tidak Ditemukan'], 404); {
$created_at = strtotime($check->created_at); // Konversi string waktu ke UNIX timestamp
$now = time(); // Waktu sekarang dalam UNIX timestamp
// Hitung selisih waktu dalam menit
$diffInMinutes = ($now - $created_at) / 60;
if ($diffInMinutes > 60) {
return ApiResponse::apiResponse('Not Found', $data, trans('Message.token_expired'), 404);
} else {
// Lanjutkan dengan proses pemulihan kata sandi
return ApiResponse::apiResponse("Success", $data, trans('Message.success'), 200);
}
}
else
{
return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404);
}
} }
Event(new ForgetPassword($user));
// Mail::to($user->email)->send(new SendVerifyEmail($user));
return response()->json($user);
} }
public function forgetPassword(Request $request) public function forgetPassword(Request $request)
{ {
$request->validate([ $data = [
'new_password' => 'required', 'email' => $request->email,
'confirm_new_password' => 'required' 'token' => $request->token,
'new_password' => $request->new_password
];
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'token' => 'required|numeric',
'new_password' => [
'required',
'min:8',
'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/'
]
], [
'email.required' => trans('Validation.required',['attribute' => 'Email']),
'email.email' => trans('Validation.email'),
'token.required' => trans('Validation.required',['attribute' => 'Token']),
'new_password.required' => trans('Validation.required',['attribute' => 'New Password']),
'new_password.min' => trans('Validation.min',['attribute' => 'New Password']),
'new_password.regex' => trans('Validation.regex',['attribute' => 'New Password']),
]); ]);
$token = Crypt::decryptString($request->token); if($request->new_password != $request->confirm_new_password)
$email = explode('|', $token)[0]; {
return ApiResponse::apiResponse('Bad Request', $data, 'Confirm password is not the same', 400);
$user = User::query() }
->where('email', $email) else if ($validator->fails())
{
return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400);
}
else
{
//Check Time
$check = DB::table('password_resets')
->where('email', '=', $request->email)
->where('token', '=', $request->token)
->select('created_at')
->first(); ->first();
if (!$user) { if($check)
return response(['message' => 'User Tidak Ditemukan'], 404); {
} $created_at = strtotime($check->created_at); // Konversi string waktu ke UNIX timestamp
$now = time(); // Waktu sekarang dalam UNIX timestamp
if ($request["new_password"] != $request["confirm_new_password"]) { // Hitung selisih waktu dalam menit
return response([ $diffInMinutes = ($now - $created_at) / 60;
'message' => "Password Tidak Sama"
], 404);
}
$user->update([ if ($diffInMinutes > 60) {
'password' => Hash::make($request->confirm_new_password), return ApiResponse::apiResponse('Not Found', $data, trans('Message.token_expired'), 404);
]); } else {
return response()->json($user); // Lanjutkan dengan proses pemulihan kata sandi
$user = User::where('email', $request->email)->first();
if ($user)
{
$newPassword = Hash::make($request->new_password);
$user->password = $newPassword;
$user->save();
return ApiResponse::apiResponse("Success", $data, trans('Message.success'), 200);
}
else
{
return ApiResponse::apiResponse('Not Found', $data, trans('Message.token_expired'), 404);
}
}
}
else
{
return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404);
}
}
} }
} }

View File

@@ -29,8 +29,9 @@ Route::prefix('v1')->group(function() {
}); });
}); });
//Route::post('forget-password', [AuthController::class, 'forgetPassword'])->name('forget-password'); Route::post('forget-password', [AuthController::class, 'forgetPassword']);
//Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email'); Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email');
Route::post('verify-code', [AuthController::class, 'verifCode']);
Route::middleware('auth:sanctum')->group(function () { Route::middleware('auth:sanctum')->group(function () {

View File

@@ -575,6 +575,12 @@ class CorporateController extends Controller
"file_url" => url('files/Template Import Request LOG.xlsx') "file_url" => url('files/Template Import Request LOG.xlsx')
]); ]);
break; break;
case 'final-log-invoice':
return Helper::responseJson([
'file_name' => "Template Import Invoice.xlsx",
"file_url" => url('files/Template Import Invoice.xlsx')
]);
break;
default: default:
return Helper::responseJson([], 'error', 404); return Helper::responseJson([], 'error', 404);
break; break;

View File

@@ -22,6 +22,8 @@ use Carbon\Carbon;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory; use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory; use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
use Box\Spout\Common\Entity\Style\CellAlignment;
use Exception; use Exception;
@@ -672,6 +674,181 @@ class RequestLogController extends Controller
]; ];
} }
public function importInvoice(Request $request)
{
if ($request->hasFile('file')) {
$file = $request->file('file');
$data = Excel::toArray([], $file);
$processedData = $this->processCategoryNames($data);
$importedRows = 0;
$result_rows = [];
$failedRows = [];
foreach ($processedData as $row) {
if($row['code'])
{
try {
$affectedRows = DB::table('request_logs')
->where('code','=', $row['code'])
->update([
'invoice_no' => $row['invoice_no'],
'billing_no' => $row['billing_no'],
]);
if ($affectedRows === 0) {
$row['code_error'] = '500';
$row['error'] = 'Gagal update karena data sudah ada ';
$result_rows[] = $row;
$failedRows[] = $row;
} else {
$importedRows += $affectedRows;
$row['code_error'] = '200';
$row['error'] = 'Sukses';
$result_rows[] = $row;
}
} catch (\Exception $e) {
$row['code_error'] = '500';
$row['error'] = $e->getMessage();
if(!$row['code'])
{
$row['error'] = 'Kolom Code wajib isi';
}
if(!$row['invoice_no'])
{
$row['error'] = 'No Invoice wajib isi';
}
if(!$row['billing_no'])
{
$row['error'] = 'No Billing wajib isi';
}
$result_rows[] = $row;
$failedRows[] = $row;
}
}
}
$response = [
'message' => 'File uploaded and data saved to database',
'metaData' => 'invoice',
'data' => [
'total_success_row' => $importedRows,
'total_failed_row' => count($failedRows),
'result_rows' => $result_rows,
],
];
return response()->json($response);
}
return response()->json(['error' => 'No file uploaded.']);
}
private function processCategoryNames($data)
{
$header = [];
$row = [];
for ($i = 1; $i < count($data[0]); $i++) {
$row[] = $data[0][$i];
$header[] = $data[0][0];
}
$filed = [];
foreach ($header[0] as $value)
{
$modelColumn = strtolower(preg_replace('/\s+/', '_', trim($value)));
$modelColumn = str_replace(['*', ' '], '', $modelColumn);
if($modelColumn)
{
$filed[] = $modelColumn;
}
}
$result = [];
foreach ($row as $subarray) {
$trimmedSubarray = [];
for ($i = 0; $i < count($filed); $i++) {
$trimmedSubarray[$filed[$i]] = $subarray[$i] ? $subarray[$i] : null;
}
$result[] = $trimmedSubarray;
}
return $result;
}
public function exportFiledInvoice(Request $request)
{
$writer = WriterEntityFactory::createXLSXWriter();
$writer->openToFile(public_path('files/Report-Data-Result-Import.xlsx'));
$header = [
'Code*',
'Inovice No*',
'Billing NO*',
'Ingest Code',
'Ingest Note'
];
$style = (new StyleBuilder())
->setFontBold()
// ->setFontSize(15)
// ->setFontColor(Color::BLUE)
// ->setShouldWrapText()
->setCellAlignment(CellAlignment::LEFT)
// ->setBackgroundColor(Color::YELLOW)
->build();
$headerRow = WriterEntityFactory::createRowFromArray($header, $style);
$writer->addRow($headerRow);
// ============================
foreach($request->params as $item)
{
$rowData = [
$item['code'],
$item['invoice_no'],
$item['billing_no'],
$item['code_error'],
$item['error']
];
$style = (new StyleBuilder())
//->setFontBold()
// ->setFontSize(15)
// ->setFontColor(Color::BLUE)
// ->setShouldWrapText()
->setCellAlignment(CellAlignment::LEFT)
// ->setBackgroundColor(Color::YELLOW)
->build();
$row = WriterEntityFactory::createRowFromArray($rowData, $style);
$writer->addRow($row);
}
$footer = [
'',
'',
'',
'',
''
];
$style = (new StyleBuilder())
->setFontBold()
// ->setFontSize(15)
// ->setFontColor(Color::BLUE)
// ->setShouldWrapText()
->setCellAlignment(CellAlignment::LEFT)
// ->setBackgroundColor(Color::YELLOW)
->build();
$footerRow = WriterEntityFactory::createRowFromArray($footer, $style);
$writer->addRow($footerRow);
$writer->close();
return Helper::responseJson([
'file_name' => 'Report-Data-Result-Import',
"file_url" => url('files/Report-Data-Result-Import.xlsx')
]);
}
public function claimRequestDetail($claimRequestId) public function claimRequestDetail($claimRequestId)
{ {
$status = DB::table('claim_requests') $status = DB::table('claim_requests')

View File

@@ -278,6 +278,8 @@ Route::prefix('internal')->group(function () {
Route::put('customer-service/request/final_log/{id}', [RequestLogController::class, 'deleteFinalLog']); Route::put('customer-service/request/final_log/{id}', [RequestLogController::class, 'deleteFinalLog']);
Route::get('customer-service/request/{id}/download', [RequestLogController::class, 'generateRequestLog']); Route::get('customer-service/request/{id}/download', [RequestLogController::class, 'generateRequestLog']);
Route::post('customer-service/request/import', [RequestLogController::class, 'importRequestLog']); Route::post('customer-service/request/import', [RequestLogController::class, 'importRequestLog']);
Route::post('customer-service/request/import-invoice', [RequestLogController::class, 'importInvoice']);
Route::post('customer-service/request/exportFiledInvoice', [RequestLogController::class, 'exportFiledInvoice']);
Route::get('customer-service/request/data', [RequestLogController::class, 'generateDataRequestLogExcel']); Route::get('customer-service/request/data', [RequestLogController::class, 'generateDataRequestLogExcel']);
Route::post('customer-service/request/{id}/add_file', [RequestLogController::class, 'requestFiles']); Route::post('customer-service/request/{id}/add_file', [RequestLogController::class, 'requestFiles']);
Route::post('customer-service/request/{id}/delete_file', [RequestLogController::class, 'deleteFiles']); Route::post('customer-service/request/{id}/delete_file', [RequestLogController::class, 'deleteFiles']);

View File

@@ -19,7 +19,8 @@ class AuthController extends Controller
{ {
private $url; private $url;
public function __construct() { public function __construct()
{
$this->url = $_ENV['LMS_APP_URL']; $this->url = $_ENV['LMS_APP_URL'];
} }
@@ -31,21 +32,34 @@ class AuthController extends Controller
// 'phone' => '', // 'phone' => '',
// 'otp' => 'required_with:phone', // 'otp' => 'required_with:phone',
'password' => 'required', 'password' => 'required',
'fcm_token' => 'required'
// 'otp' => 'required' // 'otp' => 'required'
]); ]);
// Login hit ke API linksehat, karena encrypt nya pake CI // Login hit ke API linksehat, karena encrypt nya pake CI
$response = Http::post($this->url.'login', [ $response = Http::post($this->url . 'login', [
'sEmail' => $request->phone_or_email, 'sEmail' => $request->phone_or_email,
'sPassword' => $request->password, 'sPassword' => $request->password,
'sRemember' => $request->remember 'sRemember' => $request->remember
]); ]);
$response = $response->json(); $response = $response->json();
if ($response['success']){ if ($response['success']) {
$user = User::with('detail') $user = User::with('detail')
->where('sEmail', $request->phone_or_email) ->where('sEmail', $request->phone_or_email)
->first(); ->first();
$user->notificationTokens()->updateOrCreate([
'device_id' => $request->device_id,
'token' => $request->fcm_token,
], [
'origin' => $request->origin,
'device_id' => $request->device_id,
'type' => $request->type,
'token' => $request->fcm_token,
'status' => $request->status,
]);
return Helper::responseJson( return Helper::responseJson(
data: [ data: [
'token' => $user->createToken('app')->plainTextToken, 'token' => $user->createToken('app')->plainTextToken,
@@ -58,18 +72,19 @@ class AuthController extends Controller
}; };
} }
public function forgetPassword(Request $request){ public function forgetPassword(Request $request)
{
$request->validate([ $request->validate([
'email' => 'required|email', 'email' => 'required|email',
]); ]);
// Login hit ke API linksehat, karena encrypt nya pake CI // Login hit ke API linksehat, karena encrypt nya pake CI
$response = Http::post($this->url.'forgot_password', [ $response = Http::post($this->url . 'forgot_password', [
'sEmail' => $request->email, 'sEmail' => $request->email,
]); ]);
$response = $response->json(); $response = $response->json();
if ($response['success']){ if ($response['success']) {
return Helper::responseJson( return Helper::responseJson(
data: [], data: [],
message: 'Message has been sent.' message: 'Message has been sent.'
@@ -79,7 +94,8 @@ class AuthController extends Controller
}; };
} }
public function resetPassword(Request $request){ public function resetPassword(Request $request)
{
$request->validate([ $request->validate([
'email' => 'required|email', 'email' => 'required|email',
'code' => 'required', 'code' => 'required',
@@ -87,14 +103,14 @@ class AuthController extends Controller
]); ]);
// Login hit ke API linksehat, karena encrypt nya pake CI // Login hit ke API linksehat, karena encrypt nya pake CI
$response = Http::post($this->url.'reset_password', [ $response = Http::post($this->url . 'reset_password', [
'sCode' => $request->code, 'sCode' => $request->code,
'sEmail' => $request->email, 'sEmail' => $request->email,
'sPassword' => $request->password, 'sPassword' => $request->password,
]); ]);
$response = $response->json(); $response = $response->json();
if ($response['success']){ if ($response['success']) {
return Helper::responseJson( return Helper::responseJson(
data: [], data: [],
message: 'Password telah di reset' message: 'Password telah di reset'
@@ -116,10 +132,9 @@ class AuthController extends Controller
'sPhone' => $request->phone_or_email, 'sPhone' => $request->phone_or_email,
'sVerificationCode' => $request->otp 'sVerificationCode' => $request->otp
]) ])
->first(); ->first();
if ($user){
if ($user) {
$updateVericationCode = User::with('detail') $updateVericationCode = User::with('detail')
->where([ ->where([
'sPhone' => $request->phone_or_email, 'sPhone' => $request->phone_or_email,
@@ -127,6 +142,9 @@ class AuthController extends Controller
])->update([ ])->update([
'sVerificationCode' => null, 'sVerificationCode' => null,
]); ]);
$user->fcm_token = $request->fcm_token;
$user->save();
return Helper::responseJson( return Helper::responseJson(
data: [ data: [
'token' => $user->createToken('app')->plainTextToken, 'token' => $user->createToken('app')->plainTextToken,
@@ -146,9 +164,9 @@ class AuthController extends Controller
->where('sPhone', $request->phone_or_email) ->where('sPhone', $request->phone_or_email)
->first(); ->first();
if ($user){ if ($user) {
// Request OTP ke API linksehat // Request OTP ke API linksehat
$response = Http::post($this->url.'generate_code', [ $response = Http::post($this->url . 'generate_code', [
'sPhone' => $request->phone_or_email 'sPhone' => $request->phone_or_email
]); ]);
@@ -168,9 +186,6 @@ class AuthController extends Controller
message: 'Nomor tidak ditemukan' message: 'Nomor tidak ditemukan'
); );
} }
} }
public function register(Request $request) public function register(Request $request)
@@ -256,4 +271,32 @@ class AuthController extends Controller
'token' => $user->createToken('app')->plainTextToken 'token' => $user->createToken('app')->plainTextToken
], message: 'Selamat Datang'); ], message: 'Selamat Datang');
} }
public function notificationToken(Request $request)
{
$user = Auth::user();
$origin = $request->origin;
$device_id = $request->device_id;
$type = $request->type;
$token = $request->token;
$status = $request->status;
$user->fcm_token = $request->token;
$user->save();
// $userUpdate = User::where
$user->notificationTokens()->updateOrCreate([
'device_id' => $device_id,
], [
'origin' => $origin,
'device_id' => $device_id,
'type' => $type,
'token' => $token,
'status' => $status,
]);
return response()->json([
'message' => 'Token berhasil disimpan',
]);
}
} }

View File

@@ -185,31 +185,55 @@ class ChatController extends Controller
$consultationStart = $livechat->start_date; $consultationStart = $livechat->start_date;
$consultationEnd = $livechat->end_date; $consultationEnd = $livechat->end_date;
$work = ''; $work = '';
$weight = '-';
$height = '-';
$address = ''; $address = '';
$maritalStatus = '';
} else { } else {
// Get nama pasien // Get nama pasien
$user = User::where('nID', $channel->member_id)->with('detail')->first(); $user = User::where('nID', $channel->member_id)->with('detail')->first();
$name = $user->sFirstName . ' ' . $user->sLastName; $name = $user->sFirstName . ' ' . $user->sLastName;
$urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; $gender = null;
$avatar = $user->detail->sImage ?? $urlAvatarDefault; if ($user->detail) {
$gender = DB::connection('oldlms')->table('tm_jenis_kelamin')->where('nID', $user->detail->nIDJenisKelamin)->first('sJenisKelamin'); $urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png';
if ($gender){ $avatar = $user->detail->sImage ?? $urlAvatarDefault;
$gender = $gender->sJenisKelamin; $gender = DB::connection('oldlms')->table('tm_jenis_kelamin')->where('nID', $user->detail->nIDJenisKelamin)->first('sJenisKelamin');
$age = Helper::calculateAge($user->detail->dTanggalLahir);
if ($gender){
$gender = $gender->sJenisKelamin;
}
$work = DB::connection('oldlms')->table('tm_pekerjaan')->where('nID', $user->detail->nIDPekerjaan)->first('sPekerjaan');
if($work){
$work = $work->sPekerjaan;
}
$maritalStatus = DB::connection('oldlms')->table('tm_status_pernikahan')->where('nID', $user->detail->sMartialStatus)->first('sStatusPernikahan');
if($maritalStatus){
$maritalStatus = $maritalStatus->sStatusPernikahan;
}
$weight = $user->detail->sWeight;
$height = $user->detail->sWeight;
} else {
$avatar = 'https://linksehat.dev/assets/img/users/male-avatar.png';
$age = '-';
$work = '-';
$maritalStatus = '-';
$weight = '-';
$height = '-';
} }
$age = Helper::calculateAge($user->detail->dTanggalLahir);
$question = $livechat->descriptions; $question = $livechat->descriptions;
$consultationStart = $livechat->start_date; $consultationStart = $livechat->start_date;
$consultationEnd = $livechat->end_date; $consultationEnd = $livechat->end_date;
$work = DB::connection('oldlms')->table('tm_pekerjaan')->where('nID', $user->detail->nIDPekerjaan)->first('sPekerjaan');
if($work){
$work = $work->sPekerjaan;
}
$address = DB::connection('oldlms')->table('tm_users_address')->where('nIDUser', $user->nID)->first('sAlamat'); $address = DB::connection('oldlms')->table('tm_users_address')->where('nIDUser', $user->nID)->first('sAlamat');
if($address){ if($address){
$address = $address->sAlamat; $address = $address->sAlamat;
} }
} }
// Ini Untul Chat // Ini Untul Chat
@@ -240,12 +264,22 @@ class ChatController extends Controller
'header' => $name, 'header' => $name,
'avatar' => $avatar, 'avatar' => $avatar,
'gender' => $gender, 'gender' => $gender,
'marital_status' => $maritalStatus,
'age' => $age, 'age' => $age,
'weight' => $weight,
'height' => $height,
'question' => $question, 'question' => $question,
'diseases' => [],
'medications' => [],
'allergy' => [],
'family_history' => [],
'start' => $consultationStart, 'start' => $consultationStart,
'end' => $consultationEnd, 'end' => $consultationEnd,
'work' => $work, 'work' => $work,
'address' => $address, 'address' => $address,
'consultation_id' => $livechat->id,
'consultation_status' => Helper::getStatusLivechat($livechat->status),
'health_sertificate' => $healthSertificate,
'chat' => $data->items(), 'chat' => $data->items(),
'pagination' => [ 'pagination' => [
'total' => $data->total(), 'total' => $data->total(),
@@ -256,8 +290,7 @@ class ChatController extends Controller
'to' => $data->lastItem(), 'to' => $data->lastItem(),
], ],
'summary' => $consultationSummary, 'summary' => $consultationSummary,
'livechat_id' => $livechat->id,
'health_sertificate' => $healthSertificate,
] ]
]); ]);
} }

View File

@@ -24,15 +24,18 @@ class AuthDoctorController extends Controller
{ {
$data = [ $data = [
'email' => $request->email, 'email' => $request->email,
'password' => $request->password 'password' => $request->password,
'fcm_token' => $request->fcm_token,
]; ];
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'email' => 'required|email', 'email' => 'required|email',
'password' => 'required' 'password' => 'required',
'fcm_token' => 'required'
], [ ], [
'email.required' => trans('Validation.required',['attribute' => 'Email']), 'email.required' => trans('Validation.required',['attribute' => 'Email']),
'email.email' => trans('Validation.email'), 'email.email' => trans('Validation.email'),
'password.required' => trans('Validation.required',['attribute' => 'Password']), 'password.required' => trans('Validation.required',['attribute' => 'Password']),
'fcm_token.required' => trans('Validation.required',['attribute' => 'FCM Token']),
]); ]);
if ($validator->fails()) if ($validator->fails())
@@ -50,6 +53,18 @@ class AuthDoctorController extends Controller
return ApiResponse::apiResponse('Bad Request', $data, trans('Message.password'), 400); return ApiResponse::apiResponse('Bad Request', $data, trans('Message.password'), 400);
} }
$user->notificationTokens()->updateOrCreate([
'device_id' => $request->device_id,
'token' => $request->fcm_token,
], [
'origin' => $request->origin,
'device_id' => $request->device_id,
'type' => $request->type,
'token' => $request->fcm_token,
'status' => $request->status,
]);
$res_data = [ $res_data = [
// 'user' => $user, // 'user' => $user,
'token' => $user->createToken('app')->plainTextToken 'token' => $user->createToken('app')->plainTextToken

View File

@@ -3,6 +3,7 @@
namespace Modules\Linksehat\Http\Controllers\Api\Doctor; namespace Modules\Linksehat\Http\Controllers\Api\Doctor;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Notifications\SendNotification;
use App\Models\User; use App\Models\User;
use App\Models\OLDLMS\User as UserLMS; use App\Models\OLDLMS\User as UserLMS;
use App\Models\Livechat; use App\Models\Livechat;
@@ -23,6 +24,8 @@ use Modules\HospitalPortal\Helpers\ApiResponse;
use App\Helpers\Helper; use App\Helpers\Helper;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Laravel\Firebase\Facades\Firebase;
class ChatDoctorController extends Controller class ChatDoctorController extends Controller
{ {
@@ -44,8 +47,12 @@ class ChatDoctorController extends Controller
if($chat) { if($chat) {
foreach($chat as $c){ foreach($chat as $c){
$patient = UserLMS::where('nID',$c->patient_id)->with('detail')->first(); $patient = UserLMS::where('nID',$c->patient_id)->with('detail')->first();
$urlAvatarDefault = $patient->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; if ( $patient->detail) {
$avatarMember = $patient->detail->sImage ?? $urlAvatarDefault; $urlAvatarDefault = $patient->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png';
$avatarMember = $patient->detail->sImage ?? $urlAvatarDefault;
} else {
$avatarMember = 'https://linksehat.dev/assets/img/users/male-avatar.png';
}
$arr['id'] = $c->id; $arr['id'] = $c->id;
$arr['patient_id'] = $patient->nID; $arr['patient_id'] = $patient->nID;
$arr['avatar'] = $avatarMember; $arr['avatar'] = $avatarMember;
@@ -128,6 +135,34 @@ class ChatDoctorController extends Controller
$livechat->status = 3; // Decline $livechat->status = 3; // Decline
// Menyimpan perubahan ke database // Menyimpan perubahan ke database
$livechat->save(); $livechat->save();
// Send Notification
$doctorId = $livechat->doctor_id;
$title = 'Decline Livechat';
$body = 'Decline Livechat';
$channel = Channel::where([
'member_id' => $livechat->patient_id,
'doctor_id' => $livechat->doctor_id
])->first();
$dataNotif = [
'channel_id' => $channel->id,
'livechat_id' => $livechat->id,
'type' => 'decline-chat'
];
$user = UserLMS::where('nID',$livechat->patient_id)->first();
if ($user) {
$user->notify(new SendNotification($title, $body, $dataNotif));
return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200);
} else {
return Helper::responseJson(
status: 'Not Found',
statusCode: 404,
message: 'Doctor not found.'
);
}
return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200); return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200);
} else { } else {
return response()->json(['message' => 'Livechat not found'], 404); return response()->json(['message' => 'Livechat not found'], 404);
@@ -143,7 +178,33 @@ class ChatDoctorController extends Controller
$livechat->accept_date = date('Y-m-d H:i:s'); // Accept $livechat->accept_date = date('Y-m-d H:i:s'); // Accept
// Menyimpan perubahan ke database // Menyimpan perubahan ke database
$livechat->save(); $livechat->save();
return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200);
// Send Notification
$doctorId = $livechat->doctor_id;
$title = 'Approve Request Livechat';
$body = 'Approve Livechat';
$channel = Channel::where([
'member_id' => $livechat->patient_id,
'doctor_id' => $livechat->doctor_id
])->first();
$dataNotif = [
'channel_id' => $channel->id,
'livechat_id' => $livechat->id,
'type' => 'approve-chat'
];
$user = UserLMS::where('nID',$livechat->patient_id)->first();
if ($user) {
$user->notify(new SendNotification($title, $body, $dataNotif));
return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200);
} else {
return Helper::responseJson(
status: 'Not Found',
statusCode: 404,
message: 'Doctor not found.'
);
}
} else { } else {
return response()->json(['message' => 'Livechat not found'], 404); return response()->json(['message' => 'Livechat not found'], 404);
} }
@@ -155,10 +216,36 @@ class ChatDoctorController extends Controller
if ($livechat) { if ($livechat) {
// Memperbarui atribut model // Memperbarui atribut model
$livechat->status = 6; // End Chat $livechat->status = 6; // End Chat
$livechat->end_date = date('Y-m-d H:i:s'); // Accept $livechat->end_date = date('Y-m-d H:i:s'); // Endchat
// Menyimpan perubahan ke database // Menyimpan perubahan ke database
$livechat->save(); $livechat->save();
return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200);
// Send Notification
$doctorId = $livechat->doctor_id;
$title = 'End Livechat';
$body = 'End Livechat';
$channel = Channel::where([
'member_id' => $livechat->patient_id,
'doctor_id' => $livechat->doctor_id
])->first();
$dataNotif = [
'channel_id' => $channel->id,
'livechat_id' => $livechat->id,
'type' => 'end-chat'
];
$user = UserLMS::where('nID',$livechat->patient_id)->first();
if ($user) {
$user->notify(new SendNotification($title, $body, $dataNotif));
return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200);
} else {
return Helper::responseJson(
status: 'Not Found',
statusCode: 404,
message: 'Doctor not found.'
);
}
} else { } else {
return response()->json(['message' => 'Livechat not found'], 404); return response()->json(['message' => 'Livechat not found'], 404);
} }
@@ -166,7 +253,6 @@ class ChatDoctorController extends Controller
public function summaryChat(Request $request) public function summaryChat(Request $request)
{ {
$livechat = Livechat::find($request->id); $livechat = Livechat::find($request->id);
if ($livechat) { if ($livechat) {
// Memperbarui atribut model // Memperbarui atribut model

View File

@@ -3,11 +3,14 @@
namespace Modules\Linksehat\Http\Controllers\Api; namespace Modules\Linksehat\Http\Controllers\Api;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Notifications\SendNotification;
use App\Models\Organization; use App\Models\Organization;
use App\Models\Speciality; use App\Models\Speciality;
use App\Models\Livechat; use App\Models\Livechat;
use App\Models\Channel; use App\Models\Channel;
use App\Models\UserChannel; use App\Models\UserChannel;
use App\Models\User as UserAso;
use App\Models\OLDLMS\User;
use Illuminate\Contracts\Support\Renderable; use Illuminate\Contracts\Support\Renderable;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -285,16 +288,15 @@ class DuitkuController extends Controller
'created_at' => date('Y-m-d H:i:s') 'created_at' => date('Y-m-d H:i:s')
]); ]);
if ($notif->resultCode == "00") { if ($notif->resultCode == "00") { // berhasil melakukan pembayaran
// Action Success // Action Success
$livechat = Livechat::where('uuid', $notif->merchantOrderId)->first(); $livechat = Livechat::where('uuid', $notif->merchantOrderId)->first();
// Update status pembayaran // Update status pembayaran
$livechat->payment_method = $notif->paymentCode; $livechat->payment_method = $notif->paymentCode;
$livechat->status = 5; // success payment $livechat->status = 5; // success payment
$livechat->save();
// Update start chat // Update start chat
$livechat->start_date = date('Y-m-d H:i:s'); $livechat->start_date = date('Y-m-d H:i:s');
$livechat->save();
// Buat dan simpan data channel ke dalam tabel // Buat dan simpan data channel ke dalam tabel
$channel = Channel::updateOrCreate([ $channel = Channel::updateOrCreate([
'name' => $livechat->patient_id .'_' . $request->doctor_id, 'name' => $livechat->patient_id .'_' . $request->doctor_id,
@@ -330,6 +332,23 @@ class DuitkuController extends Controller
] ]
); );
// Send Notification
$doctorId = $livechat->doctor_id;
$userDokter = UserAso::find($doctorId);
$title = 'Payment Succes Livechat';
$patient = User::where('nID', $livechat->patient_id)->first();
$body = 'Payment Succes Livechat from ' . $patient->sFirstName . ' ' . $patient->sLastName;
$channel = Channel::where([
'member_id' => $livechat->patient_id,
'doctor_id' => $livechat->doctor_id
])->first();
$dataNotif = [
'channel_id' => $channel->id,
'livechat_id' => $livechat->id,
'type' => 'success-payment'
];
$userDokter->notify(new SendNotification($title, $body, $dataNotif));
// Berikan respons yang sesuai ke klien // Berikan respons yang sesuai ke klien
return response()->json(['message' => 'Channel created successfully', 'channel' => $channel]); return response()->json(['message' => 'Channel created successfully', 'channel' => $channel]);
@@ -341,6 +360,22 @@ class DuitkuController extends Controller
$livechat->status = 7; // failed payment $livechat->status = 7; // failed payment
$livechat->save(); $livechat->save();
// Send Notification
$doctorId = $livechat->doctor_id;
$userDokter = UserAso::find($doctorId);
$title = 'Payment Failed Livechat';
$patient = User::where('nID', $livechat->patient_id)->first();
$body = 'Payment Failed Livechat from ' . $patient->sFirstName . ' ' . $patient->sLastName;
$channel = Channel::where([
'member_id' => $livechat->patient_id,
'doctor_id' => $livechat->doctor_id
])->first();
$dataNotif = [
'channel_id' => $channel->id,
'livechat_id' => $livechat->id,
'type' => 'failed-payment'
];
$userDokter->notify(new SendNotification($title, $body, $dataNotif));
return response()->json(['message' => 'User Gagal melakukan pembayaran']); return response()->json(['message' => 'User Gagal melakukan pembayaran']);
} }

View File

@@ -2,6 +2,7 @@
namespace Modules\Linksehat\Http\Controllers\Api; namespace Modules\Linksehat\Http\Controllers\Api;
use App\Notifications\SendNotification;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Helpers\DuitkuHelper; use App\Helpers\DuitkuHelper;
use App\Services\Duitku; use App\Services\Duitku;
@@ -9,8 +10,10 @@ use App\Models\Organization;
use App\Models\PractitionerRole; use App\Models\PractitionerRole;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\PaymentsMethods; use App\Models\PaymentsMethods;
use App\Models\Channel;
use App\Models\Livechat; use App\Models\Livechat;
use App\Models\OLDLMS\User; use App\Models\OLDLMS\User;
use App\Models\User as UserAso;
use Illuminate\Routing\Controller; use Illuminate\Routing\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@@ -20,6 +23,9 @@ use Illuminate\Support\Facades\Validator;
use App\Http\Controllers\DuitkuController; use App\Http\Controllers\DuitkuController;
use DB; use DB;
use Illuminate\Contracts\Filesystem\Cloud;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Laravel\Firebase\Facades\Firebase;
use Str; use Str;
class LivechatController extends Controller class LivechatController extends Controller
@@ -133,11 +139,11 @@ class LivechatController extends Controller
'descriptions' => 'required', 'descriptions' => 'required',
], [ ], [
'doctor_id.required' => 'ID Dokter harus diisi', 'doctor_id.required' => 'ID Dokter harus diisi',
'patient_id.required' => 'ID Dokter harus diisi', 'patient_id.required' => 'ID Pasien harus diisi',
'descriptions.required' => 'Description harus diisi', 'descriptions.required' => 'Deskripsi harus diisi',
]); ]);
if ($validator->fails()){ if ($validator->fails()) {
return Helper::responseJson( return Helper::responseJson(
status: 'Bad Request', status: 'Bad Request',
statusCode: 400, statusCode: 400,
@@ -148,24 +154,49 @@ class LivechatController extends Controller
/** /**
* Status Livechat * Status Livechat
* 1=Request, 2=Accept, 3=Decline, 4=Waiting Payment, 5=Success Payment, 6 = End Chat * 1=Request, 2=Accept, 3=Decline, 4=Waiting Payment, 5=Success Payment, 6 = End Chat, 7=Payment Failed
*/ */
$timezone = date_default_timezone_get(); $timezone = date_default_timezone_get();
$data['request_date'] = date('Y-m-d H:i:s'); $data['request_date'] = date('Y-m-d H:i:s');
$data['timezone'] = $timezone; $data['timezone'] = $timezone;
$data['uuid'] = (string) Str::orderedUuid(); $data['uuid'] = (string) Str::orderedUuid();
$data['status'] = 1; $data['status'] = 1; // Request
$livechat = Livechat::create($data); $livechat = Livechat::create($data);
$doctor = $livechat->doctor; $doctor = $livechat->doctor;
$data = [ $responseData = [
'id' => $livechat->id, 'id' => $livechat->id,
'request_date' => $livechat->request_date, 'request_date' => $livechat->request_date,
'image_path' =>'https' 'image_path' => 'https' // Ganti dengan path yang benar jika ada
]; ];
// Send Notification
$doctorId = $livechat->doctor_id;
$user = UserAso::find($doctorId);
$title = 'New Request Livechat';
$patient = User::where('nID', $livechat->patient_id)->first();
$body = 'Request Livechat from ' . $patient->sFirstName . ' ' . $patient->sLastName;
$channel = Channel::where([
'member_id' => $livechat->patient_id,
'doctor_id' => $livechat->doctor_id
])->first();
$dataNotif = [
'channel_id' => $channel->id,
'livechat_id' => $livechat->id,
'type' => 'request-chat'
];
return Helper::responseJson(data: $data); if ($user) {
$user->notify(new SendNotification($title, $body, $dataNotif));
return Helper::responseJson(data: $responseData);
} else {
return Helper::responseJson(
status: 'Not Found',
statusCode: 404,
message: 'Doctor not found.'
);
}
} }
} }
@@ -290,7 +321,6 @@ class LivechatController extends Controller
// Membuat invoice menggunakan DuitkuHelper // Membuat invoice menggunakan DuitkuHelper
$duitku = DuitkuHelper::createInvoice($data); $duitku = DuitkuHelper::createInvoice($data);
return response()->json(['success' => true, 'data' => $duitku], 200); return response()->json(['success' => true, 'data' => $duitku], 200);
} catch (Exception $e) { } catch (Exception $e) {
// Menangkap error dan mengembalikan respon error // Menangkap error dan mengembalikan respon error

View File

@@ -72,13 +72,14 @@ Route::prefix('linksehat')->group(function () {
Route::get('doctors/{id}', 'show')->name('doctors.show'); Route::get('doctors/{id}', 'show')->name('doctors.show');
}); });
Route::middleware('auth:sanctum')->group(function () { Route::middleware(['auth:sanctum', 'linksehat.old.auth'])->group(function () {
Route::post('notification-token', [AuthController::class,'notificationToken']);
Route::get('profile/{id}', [ProfileController::class, 'index'])->name('profile'); Route::get('profile/{id}', [ProfileController::class, 'index'])->name('profile');
Route::get('change-profile/{id}', [ProfileController::class, 'changeProfile'])->name('change-profile'); Route::get('change-profile/{id}', [ProfileController::class, 'changeProfile'])->name('change-profile');
Route::post('profile', [ProfileController::class, 'update'])->name('profile.update'); Route::post('profile', [ProfileController::class, 'update'])->name('profile.update');
Route::post('profile-add', [ProfileController::class, 'store'])->name('profile.store'); Route::post('profile-add', [ProfileController::class, 'store'])->name('profile.store');
Route::post('notification-tokens/delete/{id}', [NotificationTokenController::class, 'destroy'])->name('profile.delete.token'); // Route::post('notification-tokens/delete/{id}', [NotificationTokenController::class, 'destroy'])->name('profile.delete.token');
Route::post('notification-tokens', [NotificationTokenController::class, 'store'])->name('profile.store.token'); // Route::post('notification-tokens', [NotificationTokenController::class, 'store'])->name('profile.store.token');
Route::apiResource('appointment', AppointmentController::class); Route::apiResource('appointment', AppointmentController::class);
Route::apiResource('families', PersonController::class)->except(['destroy']); Route::apiResource('families', PersonController::class)->except(['destroy']);
@@ -114,15 +115,14 @@ Route::prefix('linksehat')->group(function () {
Route::controller(ChatController::class)->group(function () { Route::controller(ChatController::class)->group(function () {
Route::post('livechat/send-message', 'sendMessage'); Route::post('livechat/send-message', 'sendMessage');
Route::get('livechat/get-message', 'getMessage'); Route::get('livechat/get-message', 'getMessage');
Route::post('livechat/channel','createChannel'); Route::post('livechat/channel', 'createChannel');
Route::get('livechat/channel','listChannel'); Route::get('livechat/channel', 'listChannel');
Route::get('livechat/{id}/health-sertificate','downloadHealtcare'); Route::get('livechat/{id}/health-sertificate', 'downloadHealtcare');
}); });
Route::post('create-invoice-duitku', [DuitkuController::class, 'createInvoice']); Route::post('create-invoice-duitku', [DuitkuController::class, 'createInvoice']);
Route::post('check-status-duitku', [DuitkuController::class, 'checkStatus']); Route::post('check-status-duitku', [DuitkuController::class, 'checkStatus']);
}); });
Route::post('payment-method-duitku', [DuitkuController::class, 'paymentMethod']); Route::post('payment-method-duitku', [DuitkuController::class, 'paymentMethod']);
@@ -130,9 +130,9 @@ Route::prefix('linksehat')->group(function () {
Route::get('redirect-duitku', [DuitkuController::class, 'redirect']); Route::get('redirect-duitku', [DuitkuController::class, 'redirect']);
//DOCTOR API //DOCTOR API
Route::prefix('doctor')->group(function() { Route::prefix('doctor')->group(function () {
//Version 1.0 //Version 1.0
Route::prefix('v1')->group(function() { Route::prefix('v1')->group(function () {
Route::middleware(Authentication::class)->group(function () { Route::middleware(Authentication::class)->group(function () {
Route::controller(AuthDoctorController::class)->group(function () { Route::controller(AuthDoctorController::class)->group(function () {
Route::post('login', 'login'); Route::post('login', 'login');
@@ -163,7 +163,6 @@ Route::prefix('linksehat')->group(function () {
Route::post('resend-code', 'forgotPassword'); Route::post('resend-code', 'forgotPassword');
Route::post('reset-password', 'resetPassword'); Route::post('reset-password', 'resetPassword');
}); });
}); });
}); });;
;}); });

View File

@@ -498,5 +498,34 @@ class Helper
return $start->diffInDays($end) + 1; return $start->diffInDays($end) + 1;
} }
public static function getStatusLivechat($status) {
switch ($status) {
case 1:
return 'Requested'; // Clearer status name
break;
case 2:
return 'Accepted';
break;
case 3:
return 'Declined';
break;
case 4:
return 'Waiting Payment';
break;
case 5:
return 'Payment Successful';
break;
case 6:
return 'Chat Ended';
break;
case 7:
return 'Payment Failed';
break;
default:
return 'Unknown Status'; // Handle unknown statuses
break;
}
}
} }

View File

@@ -16,6 +16,7 @@ class Kernel extends HttpKernel
protected $middleware = [ protected $middleware = [
// \App\Http\Middleware\TrustHosts::class, // \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class, \App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\LinksehatOldAuthMiddleware::class,
\Illuminate\Http\Middleware\HandleCors::class, \Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,

View File

@@ -5,6 +5,8 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Models\OLDLMS\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;
class LinksehatOldAuthMiddleware class LinksehatOldAuthMiddleware
{ {
@@ -17,12 +19,12 @@ class LinksehatOldAuthMiddleware
*/ */
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if ($request->header('authorization') == 'Bearer LpMbGm0NQvFC3lUBiy1Ch3NzS0CIPSmanR12FcdP') { // if ($request->header('authorization') == 'Bearer LpMbGm0NQvFC3lUBiy1Ch3NzS0CIPSmanR12FcdP') {
Auth::loginUsingId(1); // Auth::loginUsingId(1);
return $next($request); // return $next($request);
} // }
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
return abort(401, "Unauthenticated"); return $next($request);
} }
} }

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Models\OLDLMS;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class NotificationToken extends Model
{
use HasFactory;
protected $connection = 'oldlms';
protected $fillable = [
'origin',
'type',
'token',
'status',
'device_id'
];
protected $hidden = [
'notifiabletoken_type',
'notifiabletoken_id',
'created_at',
'updated_at'
];
public function notifiabletoken()
{
return $this->morphTo();
}
}

View File

@@ -1,7 +1,6 @@
<?php <?php
namespace App\Models\OLDLMS; namespace App\Models\OLDLMS;
use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
@@ -15,7 +14,7 @@ use Illuminate\Notifications\Notifiable;
class User extends Authenticatable class User extends Authenticatable
{ {
use HasFactory, SoftDeletes, HasApiTokens, HasRoles, Notifiable, Notifiable; use HasFactory, SoftDeletes, HasApiTokens, HasRoles, Notifiable;
const CREATED_AT = 'dCreateOn'; const CREATED_AT = 'dCreateOn';
const UPDATED_AT = 'dUpdateOn'; const UPDATED_AT = 'dUpdateOn';
@@ -39,6 +38,7 @@ class User extends Authenticatable
'nIDHubunganKeluarga', 'nIDHubunganKeluarga',
'dUpdateOn', 'dUpdateOn',
'sIPAddress', 'sIPAddress',
'fcm_token',
]; ];
protected function fullName(): Attribute protected function fullName(): Attribute
@@ -72,4 +72,9 @@ class User extends Authenticatable
{ {
return $this->morphMany(NotificationToken::class, 'notifiabletoken'); return $this->morphMany(NotificationToken::class, 'notifiabletoken');
} }
public function routeNotificationForFcm()
{
return $this->notificationTokens()->pluck('token')->toArray();
}
} }

View File

@@ -102,13 +102,18 @@ class User extends Authenticatable
return $this->hasMany(Person::class, 'owner_user_id'); return $this->hasMany(Person::class, 'owner_user_id');
} }
public function getOrganization()
{
return $this->hasOne(OrganizationUser::class, 'user_id');
}
public function notificationTokens() public function notificationTokens()
{ {
return $this->morphMany(NotificationToken::class, 'notifiabletoken'); return $this->morphMany(NotificationToken::class, 'notifiabletoken');
} }
public function getOrganization() public function routeNotificationForFcm()
{ {
return $this->hasOne(OrganizationUser::class, 'user_id'); return $this->notificationTokens()->pluck('token')->toArray();
} }
} }

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Laravel\Firebase\Facades\Firebase;
use NotificationChannels\Fcm\FcmChannel;
use NotificationChannels\Fcm\FcmMessage;;
use NotificationChannels\Fcm\Resources\AndroidConfig;
use NotificationChannels\Fcm\Resources\AndroidFcmOptions;
use NotificationChannels\Fcm\Resources\AndroidNotification;
use NotificationChannels\Fcm\Resources\ApnsConfig;
use NotificationChannels\Fcm\Resources\ApnsFcmOptions;
use NotificationChannels\Fcm\Resources\Notification as FcmNotification;
class SendNotification extends Notification
{
use Queueable;
private $title;
private $body;
private $data;
public function __construct($title, $body, $data)
{
$this->title = $title;
$this->body = $body;
$this->data = is_array($data) ? $data : (array) $data; // Pastikan data adalah array
}
public function via($notifiable)
{
return [FcmChannel::class];
}
// public function toFcm($notifiable)
// {
// return FcmMessage::create()
// ->setData($this->data)
// ->setNotification([
// 'title' => $this->title,
// 'body' => $this->body,
// ]);
// }
public function toFcm($notifiable)
{
$deviceTokens = $notifiable->routeNotificationFor('fcm');
$notification = [
'title' => $this->title,
'body' => $this->body,
];
if (count($deviceTokens)){
foreach($deviceTokens as $token) {
$message = CloudMessage::withTarget('token', $token)
->withNotification($notification) // optional
->withData($this->data);
Firebase::messaging()->send($message);
}
}
$dataFcm = FcmMessage::create()
->setToken($deviceTokens[0])
->setData([])
->setNotification(
FcmNotification::create()
->setTitle('ini title')
->setBody('ini body')
)
->setAndroid(
AndroidConfig::create()
->setFcmOptions(AndroidFcmOptions::create()->setAnalyticsLabel('analytics'))
->setNotification(AndroidNotification::create()->setColor('#0A0A0A'))
)->setApns(
ApnsConfig::create()
->setFcmOptions(ApnsFcmOptions::create()->setAnalyticsLabel('analytics_ios'))
);
return $dataFcm;
}
public function fcmProject($notifiable, $message){
return 'app';
}
}

View File

@@ -206,7 +206,7 @@ class AppServiceProvider extends ServiceProvider
$this->logAuditTrail($model, 'deleted'); $this->logAuditTrail($model, 'deleted');
}); });
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); // Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
} }
private function logAuditTrail($model, $action) private function logAuditTrail($model, $action)

View File

@@ -14,6 +14,8 @@
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"h4cc/wkhtmltoimage-amd64": "^0.12.4", "h4cc/wkhtmltoimage-amd64": "^0.12.4",
"h4cc/wkhtmltopdf-amd64": "0.12.x", "h4cc/wkhtmltopdf-amd64": "0.12.x",
"kreait/firebase-php": "^6.9",
"laravel-notification-channels/fcm": "^2.1",
"laravel/framework": "^9.11", "laravel/framework": "^9.11",
"laravel/sanctum": "^2.15", "laravel/sanctum": "^2.15",
"laravel/socialite": "^5.5", "laravel/socialite": "^5.5",

1902
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -202,6 +202,11 @@ return [
App\Providers\EventServiceProvider::class, App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,
/**
* Package Firebase Cloud Message
*/
NotificationChannels\Fcm\FcmServiceProvider::class,
], ],
/* /*

5
config/fcm.php Normal file
View File

@@ -0,0 +1,5 @@
<?php
return [
'server_key' => env('FCM_SERVER_KEY'),
];

192
config/firebase.php Normal file
View File

@@ -0,0 +1,192 @@
<?php
declare(strict_types=1);
return [
/*
* ------------------------------------------------------------------------
* Default Firebase project
* ------------------------------------------------------------------------
*/
'default' => env('FIREBASE_PROJECT', 'app'),
/*
* ------------------------------------------------------------------------
* Firebase project configurations
* ------------------------------------------------------------------------
*/
'projects' => [
'app' => [
/*
* ------------------------------------------------------------------------
* Credentials / Service Account
* ------------------------------------------------------------------------
*
* In order to access a Firebase project and its related services using a
* server SDK, requests must be authenticated. For server-to-server
* communication this is done with a Service Account.
*
* If you don't already have generated a Service Account, you can do so by
* following the instructions from the official documentation pages at
*
* https://firebase.google.com/docs/admin/setup#initialize_the_sdk
*
* Once you have downloaded the Service Account JSON file, you can use it
* to configure the package.
*
* If you don't provide credentials, the Firebase Admin SDK will try to
* auto-discover them
*
* - by checking the environment variable FIREBASE_CREDENTIALS
* - by checking the environment variable GOOGLE_APPLICATION_CREDENTIALS
* - by trying to find Google's well known file
* - by checking if the application is running on GCE/GCP
*
* If no credentials file can be found, an exception will be thrown the
* first time you try to access a component of the Firebase Admin SDK.
*
*/
'credentials' => [
'file' => env('FIREBASE_CREDENTIALS', env('GOOGLE_APPLICATION_CREDENTIALS')),
/*
* If you want to prevent the auto discovery of credentials, set the
* following parameter to false. If you disable it, you must
* provide a credentials file.
*/
'auto_discovery' => true,
],
/*
* ------------------------------------------------------------------------
* Firebase Auth Component
* ------------------------------------------------------------------------
*/
'auth' => [
'tenant_id' => env('FIREBASE_AUTH_TENANT_ID'),
],
/*
* ------------------------------------------------------------------------
* Firebase Realtime Database
* ------------------------------------------------------------------------
*/
'database' => [
/*
* In most of the cases the project ID defined in the credentials file
* determines the URL of your project's Realtime Database. If the
* connection to the Realtime Database fails, you can override
* its URL with the value you see at
*
* https://console.firebase.google.com/u/1/project/_/database
*
* Please make sure that you use a full URL like, for example,
* https://my-project-id.firebaseio.com
*/
'url' => env('FIREBASE_DATABASE_URL'),
/*
* As a best practice, a service should have access to only the resources it needs.
* To get more fine-grained control over the resources a Firebase app instance can access,
* use a unique identifier in your Security Rules to represent your service.
*
* https://firebase.google.com/docs/database/admin/start#authenticate-with-limited-privileges
*/
// 'auth_variable_override' => [
// 'uid' => 'my-service-worker'
// ],
],
'dynamic_links' => [
/*
* Dynamic links can be built with any URL prefix registered on
*
* https://console.firebase.google.com/u/1/project/_/durablelinks/links/
*
* You can define one of those domains as the default for new Dynamic
* Links created within your project.
*
* The value must be a valid domain, for example,
* https://example.page.link
*/
'default_domain' => env('FIREBASE_DYNAMIC_LINKS_DEFAULT_DOMAIN'),
],
/*
* ------------------------------------------------------------------------
* Firebase Cloud Storage
* ------------------------------------------------------------------------
*/
'storage' => [
/*
* Your project's default storage bucket usually uses the project ID
* as its name. If you have multiple storage buckets and want to
* use another one as the default for your application, you can
* override it here.
*/
'default_bucket' => env('FIREBASE_STORAGE_DEFAULT_BUCKET'),
],
/*
* ------------------------------------------------------------------------
* Caching
* ------------------------------------------------------------------------
*
* The Firebase Admin SDK can cache some data returned from the Firebase
* API, for example Google's public keys used to verify ID tokens.
*
*/
'cache_store' => env('FIREBASE_CACHE_STORE', 'file'),
/*
* ------------------------------------------------------------------------
* Logging
* ------------------------------------------------------------------------
*
* Enable logging of HTTP interaction for insights and/or debugging.
*
* Log channels are defined in config/logging.php
*
* Successful HTTP messages are logged with the log level 'info'.
* Failed HTTP messages are logged with the the log level 'notice'.
*
* Note: Using the same channel for simple and debug logs will result in
* two entries per request and response.
*/
'logging' => [
'http_log_channel' => env('FIREBASE_HTTP_LOG_CHANNEL'),
'http_debug_log_channel' => env('FIREBASE_HTTP_DEBUG_LOG_CHANNEL'),
],
/*
* ------------------------------------------------------------------------
* HTTP Client Options
* ------------------------------------------------------------------------
*
* Behavior of the HTTP Client performing the API requests
*/
'http_client_options' => [
/*
* Use a proxy that all API requests should be passed through.
* (default: none)
*/
'proxy' => env('FIREBASE_HTTP_CLIENT_PROXY'),
/*
* Set the maximum amount of seconds (float) that can pass before
* a request is considered timed out
*
* The default time out can be reviewed at
* https://github.com/kreait/firebase-php/blob/6.x/src/Firebase/Http/HttpClientOptions.php
*/
'timeout' => env('FIREBASE_HTTP_CLIENT_TIMEOUT'),
],
],
],
];

View File

@@ -37,4 +37,9 @@ return [
'redirect' => env('GOOGLE_REDIRECT_URI'), 'redirect' => env('GOOGLE_REDIRECT_URI'),
], ],
'fcm' => [
'key' => env('FCM_SERVER_KEY'),
'sender_id' => env('FCM_SENDER_ID'),
],
]; ];

View File

@@ -17,6 +17,7 @@ return new class extends Migration
$table->id(); $table->id();
$table->morphs('notifiabletoken', 'notifiabletoken'); $table->morphs('notifiabletoken', 'notifiabletoken');
$table->string('origin'); $table->string('origin');
$table->string('device_id')->nullable();
$table->string('type'); $table->string('type');
$table->string('token'); $table->string('token');
$table->string('status'); $table->string('status');

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('notifications', function (Blueprint $table) {
$table->text('data')->after('notifiable_id')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('notifications', function (Blueprint $table) {
$table->dropColumn('data');
});
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('fcm_token')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('fcm_token');
});
}
};

View File

@@ -115,13 +115,12 @@ export default function List() {
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
const [paramImport, setParamImport] = useState('');
const handleImportButton = () => { const handleImportButton = (param:any) => {
setParamImport(param);
if (importForm?.current) { if (importForm?.current) {
handleClose(); handleClose();
importForm.current ? importForm.current.click() : console.log('No File selected'); importForm.current ? importForm.current.click() : console.log('No File selected');
} else {
alert('No file selected');
} }
}; };
@@ -144,12 +143,18 @@ export default function List() {
formData.append('file', importForm.current?.files[0]); formData.append('file', importForm.current?.files[0]);
setImportLoading(true); setImportLoading(true);
let url = 'claim-requests/import';
if(paramImport == 'invoice')
{
url = 'customer-service/request/import-invoice'
}
axios axios
.post(`claim-requests/import`, formData) .post(`${url}`, formData)
.then((response) => { .then((response) => {
handleCancelImportButton(); handleCancelImportButton();
loadDataTableData(); // loadDataTableData();
setImportResult(response.data); setImportResult(response.data);
setParamImport(response.data.metaData);
// alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows');
setImportLoading(false); setImportLoading(false);
}) })
@@ -166,6 +171,28 @@ export default function List() {
} }
}; };
const handleExportReportFiled = async () => {
await axios
.post('customer-service/request/exportFiledInvoice', { params: importResult?.data.result_rows })
.then((res) => {
enqueueSnackbar('Data berhasil di Export', {
variant: 'success',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
});
setImportLoading(false);
document.location.href = res.data.data.file_url;
})
.catch((err) =>
enqueueSnackbar('Data Gagal di Export', {
variant: 'error',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
})
);
};
const handleGetTemplate = (type :string) => { const handleGetTemplate = (type :string) => {
axios.get('corporates/import-document-example/' + type) axios.get('corporates/import-document-example/' + type)
.then((response) => { .then((response) => {
@@ -220,9 +247,11 @@ export default function List() {
'aria-labelledby': 'basic-button', 'aria-labelledby': 'basic-button',
}} }}
> >
<MenuItem onClick={handleImportButton}>Import</MenuItem> <MenuItem onClick={() => {handleImportButton('claim')}}>Import</MenuItem>
<MenuItem onClick={() => {handleGetTemplate('claim-request')}}>Download Template</MenuItem> <MenuItem onClick={() => {handleGetTemplate('claim-request')}}>Download Template</MenuItem>
<MenuItem onClick={() => {handleGetData('data-plan-benefit')}}>Download Claim Request</MenuItem> <MenuItem onClick={() => {handleGetData('data-plan-benefit')}}>Download Claim Request</MenuItem>
<MenuItem onClick={() => {handleImportButton('invoice')}}>Import Invoice</MenuItem>
<MenuItem onClick={() => {handleGetTemplate('final-log-invoice')}}>Download Template Invoice</MenuItem>
</Menu> </Menu>
{/* <Button {/* <Button
variant="contained" variant="contained"
@@ -265,16 +294,35 @@ export default function List() {
</LoadingButton> </LoadingButton>
</Stack> </Stack>
)} )}
{importResult && ( {importResult && importResult?.metaData == 'invoice' ? (
<Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
<Box sx={{ color: 'text.secondary' }}>
Last Import Result :{' '}
<Box sx={{ color: 'success.main', display: 'inline' }}>
{importResult.data.total_success_row ?? 0}
</Box>{' '}
Row Processed,{' '}
<Box sx={{ color: 'error.main', display: 'inline' }}>
{importResult.data.total_failed_row}
</Box>{' '}
Failed,
{/* {importResult.data.failed_rows.map((row, index) => (
<Typography variant='body' key={index} color="error"> [Code={row.code ? row.code : 'Required'}]</Typography>
))} */}
&nbsp;Report:
&nbsp;<u onClick={handleExportReportFiled} style={{cursor:'pointer'}}>Download Data Result Import</u>
</Box>
</Stack>
) : importResult ? (
<Stack direction={'row'} sx={{ px: 2, pb: 2 }}> <Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
<Box sx={{ color: 'text.secondary' }}> <Box sx={{ color: 'text.secondary' }}>
Last Import Result Report :{' '} Last Import Result Report {paramImport} :{' '}
<a href={importResult.result_file?.url ?? '#'}> <a href={importResult?.result_file?.url ?? '#'}>
{importResult.result_file?.name ?? '-'} {importResult?.result_file?.name ?? '-'}
</a> </a>
</Box> </Box>
</Stack> </Stack>
)} ):''}
</div> </div>
); );
} }

View File

@@ -59,5 +59,24 @@
"txtApprove": "Approve", "txtApprove": "Approve",
"txtDialogConfirmation": "Are you sure you want to proceed with this action?", "txtDialogConfirmation": "Are you sure you want to proceed with this action?",
"txtStartDate": "Start Date", "txtStartDate": "Start Date",
"txtEndDate": "End Date" "txtEndDate": "End Date",
"txtHelp1" : "Has problem with your account?",
"txtLupaSandi": "Forgot password?",
"txtIngatkanSaya": "Remember me",
"txtLogin": "Login",
"txtForgotYourPassword": "Forgot your password?",
"txtPleaseEnterPassword": "Please enter the email address associated with your account and We will email you a code to reset your password.",
"txtBack": "Back",
"txtSuccessSend": "Request sent successfully",
"txtCodeConfirm": "We have sent a confirmation email to",
"txtPleasCheck": "Please check your email.",
"txtCheckEmail": "Please check your email!",
"txtEmail": "We have emailed a 6-digit confirmation code and check spam folder, please enter the code in below box to verify your email.",
"txtDont": "Dont have a code?",
"txtResendCode": "Resend code",
"txtSecond": "Second",
"txtPleaseInput": "Please enter your new password.",
"txtNewPassword": "New Password",
"txtConfPassword": "Confirm Kata Sandi"
} }

View File

@@ -59,5 +59,23 @@
"txtApprove": "Terima", "txtApprove": "Terima",
"txtDialogConfirmation": "Apakah Anda yakin ingin melanjutkan tindakan ini?", "txtDialogConfirmation": "Apakah Anda yakin ingin melanjutkan tindakan ini?",
"txtStartDate": "Tanggal Mulai", "txtStartDate": "Tanggal Mulai",
"txtEndDate": "Tanggal Akhir" "txtEndDate": "Tanggal Akhir",
"txtHelp1" : "Punya masalah dengan akun Anda?",
"txtLupaSandi": "Lupa sandi?",
"txtIngatkanSaya": "Ingatkan saya",
"txtLogin": "Masuk",
"txtForgotYourPassword": "Lupa password anda?",
"txtPleaseEnterPassword": "Silakan masukkan alamat email yang terkait dengan akun Anda dan Kami akan mengirimkan email berisi kode untuk mengatur ulang kata sandi Anda.",
"txtBack": "Kembali",
"txtSuccessSend": "Permintaan berhasil dikirim",
"txtCodeConfirm": "Kami telah mengirimkan email konfirmasi ke",
"txtPleasCheck": "Mohon cek email Anda.",
"txtCheckEmail": "Mohon periksa email Anda!",
"txtEmail": "Kami telah mengirimkan kode konfirmasi 6 digit melalui email cek juga difolder spam, silakan masukkan kode di kotak bawah ini untuk memverifikasi email Anda.",
"txtDont": "Tidak mendapatkan kode?",
"txtResendCode": "Kirim ulang kode",
"txtSecond": "Detik",
"txtPleaseInput": "Mohon masukan kata sandi baru Anda.",
"txtNewPassword": "Kata Sandi Baru",
"txtConfPassword": "Konfirmasi Kata Sandi"
} }

View File

@@ -26,7 +26,7 @@ export type JWTContextType = {
isInitialized: boolean; isInitialized: boolean;
user: AuthUser; user: AuthUser;
method: 'jwt'; method: 'jwt';
login: (email: string, password: string) => Promise<void>; login: (email: string, password: string, rememberMe: boolean) => Promise<void>;
register: (email: string, password: string, firstName: string, lastName: string) => Promise<void>; register: (email: string, password: string, firstName: string, lastName: string) => Promise<void>;
logout: () => Promise<void>; logout: () => Promise<void>;
}; };

View File

@@ -1,7 +1,24 @@
// const getLocalizedData = async (locale) => {
// const response = await fetch(`/lang/${locale}.json`); // Mengambil file lokal berdasarkan bahasa yang dipilih
// const data = await response.json();
// return data;
// };
// export default getLocalizedData;
// LocalizationUtil.js
import idID from './lang/id-ID.json';
import enUS from './lang/en-US.json';
const localizedData = {
'id-ID': idID,
'en-US': enUS,
// Tambahkan bahasa lain sesuai kebutuhan
};
const getLocalizedData = async (locale) => { const getLocalizedData = async (locale) => {
const response = await fetch(`/lang/${locale}.json`); // Mengambil file lokal berdasarkan bahasa yang dipilih return localizedData[locale] || localizedData['id-ID'];
const data = await response.json();
return data;
}; };
export default getLocalizedData; export default getLocalizedData;

View File

@@ -443,7 +443,7 @@ export default function Table<T>({
)) ))
) : ( ) : (
<TableRow> <TableRow>
<TableCell colSpan={6} align="center"> <TableCell colSpan={headCells?.length} align="center">
{localeData.txtDataNotFound} {localeData.txtDataNotFound}
</TableCell> </TableCell>
</TableRow> </TableRow>

View File

@@ -2,7 +2,7 @@ import { createContext, ReactNode, useEffect, useReducer } from 'react';
// utils // utils
import axios from '@/utils/axios'; import axios from '@/utils/axios';
// import { isValidToken, setSession } from '@/utils/jwt'; // import { isValidToken, setSession } from '@/utils/jwt';
import { setSession, getSession, setUser, getUser } from '@/utils/token'; import { setSession, getSession, setUser, getUser, getCookie } from '@/utils/token';
// @types // @types
import { ActionMap, AuthState, AuthUser, JWTContextType } from '@/@types/auth'; import { ActionMap, AuthState, AuthUser, JWTContextType } from '@/@types/auth';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -86,11 +86,15 @@ function AuthProvider({ children }: AuthProviderProps) {
const initialize = async () => { const initialize = async () => {
try { try {
const accessToken = getSession(); const accessToken = getSession();
if (accessToken) { const rememberMe = getCookie('rememberMe') == 'OK' ? false : true;
setSession(accessToken);
const response = await axios.get('/user'); if (accessToken) {
const user = response.data; const userString = getUser();
const storedUser = userString ? JSON.parse(userString) : null;
setUser(storedUser, rememberMe);
setSession(accessToken, rememberMe);
const response = await axios.get('/user');
const user = response.data;
dispatch({ dispatch({
type: Types.Initial, type: Types.Initial,
@@ -126,16 +130,16 @@ function AuthProvider({ children }: AuthProviderProps) {
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type' : 'application/json', 'Content-Type' : 'application/json',
'Accept-Language': (localStorage.getItem('currentLocale') ? localStorage.getItem('currentLocale') : 'id-ID'), 'Accept-Language': localStorage.getItem('currentLocale') ?? 'id-ID',
}, },
}; };
const login = async (email: string, password: string) => axios const login = async (email: string, password: string, rememberMe: boolean) => axios
.post('/login', { email, password }, headers) .post('/login', { email, password }, headers)
.then((response) => { .then((response) => {
const { user, token } = response.data.data; const { user, token } = response.data.data;
setSession(token); setSession(token, rememberMe);
setUser(user); setUser(user, rememberMe);
dispatch({ dispatch({
type: Types.Login, type: Types.Login,
@@ -168,8 +172,9 @@ function AuthProvider({ children }: AuthProviderProps) {
}; };
const logout = async () => { const logout = async () => {
setSession(null); await axios.post('logout');
setUser(null); setSession(null, false);
setUser(null, false);
dispatch({ type: Types.Logout }); dispatch({ type: Types.Logout });
}; };

View File

@@ -0,0 +1,82 @@
{
"greeting": "Hello",
"buttonText": "Click Me",
"infoLogin": "Enter the registered account",
"txtLogin1" : "Sign in to Hospital Portal",
"txtLogin2" : "Enter your details below",
"txtCardSearchMember1" : "Membership Query",
"txtCardSearchMember2" : "Search Member",
"txtCardSearchMember3" : "Date of Birth",
"txtCardSearchMember4" : "Member ID",
"txtCardSearchMember5" : "Member",
"txtDialogMember1" : "Services",
"txtDialogMember2" : "Request LOG",
"txtDialogMember3" : "Detail",
"txtDialogMember4" : "Please select services",
"txtDialogMember5" : "Admission Date",
"txtDialogMember6" : "Please select admission date",
"txtWarningDischargeDate" : "Please select discharge date",
"txtCreateAt" : "Create at",
"txtDateBirth" : "Date of Birth",
"txtGender" : "Gender",
"txtMaritalStatus" : "Marital Status",
"txtLanguage" : "Language",
"txtRelationship" : "Relationship",
"txtRequestDate" : "Request Date",
"txtMemberID" : "Member ID",
"txtClaimCode" : "Claim Code",
"txtRequestCode" : "Request Code",
"txtName" : "Name",
"txtStatus" : "Status",
"txtSearch" : "Search Name or Member ID...",
"txtAll" : "All",
"txtSubmissionDate" : "Admission Date",
"txtDataNotFound" : "Data Not Found",
"txtConditionDocument" : "Condition Document",
"txtDiagnosisDokument" : "Diagnosis Dokument",
"txtSupportingResultDocument" : "Supporting Result Document",
"txtAddResult" : "Add Result",
"txtServiceType" : "Service Type",
"txtAdditionalDocuments" : "Additional Documents",
"txtAddNew" : "Add New",
"txtAddress" : "Address",
"txtProvider": "Provider",
"txtAlertProvider" : "Please enter provider",
"txtHelp" : "Need help?",
"txtContactUs" : "Contact Us",
"txtNotifications" : "Notifications",
"txtYouHave" : "You have",
"txtUnm" : "unread messages",
"txtNew" : "New",
"txtBeforeThat" : "Before that",
"txtDischargeDate" : "Discharge Date",
"txtPatner" : "Patner",
"txtSelected": "Selected",
"txtConfirmation": "Confirmation",
"txtReason": "Reason Decline",
"txtCancel": "Cancel",
"txtDecline": "Decline",
"txtApprove": "Approve",
"txtDialogConfirmation": "Are you sure you want to proceed with this action?",
"txtStartDate": "Start Date",
"txtEndDate": "End Date",
"txtHelp1" : "Has problem with your account?",
"txtLupaSandi": "Forgot password?",
"txtIngatkanSaya": "Remember me",
"txtLogin": "Login",
"txtForgotYourPassword": "Forgot your password?",
"txtPleaseEnterPassword": "Please enter the email address associated with your account and We will email you a code to reset your password.",
"txtBack": "Back",
"txtSuccessSend": "Request sent successfully",
"txtCodeConfirm": "We have sent a confirmation email to",
"txtPleasCheck": "Please check your email.",
"txtCheckEmail": "Please check your email!",
"txtEmail": "We have emailed a 6-digit confirmation code and check spam folder, please enter the code in below box to verify your email.",
"txtDont": "Dont have a code?",
"txtResendCode": "Resend code",
"txtSecond": "Second",
"txtPleaseInput": "Please enter your new password.",
"txtNewPassword": "New Password",
"txtConfPassword": "Confirm Kata Sandi"
}

View File

@@ -0,0 +1,81 @@
{
"greeting": "Halo",
"buttonText": "Klik Saya",
"infoLogin": "Masukan akun yang telah terdaftar",
"txtLogin1" : "Masuk ke Hospital Portal",
"txtLogin2" : "Masukkan detail Anda di bawah ini",
"txtCardSearchMember1" : "Pengajuan Jaminan",
"txtCardSearchMember2" : "Cari Anggota",
"txtCardSearchMember3" : "Tanggal Lahir",
"txtCardSearchMember4" : "Member ID",
"txtCardSearchMember5" : "Member",
"txtDialogMember1" : "Layanan",
"txtDialogMember2" : "Request LOG",
"txtDialogMember3" : "Detail",
"txtDialogMember4" : "Mohon pilih layanan",
"txtDialogMember5" : "Tanggal Masuk",
"txtDialogMember6" : "Mohon pilih tanggal masuk",
"txtWarningDischargeDate" : "Mohon pilih tanggal keluar",
"txtCreateAt" : "Tanggal Buat",
"txtDateBirth" : "Tanggal Lahir",
"txtGender" : "Jenis Kelamin",
"txtMaritalStatus" : "Status Perkawinan",
"txtLanguage" : "Bahasa",
"txtRelationship" : "Hubungan",
"txtRequestDate" : "Tanggal Permintaan",
"txtMemberID" : "ID Anggota",
"txtClaimCode" : "Kode Klaim",
"txtRequestCode" : "Kode Pengajuan",
"txtName" : "Nama",
"txtStatus" : "Status",
"txtSearch" : "Cari Nama atau ID Anggota...",
"txtAll" : "Semua",
"txtSubmissionDate" : "Tanggal Masuk",
"txtDataNotFound" : "Data Tidak Ditemukan",
"txtConditionDocument" : "Dokumen Kondisi",
"txtDiagnosisDokument" : "Dokumen Diagnosis",
"txtSupportingResultDocument" : "Dokumen Pendukung",
"txtAddResult" : "Tambah Hasil",
"txtServiceType" : "Tipe Layanan",
"txtAdditionalDocuments" : "Dokumen Tambahan",
"txtAddNew" : "Tambah Baru",
"txtAddress" : "Alamat",
"txtProvider": "Provider",
"txtAlertProvider" : "Mohon masukan provider",
"txtHelp" : "Butuh Bantuan?",
"txtContactUs" : "Kontak Kami",
"txtNotifications" : "Notifikasi",
"txtYouHave" : "Anda memiliki",
"txtUnm" : "pesan yang belum dibaca",
"txtNew" : "Baru",
"txtBeforeThat" : "Sebelum",
"txtDischargeDate" : "Tanggal Keluar",
"txtPatner" : "Rekanan",
"txtSelected": "Terpilih",
"txtConfirmation": "Konfirmasi",
"txtReason": "Alasan Penolakan",
"txtCancel": "Batal",
"txtDecline": "Tolak",
"txtApprove": "Terima",
"txtDialogConfirmation": "Apakah Anda yakin ingin melanjutkan tindakan ini?",
"txtStartDate": "Tanggal Mulai",
"txtEndDate": "Tanggal Akhir",
"txtHelp1" : "Punya masalah dengan akun Anda?",
"txtLupaSandi": "Lupa sandi?",
"txtIngatkanSaya": "Ingatkan saya",
"txtLogin": "Masuk",
"txtForgotYourPassword": "Lupa password anda?",
"txtPleaseEnterPassword": "Silakan masukkan alamat email yang terkait dengan akun Anda dan Kami akan mengirimkan email berisi kode untuk mengatur ulang kata sandi Anda.",
"txtBack": "Kembali",
"txtSuccessSend": "Permintaan berhasil dikirim",
"txtCodeConfirm": "Kami telah mengirimkan email konfirmasi ke",
"txtPleasCheck": "Mohon cek email Anda.",
"txtCheckEmail": "Mohon periksa email Anda!",
"txtEmail": "Kami telah mengirimkan kode konfirmasi 6 digit melalui email cek juga difolder spam, silakan masukkan kode di kotak bawah ini untuk memverifikasi email Anda.",
"txtDont": "Tidak mendapatkan kode?",
"txtResendCode": "Kirim ulang kode",
"txtSecond": "Detik",
"txtPleaseInput": "Mohon masukan kata sandi baru Anda.",
"txtNewPassword": "Kata Sandi Baru",
"txtConfPassword": "Konfirmasi Kata Sandi"
}

View File

@@ -8,6 +8,8 @@ import { IconButtonAnimate } from '@/components/animate';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import useAuth from '@/hooks/useAuth'; import useAuth from '@/hooks/useAuth';
import { getUser } from '@/utils/token';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
const MENU_OPTIONS = [ const MENU_OPTIONS = [
@@ -45,6 +47,8 @@ export default function AccountPopover() {
navigate('/auth/login'); navigate('/auth/login');
}; };
const userString = getUser();
const storedUser = userString ? JSON.parse(userString) : null;
return ( return (
<> <>
<IconButtonAnimate <IconButtonAnimate
@@ -89,7 +93,7 @@ export default function AccountPopover() {
Hospital Admin Hospital Admin
</Typography> </Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }} noWrap> <Typography variant="body2" sx={{ color: 'text.secondary' }} noWrap>
hospitaladmin@gmail.com {storedUser?.email}
</Typography> </Typography>
</Box> </Box>

View File

@@ -12,6 +12,8 @@ import Iconify from '@/components/Iconify';
// sections // sections
import { ForgetPasswordForm } from '@/sections/auth/forget-password'; import { ForgetPasswordForm } from '@/sections/auth/forget-password';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { useState, useContext, useEffect } from 'react';
import { LanguageContext } from '@/contexts/LanguageContext';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -25,6 +27,8 @@ const RootStyle = styled('div')(({ theme }) => ({
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export default function ForgetPassword() { export default function ForgetPassword() {
const { localeData } = useContext(LanguageContext);
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const token = searchParams.get('token'); const token = searchParams.get('token');
@@ -42,12 +46,12 @@ export default function ForgetPassword() {
startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />} startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />}
sx={{ mb: 3 }} sx={{ mb: 3 }}
> >
Back {localeData.txtBack}
</Button> </Button>
<Typography variant="h3" paragraph></Typography> <Typography variant="h3" paragraph></Typography>
<Typography sx={{ color: 'text.secondary' }}> <Typography sx={{ color: 'text.secondary' }}>
Please enter your new password. {localeData.txtPleaseInput}
</Typography> </Typography>
<Box sx={{ mt: 5, mb: 3 }}> <Box sx={{ mt: 5, mb: 3 }}>

View File

@@ -78,29 +78,28 @@ export default function Login() {
const smUp = useResponsive("up", "sm"); const smUp = useResponsive("up", "sm");
const mdUp = useResponsive("up", "md"); const mdUp = useResponsive("up", "md");
const handleClick = () => {
window.location.href = 'https://wa.me/6285890008500';
};
return ( return (
<Page title="Login"> <Page title="Login">
<RootStyle> <RootStyle>
<HeaderStyle> <HeaderStyle>
{/*<Logo sx={{ width: 150, height: 150 }} /> <Logo sx={{ width: 150, height: 150, display: 'none' }} />
{smUp && ( {smUp && (
<Typography variant="body2" sx={{ mt: { md: -2 } }}> <Typography variant="body2" sx={{ mt: { md: -2 } }}>
Has problem with your account? {""} {localeData.txtHelp1} {""}
<Link <Link
variant="subtitle2" variant="subtitle2"
component={RouterLink} component={RouterLink}
to="#" to="#"
onClick={(e) => { onClick={handleClick}
window.location.href =
"mailto:admin@linksehat.com";
e.preventDefault();
}}
> >
Contact Us {localeData.txtContactUs}
</Link> </Link>
</Typography> </Typography>
)}*/} )}
</HeaderStyle> </HeaderStyle>
{/* {mdUp && ( {/* {mdUp && (

View File

@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useContext } from 'react';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
// @mui // @mui
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
@@ -13,6 +13,9 @@ import Page from '@/components/Page';
import { ResetPasswordForm } from '@/sections/auth/reset-password'; import { ResetPasswordForm } from '@/sections/auth/reset-password';
// assets // assets
import { SentIcon } from '@/assets'; import { SentIcon } from '@/assets';
import { LanguageContext } from '@/contexts/LanguageContext';
import { useNavigate } from 'react-router-dom'; // Import useNavigate
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -27,10 +30,17 @@ const RootStyle = styled('div')(({ theme }) => ({
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export default function ResetPassword() { export default function ResetPassword() {
const { localeData } = useContext(LanguageContext);
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [sent, setSent] = useState(false); const [sent, setSent] = useState(false);
const navigate = useNavigate();
const handleSent = () => {
setSent(true);
};
return ( return (
<Page title="Reset Password" sx={{ height: 1 }}> <Page title="Reset Password" sx={{ height: 1 }}>
<RootStyle> <RootStyle>
@@ -41,16 +51,15 @@ export default function ResetPassword() {
{!sent ? ( {!sent ? (
<> <>
<Typography variant="h3" paragraph> <Typography variant="h3" paragraph>
Forgot your password? {localeData.txtForgotYourPassword}
</Typography> </Typography>
<Typography sx={{ color: 'text.secondary', mb: 5 }}> <Typography sx={{ color: 'text.secondary', mb: 5 }}>
Please enter the email address associated with your account and We will email you {localeData.txtPleaseEnterPassword}
a link to reset your password.
</Typography> </Typography>
<ResetPasswordForm <ResetPasswordForm
onSent={() => setSent(true)} onSent={handleSent}
onGetEmail={(value) => setEmail(value)} onGetEmail={(value) => setEmail(value)}
/> />
@@ -61,7 +70,7 @@ export default function ResetPassword() {
to={PATH_AUTH.login} to={PATH_AUTH.login}
sx={{ mt: 3 }} sx={{ mt: 3 }}
> >
Back {localeData.txtBack}
</Button> </Button>
</> </>
) : ( ) : (
@@ -69,24 +78,24 @@ export default function ResetPassword() {
<SentIcon sx={{ mb: 5, mx: 'auto', height: 160 }} /> <SentIcon sx={{ mb: 5, mx: 'auto', height: 160 }} />
<Typography variant="h3" gutterBottom> <Typography variant="h3" gutterBottom>
Request sent successfully {localeData.txtSuccessSend}
</Typography> </Typography>
<Typography> <Typography>
We have sent a confirmation email to &nbsp; {localeData.txtCodeConfirm} &nbsp;
<strong>{email}</strong> <strong>{email}</strong>
<br /> <br />
Please check your email. {localeData.txtPleasCheck}
</Typography> </Typography>
<Button <Button
size="large" size="large"
variant="contained" variant="contained"
component={RouterLink} component={RouterLink}
to={PATH_AUTH.login} to={PATH_AUTH.verify}
sx={{ mt: 5 }} sx={{ mt: 5 }}
> >
Back Next
</Button> </Button>
</Box> </Box>
)} )}

View File

@@ -1,4 +1,4 @@
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink, useLocation } from 'react-router-dom';
// @mui // @mui
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
import { Box, Button, Link, Container, Typography } from '@mui/material'; import { Box, Button, Link, Container, Typography } from '@mui/material';
@@ -11,6 +11,9 @@ import Page from '@/components/Page';
import Iconify from '@/components/Iconify'; import Iconify from '@/components/Iconify';
// sections // sections
import { VerifyCodeForm } from '@/sections/auth/verify-code'; import { VerifyCodeForm } from '@/sections/auth/verify-code';
import { useState, useContext, useEffect } from 'react';
import { LanguageContext } from '@/contexts/LanguageContext';
import axios from '@/utils/axios';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -21,9 +24,41 @@ const RootStyle = styled('div')(({ theme }) => ({
padding: theme.spacing(12, 0), padding: theme.spacing(12, 0),
})); }));
function useQuery() {
return new URLSearchParams(useLocation().search);
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export default function VerifyCode() { export default function VerifyCode() {
const { localeData } = useContext(LanguageContext);
const query = useQuery();
const email = query.get('email');
const [timer, setTimer] = useState(60); // Initialize timer with 60 seconds
const [canResend, setCanResend] = useState(false); // State to control resend button visibility
useEffect(() => {
const interval = setInterval(() => {
setTimer((prev) => {
if (prev > 0) {
return prev - 1;
} else {
clearInterval(interval);
setCanResend(true); // Enable resend button when timer reaches 0
return 0;
}
});
}, 1000);
return () => clearInterval(interval); // Cleanup interval on component unmount
}, []);
const handleResend = () => {
setCanResend(false);
setTimer(60); // Reset timer to 60 seconds
// Add logic to resend the code here
axios.post('/verify-email', {email: email});
};
return ( return (
<Page title="Verify" sx={{ height: 1 }}> <Page title="Verify" sx={{ height: 1 }}>
<RootStyle> <RootStyle>
@@ -34,30 +69,42 @@ export default function VerifyCode() {
<Button <Button
size="small" size="small"
component={RouterLink} component={RouterLink}
to={PATH_AUTH.login} to={PATH_AUTH.resetPassword}
startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />} startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />}
sx={{ mb: 3 }} sx={{ mb: 3 }}
> >
Back {localeData.txtBack}
</Button> </Button>
<Typography variant="h3" paragraph> <Typography variant="h3" paragraph>
Please check your email! {localeData.txtCheckEmail}
</Typography> </Typography>
<Typography variant='subtitle1'>{email}</Typography>
<Typography sx={{ color: 'text.secondary' }}> <Typography sx={{ color: 'text.secondary' }}>
We have emailed a 6-digit confirmation code to acb@domain, please enter the code in {localeData.txtEmail}
below box to verify your email.
</Typography> </Typography>
<Box sx={{ mt: 5, mb: 3 }}> <Box sx={{ mt: 5, mb: 3 }}>
<VerifyCodeForm /> <VerifyCodeForm
onGetEmail={email}
/>
</Box> </Box>
<Typography variant="body2" align="center"> {/* <Typography variant="body2" align="center">
Dont have a code? &nbsp; Dont have a code? &nbsp;
<Link variant="subtitle2" underline="none" onClick={() => {}}> <Link variant="subtitle2" underline="none" onClick={() => {}}>
Resend code Resend code
</Link> </Link>
</Typography> */}
<Typography variant="body2" align="center">
{localeData.txtDont} &nbsp;
{canResend ? (
<Link sx={{cursor: 'pointer'}} variant="subtitle2" underline="none" onClick={handleResend}>
{localeData.txtResendCode}
</Link>
) : (
<span>{`${localeData.txtResendCode} ${timer} ${localeData.txtSecond}`}</span>
)}
</Typography> </Typography>
</Box> </Box>
</Container> </Container>

View File

@@ -54,7 +54,7 @@ export default function Router() {
// { path: 'register-unprotected', element: <Register /> }, // { path: 'register-unprotected', element: <Register /> },
{ path: 'reset-password', element: <ResetPassword /> }, { path: 'reset-password', element: <ResetPassword /> },
{ path: 'forget-password', element: <ForgetPassword /> }, { path: 'forget-password', element: <ForgetPassword /> },
// { path: 'verify', element: <VerifyCode /> }, { path: 'verify', element: <VerifyCode /> },
], ],
}, },
// { // {

View File

@@ -2,8 +2,8 @@ import * as Yup from 'yup';
// form // form
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useContext, useMemo, useRef, useState } from 'react';
import { Link as RouterLink, useNavigate } from 'react-router-dom'; import { Link as RouterLink, useNavigate, useLocation } from 'react-router-dom';
// @mui // @mui
import { Alert, IconButton, InputAdornment, Stack, Typography } from '@mui/material'; import { Alert, IconButton, InputAdornment, Stack, Typography } from '@mui/material';
@@ -14,6 +14,9 @@ import useIsMountedRef from '@/hooks/useIsMountedRef';
import { FormProvider, RHFTextField } from '@/components/hook-form'; import { FormProvider, RHFTextField } from '@/components/hook-form';
import axios from '@/utils/axios'; import axios from '@/utils/axios';
import Iconify from '@/components/Iconify'; import Iconify from '@/components/Iconify';
import { LanguageContext } from '@/contexts/LanguageContext';
import { useSnackbar } from 'notistack';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -26,9 +29,19 @@ type Props = {
token: string; token: string;
}; };
function useQuery() {
return new URLSearchParams(useLocation().search);
}
export default function ForgetPasswordForm({ token }: Props) { export default function ForgetPasswordForm({ token }: Props) {
const { localeData } = useContext(LanguageContext);
const isMountedRef = useIsMountedRef(); const isMountedRef = useIsMountedRef();
const navigate = useNavigate(); const navigate = useNavigate();
const query = useQuery();
const email = query.get('email');
const token_ = query.get('token');
const { enqueueSnackbar } = useSnackbar();
const [showPasswordNew, setShowPasswordNew] = useState(false); const [showPasswordNew, setShowPasswordNew] = useState(false);
const [showPasswordConfirmNew, setShowPasswordConfirmNew] = useState(false); const [showPasswordConfirmNew, setShowPasswordConfirmNew] = useState(false);
@@ -47,19 +60,19 @@ export default function ForgetPasswordForm({ token }: Props) {
const onSubmit = async (data: FormValuesProps) => { const onSubmit = async (data: FormValuesProps) => {
try { try {
await axios.post('/forget-password', { ...data, token }); await axios.post('/forget-password', { ...data, email:email, token:token_ });
console.log(data);
// await new Promise((resolve) => setTimeout(resolve, 500)); // await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500));
if (isMountedRef.current) { if (isMountedRef.current) {
enqueueSnackbar('Password reset was successful', { variant: 'success' });
navigate('/auth/login', { replace: true }); navigate('/auth/login', { replace: true });
} }
} catch (error) { } catch (error) {
console.log(error.response.data);
if (isMountedRef.current) { if (isMountedRef.current) {
setError('afterSubmit', { ...error, message: error.response.data.message }); setError('afterSubmit', { ...error, message: error.response.data.meta.message });
} }
} }
}; };
@@ -68,10 +81,10 @@ export default function ForgetPasswordForm({ token }: Props) {
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}> <FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}> <Stack spacing={3}>
{!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>} {!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
<Typography>Kata Sandi Baru</Typography> <Typography>{localeData.txtNewPassword}</Typography>
<RHFTextField <RHFTextField
name="new_password" name="new_password"
label="Kata Sandi Baru" label={localeData.txtNewPassword}
type={showPasswordNew ? 'text' : 'password'} type={showPasswordNew ? 'text' : 'password'}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
@@ -83,10 +96,10 @@ export default function ForgetPasswordForm({ token }: Props) {
), ),
}} }}
/> />
<Typography>Konfirmasi Kata Sandi </Typography> <Typography>{localeData.txtConfPassword}</Typography>
<RHFTextField <RHFTextField
name="confirm_new_password" name="confirm_new_password"
label="Konfirmasi Kata Sandi" label={localeData.txtConfPassword}
type={showPasswordConfirmNew ? 'text' : 'password'} type={showPasswordConfirmNew ? 'text' : 'password'}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (

View File

@@ -60,7 +60,7 @@ export default function LoginForm() {
const onSubmit = async (data: FormValuesProps) => { const onSubmit = async (data: FormValuesProps) => {
try { try {
const loginResult = await login(data.email, data.password); const loginResult = await login(data.email, data.password, data.remember);
navigate('/dashboard'); navigate('/dashboard');
} catch (error) { } catch (error) {
@@ -100,10 +100,10 @@ export default function LoginForm() {
</Stack> </Stack>
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ my: 2 }}> <Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ my: 2 }}>
{/*<RHFCheckbox name="remember" label="Remember me" /> <RHFCheckbox name="remember" label={localeData.txtIngatkanSaya}/>
<Link component={RouterLink} variant="subtitle2" to={PATH_AUTH.resetPassword}> <Link component={RouterLink} variant="subtitle2" to={PATH_AUTH.resetPassword}>
Forgot password? {localeData.txtLupaSandi}
</Link>*/} </Link>
</Stack> </Stack>
<LoadingButton <LoadingButton
@@ -114,7 +114,7 @@ export default function LoginForm() {
loading={isSubmitting} loading={isSubmitting}
sx={{ marginTop: 2 }} sx={{ marginTop: 2 }}
> >
Login {localeData.txtLogin}
</LoadingButton> </LoadingButton>
</FormProvider> </FormProvider>
); );

View File

@@ -10,6 +10,7 @@ import useIsMountedRef from '@/hooks/useIsMountedRef';
// components // components
import { FormProvider, RHFTextField } from '@/components/hook-form'; import { FormProvider, RHFTextField } from '@/components/hook-form';
import axios from '@/utils/axios'; import axios from '@/utils/axios';
import { useNavigate } from 'react-router-dom';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -26,6 +27,8 @@ type Props = {
export default function ResetPasswordForm({ onSent, onGetEmail }: Props) { export default function ResetPasswordForm({ onSent, onGetEmail }: Props) {
const isMountedRef = useIsMountedRef(); const isMountedRef = useIsMountedRef();
const navigate = useNavigate();
const ResetPasswordSchema = Yup.object().shape({ const ResetPasswordSchema = Yup.object().shape({
email: Yup.string().email('Email must be a valid email address').required('Email is required'), email: Yup.string().email('Email must be a valid email address').required('Email is required'),
}); });
@@ -43,19 +46,22 @@ export default function ResetPasswordForm({ onSent, onGetEmail }: Props) {
const onSubmit = async (data: FormValuesProps) => { const onSubmit = async (data: FormValuesProps) => {
try { try {
await axios.post('/verify-email', data); const response = await axios.post('/verify-email', data);
console.log(data); if(response.data.data.email)
{
onGetEmail(response.data.data.email);
navigate(`/auth/verify?email=${response.data.data.email}`);
}
// await new Promise((resolve) => setTimeout(resolve, 500)); // await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500));
if (isMountedRef.current) { if (isMountedRef.current) {
onSent(); onSent();
onGetEmail(data.email); onGetEmail(response.data.data.email);
} }
} catch (error) { } catch (error) {
console.log(error.response.data);
if (isMountedRef.current) { if (isMountedRef.current) {
setError('afterSubmit', { ...error, message: error.response.data.message }); setError('afterSubmit', { ...error, message: error.response.data.meta.message });
} }
} }
}; };

View File

@@ -6,9 +6,12 @@ import { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
// @mui // @mui
import { OutlinedInput, Stack } from '@mui/material'; import { OutlinedInput, Alert, Stack } from '@mui/material';
import useIsMountedRef from '@/hooks/useIsMountedRef';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { FormProvider, RHFTextField } from '@/components/hook-form';
// routes // routes
import axios from '@/utils/axios';
// import { PATH_DASHBOARD } from '@/routes/paths'; // import { PATH_DASHBOARD } from '@/routes/paths';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -23,8 +26,11 @@ type FormValuesProps = {
}; };
type ValueNames = 'code1' | 'code2' | 'code3' | 'code4' | 'code5' | 'code6'; type ValueNames = 'code1' | 'code2' | 'code3' | 'code4' | 'code5' | 'code6';
type Props = {
export default function VerifyCodeForm() { onGetEmail: (value: string) => void;
};
export default function VerifyCodeForm({ onGetEmail }: Props) {
const isMountedRef = useIsMountedRef();
const navigate = useNavigate(); const navigate = useNavigate();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
@@ -51,8 +57,9 @@ export default function VerifyCodeForm() {
watch, watch,
control, control,
setValue, setValue,
setError,
handleSubmit, handleSubmit,
formState: { isSubmitting, isValid }, formState: { isSubmitting, isValid , errors},
} = useForm({ } = useForm({
mode: 'onBlur', mode: 'onBlur',
resolver: yupResolver(VerifyCodeSchema), resolver: yupResolver(VerifyCodeSchema),
@@ -67,16 +74,36 @@ export default function VerifyCodeForm() {
}, []); }, []);
const onSubmit = async (data: FormValuesProps) => { const onSubmit = async (data: FormValuesProps) => {
// try {
// await new Promise((resolve) => setTimeout(resolve, 500));
// console.log('code:', Object.values(data).join(''));
// enqueueSnackbar('Verify success!', { variant: 'success' });
// navigate('/dashboard', { replace: true });
// } catch (error) {
// console.error(error);
// }
try { try {
await new Promise((resolve) => setTimeout(resolve, 500)); const response = await axios.post('/verify-code', {email: onGetEmail, token: Object.values(data).join('')});
console.log('code:', Object.values(data).join('')); // await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500));
enqueueSnackbar('Verify success!', { variant: 'success' }); if(response.data.meta.code === 200)
{
navigate('/dashboard', { replace: true }); navigate(`/auth/forget-password?email=${response.data.data.email}&token=${response.data.data.token}`);
} catch (error) { }
console.error(error); if (isMountedRef.current) {
} if(response.data.meta.code === 404)
{
setError('afterSubmit', { ...response, message: response.data.meta.message });
}
}
} catch (error) {
if (isMountedRef.current) {
setError('afterSubmit', { ...error, message: error.response.data.meta.message });
}
}
}; };
const handlePasteClipboard = (event: ClipboardEvent) => { const handlePasteClipboard = (event: ClipboardEvent) => {
@@ -115,35 +142,38 @@ export default function VerifyCodeForm() {
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<Stack direction="row" spacing={2} justifyContent="center"> <Stack spacing={3}>
{Object.keys(values).map((name, index) => ( {!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
<Controller <Stack direction="row" spacing={2} justifyContent="center">
key={name} {Object.keys(values).map((name, index) => (
name={`code${index + 1}` as ValueNames} <Controller
control={control} key={name}
render={({ field }) => ( name={`code${index + 1}` as ValueNames}
<OutlinedInput control={control}
{...field} render={({ field }) => (
id="field-code" <OutlinedInput
autoFocus={index === 0} {...field}
placeholder="-" id="field-code"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => autoFocus={index === 0}
handleChangeWithNextField(event, field.onChange) placeholder="-"
} onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
inputProps={{ handleChangeWithNextField(event, field.onChange)
maxLength: 1, }
sx: { inputProps={{
p: 0, maxLength: 1,
textAlign: 'center', sx: {
width: { xs: 36, sm: 56 }, p: 0,
height: { xs: 36, sm: 56 }, textAlign: 'center',
}, width: { xs: 36, sm: 56 },
}} height: { xs: 36, sm: 56 },
/> },
)} }}
/> />
))} )}
</Stack> />
))}
</Stack>
</Stack>
<LoadingButton <LoadingButton
fullWidth fullWidth

View File

@@ -25,18 +25,45 @@ import axios from './axios';
// }, timeLeft); // }, timeLeft);
// }; // };
const setSession = (accessToken: string | null) => { // let expiredCookie = 1/24/60; 1 menit
let expiredCookie = 12/24; //12 jam
const setCookie = (name:any, value:any, days:any) => {
let expires = "";
if (days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + decodeURIComponent(value || "") + expires + "; path=/; SameSite=Strict";
};
const setSession = (accessToken: string | null, rememberMe: boolean) => {
if (accessToken) { if (accessToken) {
localStorage.setItem('accessToken', accessToken); const userString = getUser();
const storedUser = userString ? JSON.parse(userString) : null;
if(rememberMe)
{
localStorage.setItem('accessToken', accessToken);
}
else
{
setCookie('accessToken', accessToken, expiredCookie);
setCookie('rememberMe', 'OK', expiredCookie);
}
axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`; axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
axios.defaults.headers.common['Accept-Language'] = (localStorage.getItem('currentLocale') ? localStorage.getItem('currentLocale') : 'id-ID'); axios.defaults.headers.common['Accept-Language'] = localStorage.getItem('currentLocale') ?? 'id-ID';
axios.defaults.headers.common['Accept'] = 'application/json'; axios.defaults.headers.common['Accept'] = 'application/json';
axios.defaults.headers.common['Content-Type'] = 'application/json'; axios.defaults.headers.common['Content-Type'] = 'application/json';
axios.defaults.headers.common['Organization-id'] = storedUser?.organization_id;
// This function below will handle when token is expired // This function below will handle when token is expired
// const { exp } = jwtDecode(accessToken); // const { exp } = jwtDecode(accessToken);
// handleTokenExpired(exp); // handleTokenExpired(exp);
} else { } else {
localStorage.removeItem('accessToken'); localStorage.removeItem('accessToken');
removeCookie('accessToken');
removeCookie('rememberMe');
delete axios.defaults.headers.common.Authorization; delete axios.defaults.headers.common.Authorization;
delete axios.defaults.headers.common['Accept-Language']; delete axios.defaults.headers.common['Accept-Language'];
delete axios.defaults.headers.common['Accept']; delete axios.defaults.headers.common['Accept'];
@@ -44,15 +71,52 @@ const setSession = (accessToken: string | null) => {
} }
}; };
const setUser = (user: any) => { const setUser = (user: any, rememberMe: boolean) => {
if (user) { if (user) {
localStorage.setItem('user', user); if(rememberMe)
{
localStorage.setItem('user', JSON.stringify(user));
}
else
{
setCookie('user', JSON.stringify(user), expiredCookie);
setCookie('rememberMe', 'OK', expiredCookie);
}
} else { } else {
localStorage.removeItem('user'); localStorage.removeItem('user');
removeCookie('user');
removeCookie('rememberMe');
} }
}; };
const getSession = () => window.localStorage.getItem('accessToken') const getCookie = (name:any) => {
const getUser = () => window.localStorage.getItem('user') const cookies = document.cookie.split('; ');
for (let i = 0; i < cookies.length; i++) {
const cookiePair = cookies[i].split('=');
if (cookiePair[0] === name) {
return decodeURIComponent(cookiePair[1]);
}
}
return null;
};
export { setSession, getSession, setUser, getUser }; const getSession = () => {
const localToken = window.localStorage.getItem('accessToken');
const cookieToken = getCookie('accessToken');
// Prioritaskan token dari localStorage
return localToken || cookieToken;
};
// const getUser = () => window.localStorage.getItem('user') || window.sessionStorage.getItem('user')
const getUser = () => {
const localUser = window.localStorage.getItem('user');
const cookieUser = getCookie('user');
// Prioritaskan token dari localStorage
return localUser || cookieUser;
};
const removeCookie = (name:any) => {
document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
};
export { setSession, getSession, setUser, getUser, getCookie };

Binary file not shown.