From be31b6f7644d1ab26dfa61d23e2aea7816f380ae Mon Sep 17 00:00:00 2001 From: Dell Date: Fri, 26 Aug 2022 10:31:10 +0700 Subject: [PATCH] Add Corporate Specialities --- .../Api/CorporateFormulariumController.php | 13 +- .../Api/CorporateServiceController.php | 70 ++- Modules/Internal/Routes/api.php | 5 +- .../Services/MemberEnrollmentService.php | 105 ++-- .../CorporateServiceConfigResource.php | 3 +- app/Models/CorporateService.php | 15 + app/Models/CorporateServiceSpeciality.php | 34 ++ app/Models/Speciality.php | 23 + composer.json | 1 + composer.lock | 152 +++++- ...08_24_024003_create_specialities_table.php | 38 ++ ...e_corporate_service_specialities_table.php | 40 ++ database/seeders/IcdSeeder.php | 2 +- database/seeders/SpecialitiesSeeder.php | 234 +++++++++ frontend/dashboard/src/@types/corporates.ts | 10 + .../src/pages/Corporates/Benefit/Index.tsx | 18 +- .../Corporates/ClaimHistory/CreateUpdate.tsx | 64 +++ .../pages/Corporates/ClaimHistory/Form.tsx | 129 +++++ .../pages/Corporates/ClaimHistory/Index.tsx | 43 ++ .../pages/Corporates/ClaimHistory/List.tsx | 234 +++++++++ .../Corporates/CorporateTabNavigations.tsx | 7 +- .../Corporates/DiagnosisExclusion/Index.tsx | 3 +- .../src/pages/Corporates/Division/Index.tsx | 18 +- .../src/pages/Corporates/Formularium/List.tsx | 60 ++- .../Corporates/Hospital/CreateUpdate.tsx | 64 +++ .../src/pages/Corporates/Hospital/Form.tsx | 129 +++++ .../src/pages/Corporates/Hospital/Index.tsx | 43 ++ .../src/pages/Corporates/Hospital/List.tsx | 234 +++++++++ .../src/pages/Corporates/Plan/Index.tsx | 19 +- .../src/pages/Corporates/Services/Create.tsx | 481 +++++++++++------- .../src/pages/Corporates/Services/List.tsx | 87 ++-- frontend/dashboard/src/routes/index.tsx | 18 + storage/debugbar/.gitignore | 2 + 33 files changed, 2078 insertions(+), 320 deletions(-) create mode 100644 app/Models/CorporateServiceSpeciality.php create mode 100644 app/Models/Speciality.php create mode 100644 database/migrations/2022_08_24_024003_create_specialities_table.php create mode 100644 database/migrations/2022_08_24_225705_create_corporate_service_specialities_table.php create mode 100644 database/seeders/SpecialitiesSeeder.php create mode 100644 frontend/dashboard/src/pages/Corporates/ClaimHistory/CreateUpdate.tsx create mode 100644 frontend/dashboard/src/pages/Corporates/ClaimHistory/Form.tsx create mode 100644 frontend/dashboard/src/pages/Corporates/ClaimHistory/Index.tsx create mode 100644 frontend/dashboard/src/pages/Corporates/ClaimHistory/List.tsx create mode 100644 frontend/dashboard/src/pages/Corporates/Hospital/CreateUpdate.tsx create mode 100644 frontend/dashboard/src/pages/Corporates/Hospital/Form.tsx create mode 100644 frontend/dashboard/src/pages/Corporates/Hospital/Index.tsx create mode 100644 frontend/dashboard/src/pages/Corporates/Hospital/List.tsx create mode 100644 storage/debugbar/.gitignore diff --git a/Modules/Internal/Http/Controllers/Api/CorporateFormulariumController.php b/Modules/Internal/Http/Controllers/Api/CorporateFormulariumController.php index 19691b0a..8a84ab9d 100644 --- a/Modules/Internal/Http/Controllers/Api/CorporateFormulariumController.php +++ b/Modules/Internal/Http/Controllers/Api/CorporateFormulariumController.php @@ -19,8 +19,17 @@ class CorporateFormulariumController extends Controller public function index(Request $request, $corporate_id) { $formulariums = Formularium::query() - ->filter($request->all()) - ->with(['corporateFormulariums' => function ($query) use ($corporate_id) { + ->filter($request->all()); + if (!empty($request->status) && $request->status == 'inactive') { + $formulariums = $formulariums->whereDoesntHave('corporateFormulariums'); + } else if (!empty($request->status) && $request->status == 'all') { + + } else { + $formulariums->whereHas('corporateFormulariums', function ($corporateFormularium) use ($corporate_id){ + $corporateFormularium->where('corporate_id', $corporate_id); + }); + } + $formulariums = $formulariums->with(['corporateFormulariums' => function ($query) use ($corporate_id) { $query->where('corporate_id', $corporate_id); }]) ->withCount('items') diff --git a/Modules/Internal/Http/Controllers/Api/CorporateServiceController.php b/Modules/Internal/Http/Controllers/Api/CorporateServiceController.php index 675abc10..61e3717e 100644 --- a/Modules/Internal/Http/Controllers/Api/CorporateServiceController.php +++ b/Modules/Internal/Http/Controllers/Api/CorporateServiceController.php @@ -3,8 +3,11 @@ namespace Modules\Internal\Http\Controllers\Api; use App\Helpers\Helper; +use App\Models\Corporate; use App\Models\CorporateService; use App\Models\CorporateServiceConfig; +use App\Models\CorporateServiceSpeciality; +use App\Models\Speciality; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; @@ -18,7 +21,7 @@ class CorporateServiceController extends Controller */ public function index(Request $request, $corporate_id) { - $services = CorporateService::with('configs', 'service')->where('corporate_id', $corporate_id)->paginate(); + $services = CorporateService::with('configs', 'service')->where('corporate_id', $corporate_id)->filter($request->toArray())->paginate(); return Helper::paginateResources(CorporateServiceConfigResource::collection($services)); } @@ -91,4 +94,69 @@ class CorporateServiceController extends Controller { // } + + public function corporateServiceIndex($corporate_id, $service_code) + { + $corporate = Corporate::findOrFail($corporate_id); + $corporateService = CorporateService::query() + ->where('corporate_id', $corporate_id) + ->where('service_code', $service_code) + ->with(['configs', 'service', + 'specialities' => function($speciality) { + $speciality->where('status', 'active'); + }, + 'specialities.speciality']) + ->first(); + // $service = CorporateServiceConfigResource::make($corporateService); + $specialities = Speciality::get(); + + return response()->json( + [ + 'corporate' => $corporate, + 'service' => CorporateServiceConfigResource::make($corporateService), + 'specialities' => $specialities, + ] + ); + } + + public function corporateServiceUpdate(Request $request, $corporate_id, $service_code) + { + // $corporate = Corporate::findOrFail($corporate_id); + $corporateService = CorporateService::query() + ->where('corporate_id', $corporate_id) + ->where('service_code', $service_code) + // ->with('configs', 'service') + ->first(); + $corporateService->fill([ + 'status' => $request->status == 'active' ? 'active' : 'inactive' + ]); + $corporateService->save(); + + return response()->json($corporateService); + } + + public function corporateServiceSpecialityUpdate(Request $request, $corporate_id, $service_code) + { + $corporateService = CorporateService::query() + ->where('corporate_id', $corporate_id) + ->where('service_code', $service_code) + ->first(); + CorporateServiceSpeciality::updateOrCreate([ + 'corporate_service_id' => $corporateService->id, + 'speciality_id' => $request->speciality_id, + ], [ + 'corporate_service_id' => $corporateService->id, + 'speciality_id' => $request->speciality_id, + 'status' => $request->status + ]); + + $selected_specialities = CorporateServiceSpeciality::query() + ->where('corporate_service_id', $corporateService->id) + ->where('status', 'active') + ->with('speciality') + ->get() + ->pluck('speciality.name', 'speciality.id'); + + return response()->json($selected_specialities); + } } diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index b99f863d..d18f067c 100644 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -73,7 +73,10 @@ Route::prefix('internal')->group(function () { Route::post('corporates/{corporate_id}/diagnosis-exclusions/import', [DiagnosisExclusionController::class, 'import']); Route::get('corporates/{corporate_id}/services', [CorporateServiceController::class, 'index']); - Route::post('corporates/{corporate_id}/services', [CorporateServiceController::class, 'update']); + Route::put('corporates/{corporate_id}/services', [CorporateServiceController::class, 'update']); + Route::get('corporates/{corporate_id}/services/{service_code}', [CorporateServiceController::class, 'corporateServiceIndex']); + Route::put('corporates/{corporate_id}/services/{service_code}', [CorporateServiceController::class, 'corporateServiceUpdate']); + Route::post('corporates/{corporate_id}/services/{service_code}/specialities', [CorporateServiceController::class, 'corporateServiceSpecialityUpdate']); Route::get('corporates/{corporate_id}/formulariums', [CorporateFormulariumController::class, 'index']); Route::put('corporates/{corporate_id}/formulariums/{formularium_id}/{action}', [CorporateFormulariumController::class, 'updateStatus']); diff --git a/Modules/Internal/Services/MemberEnrollmentService.php b/Modules/Internal/Services/MemberEnrollmentService.php index ebd1dcf0..e178ef39 100644 --- a/Modules/Internal/Services/MemberEnrollmentService.php +++ b/Modules/Internal/Services/MemberEnrollmentService.php @@ -509,42 +509,6 @@ class MemberEnrollmentService $member->active = false; $member->save(); - break; - case "4": // Member Update Start and End Date - $memberPolicy = MemberPolicy::query() - ->where('policy_id', $row['policy_number']) - ->where('member_id', $row['member_id']) - ->first(); - - if (!$memberPolicy) { - throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [ - 'member_id' => $row['member_id'], - 'policy_id' => $row['policy_number'] - ]), 0, null, $row); - } - - if ($memberPolicy->status != 'active') { - throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [ - 'member_id' => $row['member_id'], - 'policy_id' => $row['policy_number'] - ]), 0, null, $row); - } - - $memberPolicy->fill([ - 'start' => $row['start_date'], - 'end' => $row['end_date'] - ]); - - if (!$memberPolicy->isDirty()) { - throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_NO_CHANGE'), 0, null, $row); - } - - if (Carbon::parse(strtotime($row['member_effective_date'])) > Carbon::parse(strtotime($row['member_expiry_date']))) { - throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_INVALID'), 0, null, $row); - } - - $memberPolicy->save(); - break; case "5": // Member Renewal Policy (without card) $memberPolicy = MemberPolicy::query() @@ -684,9 +648,6 @@ class MemberEnrollmentService } throw new ImportRowException(__('MODE 7 NOT HANDLED PROPERLY'), 0, null, $row); - break; - case "8": // Member Information Update (With Replacement Card) - break; case "9": // Member Reactivation and Personal information update (Without replacement Card) $memberPolicy = MemberPolicy::query() @@ -716,29 +677,77 @@ class MemberEnrollmentService $memberPolicy->save(); break; + case "13": // Advance Renewal with OLD Card No. (NO PRINT) + // ASDASDASD + throw new ImportRowException(__('MODE 13 NOT HANDLED PROPERLY'), 0, null, $row); + break; + + + + + // THESE MODES BELOW ARE DISABLED + case "4": // Member Update Start and End Date + throw new ImportRowException(__('MODE 4 NOT HANDLED PROPERLY, TRY TO USE MODE 2'), 0, null, $row); + break; + $memberPolicy = MemberPolicy::query() + ->where('policy_id', $row['policy_number']) + ->where('member_id', $row['member_id']) + ->first(); + + if (!$memberPolicy) { + throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } + + if ($memberPolicy->status != 'active') { + throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } + + $memberPolicy->fill([ + 'start' => $row['start_date'], + 'end' => $row['end_date'] + ]); + + if (!$memberPolicy->isDirty()) { + throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_NO_CHANGE'), 0, null, $row); + } + + if (Carbon::parse(strtotime($row['member_effective_date'])) > Carbon::parse(strtotime($row['member_expiry_date']))) { + throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_INVALID'), 0, null, $row); + } + + $memberPolicy->save(); + + break; + case "8": // Member Information Update (With Replacement Card) + throw new ImportRowException(__('MODE 8 NOT HANDLED PROPERLY, TRY TO USE MODE 2'), 0, null, $row); + break; // case "10": // No Information Available // break; case "11": // Advance Renewal with OLD Card No. (PRINT) - throw new ImportRowException(__('MODE 11 NOT HANDLED PROPERLY'), 0, null, $row); + throw new ImportRowException(__('MODE 11 NOT HANDLED PROPERLY, TRY TO USE MODE 13'), 0, null, $row); break; case "12": // Advance Renewal iwh NEW Card No. (PRINT) - throw new ImportRowException(__('MODE 12 NOT HANDLED PROPERLY'), 0, null, $row); - break; - case "13": // Advance Renewal with OLD Card No. (NO PRINT) - - throw new ImportRowException(__('MODE 13 NOT HANDLED PROPERLY'), 0, null, $row); + throw new ImportRowException(__('MODE 12 NOT HANDLED PROPERLY, TRY TO USE MODE 13'), 0, null, $row); break; // case "14": // No Information Available // break; case "15": // Lost Card / Change Card with new card number (Print) (Rarely Used) - throw new ImportRowException(__('MODE 15 NOT HANDLED PROPERLY'), 0, null, $row); + throw new ImportRowException(__('MODE 15 NOT HANDLED PROPERLY, TRY TO USE MODE 2'), 0, null, $row); break; case "16": // Endorsement Plan OLD Card No. (NO PRINT) + throw new ImportRowException(__('MODE 16 NOT HANDLED PROPERLY, TRY TO USE MODE 2'), 0, null, $row); + break; $plan = CorporatePlan::query() ->where('corporate_id', $corporate->id) ->where('code', $row['plan_id']) @@ -767,11 +776,9 @@ class MemberEnrollmentService ]), 0, null, $row); } - throw new ImportRowException(__('MODE 16 NOT HANDLED PROPERLY'), 0, null, $row); - break; case "17": // Endorsement Plan OLD Card No. (PRINT) - throw new ImportRowException(__('MODE 17 NOT HANDLED PROPERLY'), 0, null, $row); + throw new ImportRowException(__('MODE 17 NOT HANDLED PROPERLY, TRY TO USE MODE 2'), 0, null, $row); break; default: throw new ImportRowException(__("enrollment.MODE_UNAVAILABLE"), 0, null, $row); diff --git a/Modules/Internal/Transformers/CorporateServiceConfigResource.php b/Modules/Internal/Transformers/CorporateServiceConfigResource.php index fd3fa62b..64337560 100644 --- a/Modules/Internal/Transformers/CorporateServiceConfigResource.php +++ b/Modules/Internal/Transformers/CorporateServiceConfigResource.php @@ -22,7 +22,8 @@ class CorporateServiceConfigResource extends JsonResource 'status' => $this->status, 'name' => $this->service->name, 'description' => $this->service->description, - 'configurations' => $this->configs->pluck('value', 'name') + 'configurations' => $this->configs->pluck('value', 'name'), + 'selected_specialities' => $this->specialities->pluck('speciality.name', 'speciality_id') ]; } } diff --git a/app/Models/CorporateService.php b/app/Models/CorporateService.php index df53c8a1..59ab214e 100644 --- a/app/Models/CorporateService.php +++ b/app/Models/CorporateService.php @@ -31,4 +31,19 @@ class CorporateService extends Model { return $this->hasOne(Service::class, 'code', 'service_code'); } + + public function specialities() + { + return $this->hasMany(CorporateServiceSpeciality::class, 'corporate_service_id'); + } + + public function scopeFilter($query, array $filters) + { + if (!empty($filters['search'])) { + $query->where('service_code', 'LIKE', '%'.$filters['search'].'%') + ->orWhereHas('service', function($service) use ($filters) { + $service->where('name', 'LIKE', '%'.$filters['search'].'%'); + }); + } + } } diff --git a/app/Models/CorporateServiceSpeciality.php b/app/Models/CorporateServiceSpeciality.php new file mode 100644 index 00000000..5746ba40 --- /dev/null +++ b/app/Models/CorporateServiceSpeciality.php @@ -0,0 +1,34 @@ +belongsTo(CorporateService::class, 'corporate_service_id'); + } + + public function corporate() + { + return $this->hasOneThrough(Corporate::class, CorporateService::class); + } + + public function speciality() + { + return $this->belongsTo(Speciality::class, 'speciality_id'); + } +} diff --git a/app/Models/Speciality.php b/app/Models/Speciality.php new file mode 100644 index 00000000..45ae5c39 --- /dev/null +++ b/app/Models/Speciality.php @@ -0,0 +1,23 @@ +=7.2.5", + "symfony/finder": "^5|^6" + }, + "require-dev": { + "mockery/mockery": "^1.3.3", + "orchestra/testbench-dusk": "^5|^6|^7", + "phpunit/phpunit": "^8.5|^9.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ], + "aliases": { + "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" + } + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "PHP Debugbar integration for Laravel", + "keywords": [ + "debug", + "debugbar", + "laravel", + "profiler", + "webprofiler" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-debugbar/issues", + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.7.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2022-07-11T09:26:42+00:00" + }, { "name": "barryvdh/laravel-ide-helper", "version": "v2.12.3", @@ -6640,6 +6724,72 @@ }, "time": "2022-05-02T13:58:40+00:00" }, + { + "name": "maximebf/debugbar", + "version": "v1.18.0", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", + "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^2.6|^3|^4|^5|^6" + }, + "require-dev": { + "phpunit/phpunit": "^7.5.20 || ^9.4.2", + "twig/twig": "^1.38|^2.7|^3.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/maximebf/php-debugbar", + "keywords": [ + "debug", + "debugbar" + ], + "support": { + "issues": "https://github.com/maximebf/php-debugbar/issues", + "source": "https://github.com/maximebf/php-debugbar/tree/v1.18.0" + }, + "time": "2021-12-27T18:49:48+00:00" + }, { "name": "mockery/mockery", "version": "1.5.0", diff --git a/database/migrations/2022_08_24_024003_create_specialities_table.php b/database/migrations/2022_08_24_024003_create_specialities_table.php new file mode 100644 index 00000000..65df4e07 --- /dev/null +++ b/database/migrations/2022_08_24_024003_create_specialities_table.php @@ -0,0 +1,38 @@ +id(); + $table->string('code')->nullable(); + $table->string('name')->nullable(); + + $table->timestamps(); + $table->softDeletes(); + $table->unsignedBigInteger('created_by')->nullable()->index(); + $table->unsignedBigInteger('updated_by')->nullable()->index(); + $table->unsignedBigInteger('deleted_by')->nullable()->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('specialities'); + } +}; diff --git a/database/migrations/2022_08_24_225705_create_corporate_service_specialities_table.php b/database/migrations/2022_08_24_225705_create_corporate_service_specialities_table.php new file mode 100644 index 00000000..34af5752 --- /dev/null +++ b/database/migrations/2022_08_24_225705_create_corporate_service_specialities_table.php @@ -0,0 +1,40 @@ +id(); + $table->foreignId('corporate_service_id'); + $table->foreignId('speciality_id'); + $table->string('status')->default('active'); + + $table->timestamps(); + $table->softDeletes(); + + $table->foreignId('created_by')->nullable(); + $table->foreignId('updated_by')->nullable(); + $table->foreignId('deleted_by')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('corporate_service_specialities'); + } +}; diff --git a/database/seeders/IcdSeeder.php b/database/seeders/IcdSeeder.php index 842f8a3b..8988c909 100644 --- a/database/seeders/IcdSeeder.php +++ b/database/seeders/IcdSeeder.php @@ -50,7 +50,7 @@ class IcdSeeder extends Seeder $chunks[] = $row_data; } - if ($chunks && count($chunks) == 1000) { + if ($chunks && count($chunks) == 100) { Icd::insert($chunks); $chunks = []; } diff --git a/database/seeders/SpecialitiesSeeder.php b/database/seeders/SpecialitiesSeeder.php new file mode 100644 index 00000000..808b57aa --- /dev/null +++ b/database/seeders/SpecialitiesSeeder.php @@ -0,0 +1,234 @@ + '1', + 'name' => 'Akupunktur', + 'image' => 'image/specialities/akupunktur.png', + ), + array( + 'code' => '2', + 'name' => 'Anak', + 'image' => 'image/specialities/anak.png', + ), + array( + 'code' => '3', + 'name' => 'Andrologi', + 'image' => 'image/specialities/andrologi.png', + ), + array( + 'code' => '4', + 'name' => 'Anestesi', + 'image' => 'image/specialities/anestesi.png', + ), + array( + 'code' => '5', + 'name' => 'Bedah Anak', + 'image' => 'image/specialities/bedah_anak.png', + ), + array( + 'code' => '6', + 'name' => 'Bedah Digestif', + 'image' => 'image/specialities/bedah_digestif.png', + ), + array( + 'code' => '7', + 'name' => 'Bedah Mulut', + 'image' => 'image/specialities/bedah_mulut.png', + ), + array( + 'code' => '8', + 'name' => 'Bedah Onkologi', + 'image' => 'image/specialities/bedah_onkologi.png', + ), + array( + 'code' => '9', + 'name' => 'Bedah Orthopedi', + 'image' => 'image/specialities/bedah_orthopedi.png', + ), + array( + 'code' => '10', + 'name' => 'Bedah Plastik & Rekonstruksi', + 'image' => 'image/specialities/bedah_plastik_dan_rekonstruksi.png', + ), + array( + 'code' => '11', + 'name' => 'Bedah Saraf', + 'image' => 'image/specialities/bedah_saraf.png', + ), + array( + 'code' => '12', + 'name' => 'Bedah Thorak & Kardiovaskuler', + 'image' => 'image/specialities/bedah_thorak_dan_kardiovaskuler.png', + ), + array( + 'code' => '13', + 'name' => 'Bedah Umum', + 'image' => 'image/specialities/bedah_umum.png', + ), + array( + 'code' => '14', + 'name' => 'Bedah Vaskuler', + 'image' => 'image/specialities/bedah_vaskuler.png', + ), + array( + 'code' => '15', + 'name' => 'Dokter Gigi', + 'image' => 'image/specialities/dokter_gigi.png', + ), + array( + 'code' => '16', + 'name' => 'Fisik & Rehabilitasi', + 'image' => 'image/specialities/fisik_dan_rehabilitasi.png', + ), + array( + 'code' => '17', + 'name' => 'Gastroenterologi Hepatologi', + 'image' => 'image/specialities/gastroenterologi_hepatologi.png', + ), + array( + 'code' => '18', + 'name' => 'Ginjal Hipertensi', + 'image' => 'image/specialities/ginjal_hipertensi.png', + ), + array( + 'code' => '19', + 'name' => 'Gizi Klinik', + 'image' => 'image/specialities/gizi_klinik.png', + ), + array( + 'code' => '20', + 'name' => 'Hematologi Onkologi', + 'image' => 'image/specialities/hematologi_onkologi.png', + ), + array( + 'code' => '21', + 'name' => 'Jantung & Pembuluh Darah', + 'image' => 'image/specialities/jantung_dan_pembuluh_darah.png', + ), + array( + 'code' => '22', + 'name' => 'Kardio Vaskuler', + 'image' => 'image/specialities/kardio_vaskuler.png', + ), + array( + 'code' => '23', + 'name' => 'Kebidanan & Kandungan', + 'image' => 'image/specialities/kebidanan_dan_kandungan.png', + ), + array( + 'code' => '24', + 'name' => 'Kesehatan Jiwa', + 'image' => 'image/specialities/kesehatan_jiwa.png', + ), + array( + 'code' => '25', + 'name' => 'Kulit & Kelamin', + 'image' => 'image/specialities/kulit_dan_kelamin.png', + ), + array( + 'code' => '26', + 'name' => 'Laboratorium', + 'image' => 'image/specialities/laboratorium.png', + ), + array( + 'code' => '27', + 'name' => 'Mata', + 'image' => 'image/specialities/mata.png', + ), + array( + 'code' => '28', + 'name' => 'Okupasi', + 'image' => 'image/specialities/okupasi.png', + ), + array( + 'code' => '29', + 'name' => 'Paru', + 'image' => 'image/specialities/paru.png', + ), + array( + 'code' => '30', + 'name' => 'Patologi Klinik', + 'image' => 'image/specialities/patologi_klinik.png', + ), + array( + 'code' => '31', + 'name' => 'Penyakit Dalam', + 'image' => 'image/specialities/penyakit_dalam.png', + ), + array( + 'code' => '32', + 'name' => 'Psikolog', + 'image' => 'image/specialities/psikolog.png', + ), + array( + 'code' => '33', + 'name' => 'Radiolog', + 'image' => 'image/specialities/radiolog.png', + ), + array( + 'code' => '34', + 'name' => 'Rehabilitasi Medik', + 'image' => 'image/specialities/rehabilitasi_medik.png', + ), + array( + 'code' => '35', + 'name' => 'Rheumatologi', + 'image' => 'image/specialities/rheumatologi.png', + ), + array( + 'code' => '36', + 'name' => 'Saraf', + 'image' => 'image/specialities/saraf.png', + ), + array( + 'code' => '37', + 'name' => 'THT', + 'image' => 'image/specialities/tht.png', + ), + array( + 'code' => '38', + 'name' => 'Umum', + 'image' => 'image/specialities/umum.png', + ), + array( + 'code' => '39', + 'name' => 'Urologi', + 'image' => 'image/specialities/urologi.png', + ), + array( + 'code' => '40', + 'name' => 'Lain-Lain', + 'image' => 'image/specialities/lain_lain.png', + ), + ); + + // Speciality::truncate(); + + foreach ($specialities as $speciality) { + $speciality['code'] = "SP".str_pad($speciality['code'], 4, 0, STR_PAD_LEFT); + unset($speciality['image']); + Speciality::updateOrCreate( + [ + 'code' => $speciality['code'], + ], + $speciality + ); + } + } +} diff --git a/frontend/dashboard/src/@types/corporates.ts b/frontend/dashboard/src/@types/corporates.ts index 5279366b..03c941d7 100644 --- a/frontend/dashboard/src/@types/corporates.ts +++ b/frontend/dashboard/src/@types/corporates.ts @@ -171,3 +171,13 @@ export type Benefit = { show_benefit_item : string; show_benefit_value : string; } + +export type CorporateService = { + id?: string | number; + corporate_id?: string | number; + description?: string; + name?: string; + service_code: string; + status: string; + configurations: any; +} diff --git a/frontend/dashboard/src/pages/Corporates/Benefit/Index.tsx b/frontend/dashboard/src/pages/Corporates/Benefit/Index.tsx index 67f09c84..838b1437 100644 --- a/frontend/dashboard/src/pages/Corporates/Benefit/Index.tsx +++ b/frontend/dashboard/src/pages/Corporates/Benefit/Index.tsx @@ -35,20 +35,10 @@ export default function Divisions() { ]} /> - - - - - - - - - - Corporate Detail Goes Here -   - - - + + + + ); } diff --git a/frontend/dashboard/src/pages/Corporates/ClaimHistory/CreateUpdate.tsx b/frontend/dashboard/src/pages/Corporates/ClaimHistory/CreateUpdate.tsx new file mode 100644 index 00000000..d4ad3ea9 --- /dev/null +++ b/frontend/dashboard/src/pages/Corporates/ClaimHistory/CreateUpdate.tsx @@ -0,0 +1,64 @@ + +import { useNavigate, useParams } from "react-router-dom"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import useSettings from "../../../hooks/useSettings"; +import { useEffect, useMemo, useState } from 'react'; +import axios from '../../../utils/axios'; +import { useSnackbar } from 'notistack'; +import CorporatePlanForm from './Form'; +import { CorporatePlan } from '../../../@types/corporates'; + + + +export default function PlanCreate() { + const { themeStretch } = useSettings(); + const { corporate_id, id } = useParams(); + const [ currentCorporatePlan, setCurrentCorporatePlan ] = useState(); + const navigate = useNavigate(); + + const isEdit = !!id; + + useEffect(() => { + if (isEdit) { + axios.get('/corporates/'+corporate_id+'/divisions/'+id+'/edit') + .then((res) => { + setCurrentCorporatePlan(res.data); + }) + .catch((err) => { + if (err.response.status === 404) { + navigate('/404'); + } + }) + } + }, [corporate_id, id]); + + + return ( + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Corporates/ClaimHistory/Form.tsx b/frontend/dashboard/src/pages/Corporates/ClaimHistory/Form.tsx new file mode 100644 index 00000000..dfdde430 --- /dev/null +++ b/frontend/dashboard/src/pages/Corporates/ClaimHistory/Form.tsx @@ -0,0 +1,129 @@ +import * as Yup from 'yup'; +import { LoadingButton } from "@mui/lab"; +import { Card, Grid, Stack, Typography } from "@mui/material"; +import { CorporatePlan } from "../../../@types/corporates"; +import { FormProvider, RHFSwitch, RHFTextField } from "../../../components/hook-form"; +import { useEffect, useMemo } from 'react'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate, useParams } from 'react-router-dom'; +import axios from '../../../utils/axios'; + +type Props = { + isEdit: boolean; + currentCorporatePlan?: CorporatePlan; +}; + +export default function CorporatePlanForm({ isEdit, currentCorporatePlan }: Props) { + + const { enqueueSnackbar } = useSnackbar(); + const navigate = useNavigate(); + const { corporate_id } = useParams(); + + const NewCorporatePlanSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + code: Yup.string().required('Corporate Code is required'), + }); + + const defaultValues = useMemo( + () => ({ + name: currentCorporatePlan?.name || '', + code: currentCorporatePlan?.code || '', + active: currentCorporatePlan?.active === 1 ? true : false, + }), + [currentCorporatePlan] + ); + + useEffect(() => { + if (isEdit && currentCorporatePlan) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + }, [isEdit, currentCorporatePlan]); + + const methods = useForm({ + resolver: yupResolver(NewCorporatePlanSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + + const onSubmit = async (data: any) => { + if (!isEdit) { + await axios + .post('/corporates/' + corporate_id + '/divisions', data) + .then((res) => { + enqueueSnackbar('Division created successfully', { variant: 'success' }); + }) + .then((res) => { + navigate('/corporates/' + corporate_id + '/divisions', { replace: true }); + }) + .catch(({ response }) => { + if (response.status === 422) { + for (const [key, value] of Object.entries(response.data.errors)) { + setError(key, { message: value[0] }); + enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' }); + } + } + else { + enqueueSnackbar('Create Failed : '+ response.data.message, { variant: 'error' }); + } + }); + } else { + await axios + .put('/corporates/' + corporate_id + '/divisions/' + currentCorporatePlan?.id , data) + .then((res) => { + enqueueSnackbar('Division updated successfully', { variant: 'success' }); + }) + .then((res) => { + navigate('/corporates/' + corporate_id + '/divisions/' , { replace: true }); + }) + .catch(({ response }) => { + enqueueSnackbar('Update Failed : '+ response.data.message, { variant: 'error' }); + }); + } + }; + + return ( + + + + + + + Division Detail + + + + + + + { isEdit? 'Update' : 'Create' } + + + + + + + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Corporates/ClaimHistory/Index.tsx b/frontend/dashboard/src/pages/Corporates/ClaimHistory/Index.tsx new file mode 100644 index 00000000..5f615aae --- /dev/null +++ b/frontend/dashboard/src/pages/Corporates/ClaimHistory/Index.tsx @@ -0,0 +1,43 @@ +import { Card, Grid, Typography } from "@mui/material"; +import { useParams } from "react-router-dom"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import useSettings from "../../../hooks/useSettings"; +import CorporateTabNavigations from "../CorporateTabNavigations"; +import DivisionsList from "./List"; + + + +export default function Divisions() { + const { themeStretch } = useSettings(); + + const { corporate_id } = useParams(); + + return ( + + + + + + + Feature Not Implemented Yet + + + ); +} diff --git a/frontend/dashboard/src/pages/Corporates/ClaimHistory/List.tsx b/frontend/dashboard/src/pages/Corporates/ClaimHistory/List.tsx new file mode 100644 index 00000000..fb019971 --- /dev/null +++ b/frontend/dashboard/src/pages/Corporates/ClaimHistory/List.tsx @@ -0,0 +1,234 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup } from '@mui/material'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import AddIcon from '@mui/icons-material/Add'; +import UploadIcon from '@mui/icons-material/Upload'; +import CancelIcon from '@mui/icons-material/Cancel'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../../hooks/useSettings'; +import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom'; +// components +import axios from '../../../utils/axios'; +import { CorporatePlan } from '../../../@types/corporates'; +import { LaravelPaginatedData } from '../../../@types/paginated-data'; +import BasePagination from '../../../components/BasePagination'; + +export default function PlanList() { + const { themeStretch } = useSettings(); + const { corporate_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const navigate = useNavigate(); + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? '' + setSearchText(newSearchText); + } + + const handleSubmit = (event: any) => { + event.preventDefault(); + props.onSearch(searchText); // Trigger to Parent + } + + useEffect(() => { + // console.log('Search Input: useEffect') + setSearchText(searchParams.get('search') ?? ''); + }, [searchParams]) + + return ( +
+ + + ); + } + + // Called on every row to map the data to the columns + function createData( plan: CorporatePlan ): CorporatePlan { + return { + ...plan, + } + } + + // Generate the every row of the table + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + + setOpen(!open)} + > + {open ? : } + + + {row.id} + {row.code} + {row.name} + {row.description} + + + + {/* COLLAPSIBLE ROW */} + + + + + + No Extra Data + + + {false && + + Rules + + + + + Date + Customer + Amount + Total price ($) + + + + {/* {row.history ? row.history.map((historyRow) => ( */} + + {row.start} - {row.end} + {row.start} + {row.start} + {row.start} + + {/* )) + : ( + + No Data + + ) + } */} + +
+
} +
+
+
+
+ ); + } + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = React.useState(true); + const [dataTableData, setDataTableData] = React.useState({ + current_page: 1, + data: [], + path: "", + first_page_url: "", + last_page: 1, + last_page_url: "", + next_page_url: "", + prev_page_url: "", + per_page: 10, + from: 0, + to: 0, + total: 0 + }); + + const loadDataTableData = async (appliedFilter : any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/corporates/'+corporate_id+'/divisions', { params: filter }); + // console.log(response.data); + setDataTableLoading(false); + + setDataTableData(response.data); + } + + const headStyle = { + fontWeight: 'bold', + }; + + const applyFilter = async (searchFilter: any) => { + await loadDataTableData({ "search" : searchFilter }); + setSearchParams({ "search" : searchFilter }); + } + + const handlePageChange = (event : ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ["page", value]]); + loadDataTableData(filter); + setSearchParams(filter); + } + + + useEffect(() => { + loadDataTableData(); + }, []) + + return ( + + + + + + + + + + {/* The Main Table */} + + + + + + ID + Code + Name + Description + + + {dataTableIsLoading ? + ( + + + Loading + + + ) : ( + dataTableData.data.length == 0 ? + ( + + + No Data + + + ) : ( + + {dataTableData.data.map(row => ( + + ))} + + ) + )} +
+
+ +
+
+ ); +} diff --git a/frontend/dashboard/src/pages/Corporates/CorporateTabNavigations.tsx b/frontend/dashboard/src/pages/Corporates/CorporateTabNavigations.tsx index 28c3207d..4e4289ff 100644 --- a/frontend/dashboard/src/pages/Corporates/CorporateTabNavigations.tsx +++ b/frontend/dashboard/src/pages/Corporates/CorporateTabNavigations.tsx @@ -1,7 +1,7 @@ import { Tab, Tabs } from "@mui/material"; import { useTheme } from "@mui/system"; import React, { useEffect } from "react"; -import { Link, useParams } from "react-router-dom"; +import { Link, useNavigate, useParams } from "react-router-dom"; type Props = { position: string @@ -9,6 +9,7 @@ type Props = { export default function CorporateTabNavigations({ position }: Props) { const theme = useTheme(); + const navigate = useNavigate(); const { corporate_id } = useParams(); @@ -73,13 +74,13 @@ export default function CorporateTabNavigations({ position }: Props) { false} + // onChange={(event, newValue) => false} variant="scrollable" scrollButtons allowScrollButtonsMobile aria-label="scrollable force tabs example" > - {mainTabItems.map((tabItem, index) => ({tabItem.label})} />))} + {mainTabItems.map((tabItem, index) => ( { navigate("/corporates/"+corporate_id+"/"+mainTabItems[index].path) }}label={tabItem.label}/>))} ) } diff --git a/frontend/dashboard/src/pages/Corporates/DiagnosisExclusion/Index.tsx b/frontend/dashboard/src/pages/Corporates/DiagnosisExclusion/Index.tsx index 81ce66a0..e9ce93d8 100644 --- a/frontend/dashboard/src/pages/Corporates/DiagnosisExclusion/Index.tsx +++ b/frontend/dashboard/src/pages/Corporates/DiagnosisExclusion/Index.tsx @@ -35,9 +35,8 @@ export default function Divisions() { ]} /> - - + diff --git a/frontend/dashboard/src/pages/Corporates/Division/Index.tsx b/frontend/dashboard/src/pages/Corporates/Division/Index.tsx index 00fe75bf..3744a469 100644 --- a/frontend/dashboard/src/pages/Corporates/Division/Index.tsx +++ b/frontend/dashboard/src/pages/Corporates/Division/Index.tsx @@ -34,20 +34,10 @@ export default function Divisions() { ]} /> - - - - - - - - - - Corporate Detail Goes Here -   - - - + + + + ); } diff --git a/frontend/dashboard/src/pages/Corporates/Formularium/List.tsx b/frontend/dashboard/src/pages/Corporates/Formularium/List.tsx index f8ddee2b..4785cf97 100644 --- a/frontend/dashboard/src/pages/Corporates/Formularium/List.tsx +++ b/frontend/dashboard/src/pages/Corporates/Formularium/List.tsx @@ -1,5 +1,5 @@ // @mui -import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup } from '@mui/material'; +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup, Input, Grid } from '@mui/material'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; import AddIcon from '@mui/icons-material/Add'; @@ -44,26 +44,66 @@ export default function PlanList() { function SearchInput(props: any) { // SEARCH const searchInput = useRef(null); + // const filterForm = useRef(); const [searchText, setSearchText] = useState(""); + const [searchStatus, setSearchStatus] = useState("active"); const handleSearchChange = (event: any) => { const newSearchText = event.target.value ?? '' setSearchText(newSearchText); } - const handleSubmit = (event: any) => { - event.preventDefault(); - props.onSearch(searchText); // Trigger to Parent + const handleStatusChange = (event: any) => { + const newSearchStatus = event.target.value ?? '' + console.log('changing to', newSearchStatus) + setSearchStatus(newSearchStatus) + // console.log(searchStatus); + + const searchFilter = { + "search" : searchText, + "status" : newSearchStatus + }; + + props.onSearch(searchFilter); } - useEffect(() => { - // console.log('Search Input: useEffect') + const handleSubmit = (event?: any) => { + event?.preventDefault(); + const searchFilter = { + "search" : searchText, + "status" : searchStatus + }; + + props.onSearch(searchFilter); // Trigger to Parent + } + + useEffect(() => { setSearchText(searchParams.get('search') ?? ''); + setSearchStatus(searchParams.get('status') ?? searchStatus ?? 'active'); }, [searchParams]) return ( -
- + + + + + + + + + + {/* ITS FUCKING MAGIC, SUBMIT BY ENTER WORKING IF THIS BUTTON IS AVAILABLE */} + + ); } @@ -171,8 +211,8 @@ export default function PlanList() { }; const applyFilter = async (searchFilter: any) => { - await loadDataTableData({ "search" : searchFilter }); - setSearchParams({ "search" : searchFilter }); + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); } const handlePageChange = (event : ChangeEvent, value: number) => { diff --git a/frontend/dashboard/src/pages/Corporates/Hospital/CreateUpdate.tsx b/frontend/dashboard/src/pages/Corporates/Hospital/CreateUpdate.tsx new file mode 100644 index 00000000..d4ad3ea9 --- /dev/null +++ b/frontend/dashboard/src/pages/Corporates/Hospital/CreateUpdate.tsx @@ -0,0 +1,64 @@ + +import { useNavigate, useParams } from "react-router-dom"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import useSettings from "../../../hooks/useSettings"; +import { useEffect, useMemo, useState } from 'react'; +import axios from '../../../utils/axios'; +import { useSnackbar } from 'notistack'; +import CorporatePlanForm from './Form'; +import { CorporatePlan } from '../../../@types/corporates'; + + + +export default function PlanCreate() { + const { themeStretch } = useSettings(); + const { corporate_id, id } = useParams(); + const [ currentCorporatePlan, setCurrentCorporatePlan ] = useState(); + const navigate = useNavigate(); + + const isEdit = !!id; + + useEffect(() => { + if (isEdit) { + axios.get('/corporates/'+corporate_id+'/divisions/'+id+'/edit') + .then((res) => { + setCurrentCorporatePlan(res.data); + }) + .catch((err) => { + if (err.response.status === 404) { + navigate('/404'); + } + }) + } + }, [corporate_id, id]); + + + return ( + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Corporates/Hospital/Form.tsx b/frontend/dashboard/src/pages/Corporates/Hospital/Form.tsx new file mode 100644 index 00000000..dfdde430 --- /dev/null +++ b/frontend/dashboard/src/pages/Corporates/Hospital/Form.tsx @@ -0,0 +1,129 @@ +import * as Yup from 'yup'; +import { LoadingButton } from "@mui/lab"; +import { Card, Grid, Stack, Typography } from "@mui/material"; +import { CorporatePlan } from "../../../@types/corporates"; +import { FormProvider, RHFSwitch, RHFTextField } from "../../../components/hook-form"; +import { useEffect, useMemo } from 'react'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate, useParams } from 'react-router-dom'; +import axios from '../../../utils/axios'; + +type Props = { + isEdit: boolean; + currentCorporatePlan?: CorporatePlan; +}; + +export default function CorporatePlanForm({ isEdit, currentCorporatePlan }: Props) { + + const { enqueueSnackbar } = useSnackbar(); + const navigate = useNavigate(); + const { corporate_id } = useParams(); + + const NewCorporatePlanSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + code: Yup.string().required('Corporate Code is required'), + }); + + const defaultValues = useMemo( + () => ({ + name: currentCorporatePlan?.name || '', + code: currentCorporatePlan?.code || '', + active: currentCorporatePlan?.active === 1 ? true : false, + }), + [currentCorporatePlan] + ); + + useEffect(() => { + if (isEdit && currentCorporatePlan) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + }, [isEdit, currentCorporatePlan]); + + const methods = useForm({ + resolver: yupResolver(NewCorporatePlanSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + + const onSubmit = async (data: any) => { + if (!isEdit) { + await axios + .post('/corporates/' + corporate_id + '/divisions', data) + .then((res) => { + enqueueSnackbar('Division created successfully', { variant: 'success' }); + }) + .then((res) => { + navigate('/corporates/' + corporate_id + '/divisions', { replace: true }); + }) + .catch(({ response }) => { + if (response.status === 422) { + for (const [key, value] of Object.entries(response.data.errors)) { + setError(key, { message: value[0] }); + enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' }); + } + } + else { + enqueueSnackbar('Create Failed : '+ response.data.message, { variant: 'error' }); + } + }); + } else { + await axios + .put('/corporates/' + corporate_id + '/divisions/' + currentCorporatePlan?.id , data) + .then((res) => { + enqueueSnackbar('Division updated successfully', { variant: 'success' }); + }) + .then((res) => { + navigate('/corporates/' + corporate_id + '/divisions/' , { replace: true }); + }) + .catch(({ response }) => { + enqueueSnackbar('Update Failed : '+ response.data.message, { variant: 'error' }); + }); + } + }; + + return ( + + + + + + + Division Detail + + + + + + + { isEdit? 'Update' : 'Create' } + + + + + + + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Corporates/Hospital/Index.tsx b/frontend/dashboard/src/pages/Corporates/Hospital/Index.tsx new file mode 100644 index 00000000..b47f9437 --- /dev/null +++ b/frontend/dashboard/src/pages/Corporates/Hospital/Index.tsx @@ -0,0 +1,43 @@ +import { Card, Grid, Typography } from "@mui/material"; +import { useParams } from "react-router-dom"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import useSettings from "../../../hooks/useSettings"; +import CorporateTabNavigations from "../CorporateTabNavigations"; +import DivisionsList from "./List"; + + + +export default function Divisions() { + const { themeStretch } = useSettings(); + + const { corporate_id } = useParams(); + + return ( + + + + + + + Feature Not Implemented Yet + + + ); +} diff --git a/frontend/dashboard/src/pages/Corporates/Hospital/List.tsx b/frontend/dashboard/src/pages/Corporates/Hospital/List.tsx new file mode 100644 index 00000000..fb019971 --- /dev/null +++ b/frontend/dashboard/src/pages/Corporates/Hospital/List.tsx @@ -0,0 +1,234 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup } from '@mui/material'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import AddIcon from '@mui/icons-material/Add'; +import UploadIcon from '@mui/icons-material/Upload'; +import CancelIcon from '@mui/icons-material/Cancel'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../../hooks/useSettings'; +import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom'; +// components +import axios from '../../../utils/axios'; +import { CorporatePlan } from '../../../@types/corporates'; +import { LaravelPaginatedData } from '../../../@types/paginated-data'; +import BasePagination from '../../../components/BasePagination'; + +export default function PlanList() { + const { themeStretch } = useSettings(); + const { corporate_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const navigate = useNavigate(); + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? '' + setSearchText(newSearchText); + } + + const handleSubmit = (event: any) => { + event.preventDefault(); + props.onSearch(searchText); // Trigger to Parent + } + + useEffect(() => { + // console.log('Search Input: useEffect') + setSearchText(searchParams.get('search') ?? ''); + }, [searchParams]) + + return ( +
+ + + ); + } + + // Called on every row to map the data to the columns + function createData( plan: CorporatePlan ): CorporatePlan { + return { + ...plan, + } + } + + // Generate the every row of the table + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + + setOpen(!open)} + > + {open ? : } + + + {row.id} + {row.code} + {row.name} + {row.description} + + + + {/* COLLAPSIBLE ROW */} + + + + + + No Extra Data + + + {false && + + Rules + + + + + Date + Customer + Amount + Total price ($) + + + + {/* {row.history ? row.history.map((historyRow) => ( */} + + {row.start} - {row.end} + {row.start} + {row.start} + {row.start} + + {/* )) + : ( + + No Data + + ) + } */} + +
+
} +
+
+
+
+ ); + } + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = React.useState(true); + const [dataTableData, setDataTableData] = React.useState({ + current_page: 1, + data: [], + path: "", + first_page_url: "", + last_page: 1, + last_page_url: "", + next_page_url: "", + prev_page_url: "", + per_page: 10, + from: 0, + to: 0, + total: 0 + }); + + const loadDataTableData = async (appliedFilter : any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/corporates/'+corporate_id+'/divisions', { params: filter }); + // console.log(response.data); + setDataTableLoading(false); + + setDataTableData(response.data); + } + + const headStyle = { + fontWeight: 'bold', + }; + + const applyFilter = async (searchFilter: any) => { + await loadDataTableData({ "search" : searchFilter }); + setSearchParams({ "search" : searchFilter }); + } + + const handlePageChange = (event : ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ["page", value]]); + loadDataTableData(filter); + setSearchParams(filter); + } + + + useEffect(() => { + loadDataTableData(); + }, []) + + return ( + + + + + + + + + + {/* The Main Table */} + + + + + + ID + Code + Name + Description + + + {dataTableIsLoading ? + ( + + + Loading + + + ) : ( + dataTableData.data.length == 0 ? + ( + + + No Data + + + ) : ( + + {dataTableData.data.map(row => ( + + ))} + + ) + )} +
+
+ +
+
+ ); +} diff --git a/frontend/dashboard/src/pages/Corporates/Plan/Index.tsx b/frontend/dashboard/src/pages/Corporates/Plan/Index.tsx index f8d8b78c..5de637be 100644 --- a/frontend/dashboard/src/pages/Corporates/Plan/Index.tsx +++ b/frontend/dashboard/src/pages/Corporates/Plan/Index.tsx @@ -34,20 +34,11 @@ export default function Divisions() { ]} /> - - - - - - - - - - Corporate Detail Goes Here -   - - - + + + + + ); } diff --git a/frontend/dashboard/src/pages/Corporates/Services/Create.tsx b/frontend/dashboard/src/pages/Corporates/Services/Create.tsx index 5e9fccdf..9556c2d1 100644 --- a/frontend/dashboard/src/pages/Corporates/Services/Create.tsx +++ b/frontend/dashboard/src/pages/Corporates/Services/Create.tsx @@ -1,6 +1,6 @@ import * as Yup from 'yup'; import { yupResolver } from "@hookform/resolvers/yup"; -import { Card, Collapse, Divider, Grid, Stack, Typography } from "@mui/material"; +import { Box, Card, Checkbox, Collapse, Divider, FormControlLabel, Grid, Modal, Paper, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; import { useForm } from "react-hook-form"; import { useParams } from "react-router-dom"; import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; @@ -9,134 +9,91 @@ import Page from "../../../components/Page"; import useSettings from "../../../hooks/useSettings"; import CorporateTabNavigations from "../CorporateTabNavigations"; import DivisionsList from "./List"; -import { useMemo, useState } from 'react'; +import { ChangeEvent, useEffect, useMemo, useState } from 'react'; +import axios from '../../../utils/axios'; +import { CorporateService } from '../../../@types/corporates'; export default function Divisions() { const { themeStretch } = useSettings(); - const { corporate_id } = useParams(); + const { corporate_id, service_code } = useParams(); - const NewDivisionSchema = Yup.object().shape({ - name: Yup.string().required('Name is required'), - code: Yup.string().required('Corporate Code is required'), - active: Yup.boolean().required('Corporate Status is required'), + const [service, setService] = useState({ + "configurations" : {}, + "corporate_id": "null", + "name" : "", + "description" : "", + "service_code" : "", + "status" : "active" }); - const defaultValues = useMemo( - () => ({ - code: '', - }), - [] - ); + const [specialities, setSpecialities] = useState([]) - const methods = useForm({ - resolver: yupResolver(NewDivisionSchema), - defaultValues, - }); + useEffect(() => { + axios.get('/corporates/'+corporate_id+'/services/'+service_code) + .then((res) => { + setService(res.data.service) + setSpecialities(res.data.specialities) + }); + }, []) - const { - reset, - watch, - control, - setValue, - getValues, - setError, - handleSubmit, - formState: { isSubmitting }, - } = methods; - const onSubmit = async (data: any) => { - console.log(data); + const handleConfigChange = (event: ChangeEvent, service: any) => { + axios.put(`/corporates/${corporate_id}/services`, { + service_code: service.service_code, + config_name: event.target.name, + config_value: event.target.checked + }) + .then((res) => { + let newConfigurations = service.configurations + newConfigurations[res.data.name] = res.data.value == true + + setService({ + ...service, + configurations: { ...newConfigurations } + }) + }) + } + + const handleToggleSpeciality = (event: ChangeEvent, service: any, speciality: any) => { + console.log('Changing Service ', service, 'and', speciality) + axios.post(`/corporates/${corporate_id}/services/${service_code}/specialities`, { + // service_code: service.service_code, + speciality_id: speciality.id, + status: event.target.checked ? 'active' : 'inactive' + }) + .then((res) => { + setService({ + ...service, + selected_specialities : res.data + }) + // let newConfigurations = service.configurations + // newConfigurations[res.data.name] = res.data.value == true + + // setService({ + // ...service, + // configurations: { ...newConfigurations } + // }) + }) + } + + const specialityModalStyle = { + position: 'absolute' as 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '80%', + maxHeight: '80%', + overflowY: 'scroll', + bgcolor: 'background.paper', + border: '2px solid gray', + boxShadow: 24, + p: 4, }; + const [specialityModal, setSpecialityModal] = useState(false) - const [open, setOpen] = useState(false); - - const benefits = [ - { - 'category' : 'General Practitioner', - 'childs' : [ - { - 'name' : 'External Doctor Online', - 'code' : 'gp-external-doctor-online' - }, - { - 'name' : 'External Doctor Offline', - 'code' : 'gp-external-doctor-offline' - }, - { - 'name' : 'Internal Doctor Online', - 'code' : 'gp-internal-doctor-online' - }, - { - 'name' : 'Internal Doctor Offline', - 'code' : 'gp-internal-doctor-offline' - }, - ] - }, - { - 'category' : 'Specialist', - 'childs' : [ - { - 'name' : 'External Doctor Online', - 'code' : 'sp-external-doctor-online' - }, - { - 'name' : 'External Doctor Offline', - 'code' : 'sp-external-doctor-offline' - }, - { - 'name' : 'Internal Doctor Online', - 'code' : 'sp-internal-doctor-online' - }, - { - 'name' : 'Internal Doctor Offline', - 'code' : 'sp-internal-doctor-offline' - }, - ] - }, - { - 'category' : 'Medicines', - 'childs' : [ - { - 'name' : 'Vitamins', - 'code' : 'medicines-vitamins' - }, - { - 'name' : 'Delivery Fee', - 'code' : 'medicines-delivery-fee' - }, - ] - }, - ]; - - const products = [ - { - 'name' : 'Inpatient', - 'code' : 'IP', - }, - { - 'name' : 'Outpatient', - 'code' : 'OP', - }, - { - 'name' : 'Dental', - 'code' : 'DT', - }, - { - 'name' : 'Dental', - 'code' : 'DTL', - }, - { - 'name' : 'Matternity', - 'code' : 'MT', - }, - { - 'name' : 'Special Benefit', - 'code' : 'SB', - }, - ]; return ( @@ -151,15 +108,15 @@ export default function Divisions() { }, { name: 'Corporate Name', - href: '/corporates/'+id, + href: '/corporates/'+corporate_id, }, { - name: 'Benefits', - href: '/corporates/'+id+'/benefits', + name: 'Services', + href: '/corporates/'+corporate_id+'/services', }, { - name: 'Create', - href: '/corporates/'+id+'/benefits/create', + name: service.name ?? '-', + href: '/corporates/'+corporate_id+'/services/'+service_code, }, ]} /> @@ -168,71 +125,239 @@ export default function Divisions() { - - + {/* */} + + - Benefit Detail + + + + + General Practitioner + + + + + External Doctor + Internal Doctor + + + + {handleConfigChange(event, service)}} + name="gp_external_doctor_online" /> + )} + label="Online" /> + + + {handleConfigChange(event, service)}} + name="gp_external_doctor_offline" /> + )} + label="Offline" /> + + + {handleConfigChange(event, service)}} + name="gp_internal_doctor_online" /> + )} + label="Online" /> + + + {handleConfigChange(event, service)}} + name="gp_internal_doctor_offline" /> + )} + label="Offline" /> + + + +
+
- + + + + + Specialist Practitioner + + + + + External Doctor + Internal Doctor + + + + {handleConfigChange(event, service)}} + name="sp_external_doctor_online" /> + )} + label="Online" /> + + + {handleConfigChange(event, service)}} + name="sp_external_doctor_offline" /> + )} + label="Offline" /> + + + {handleConfigChange(event, service)}} + name="sp_internal_doctor_online" /> + )} + label="Online" /> + + + {handleConfigChange(event, service)}} + name="sp_internal_doctor_offline" /> + )} + label="Offline" /> + + + + + { setSpecialityModal(true) }}> + Specialities : ({service.selected_specialities ? Object.keys(service.selected_specialities).length : '0'}) + + {service.selected_specialities ? '{'+Object.values(service.selected_specialities).join(', ')+'}' : ''} + + + +
+
- + { setSpecialityModal(false) }} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description" + > + + + Specialities + + + + + + + Nama Spesialisasi + + + + {specialities.map((row) => ( + + + {handleToggleSpeciality(event, service, row)}} + name="vitamins" /> + + + {row.name} + + + ))} + +
+
+
+
- Benefit Configuration - - - }> - - - {benefits.map(row => ( - - {row.category} - - {row.childs.map(benefit => ( - - - - ))} - - - ))} - Admin Fee - - {benefits.map(row => ( - - - - ))} - - + + + + + Medicine + + + + + + {handleConfigChange(event, service)}} + name="vitamins" /> + )} + label="Vitamins" /> + + + {handleConfigChange(event, service)}} + name="delivery_fee" /> + )} + label="Delivery Fee" /> + + + + + +
+
+ + + + + + Free Admin Fee + + + + + + {handleConfigChange(event, service)}} + name="general_practitioner_fee" /> + )} + label="General Practitioner" /> + + + {handleConfigChange(event, service)}} + name="specialist_practitioner_fee" /> + )} + label="Specialist Practitioner" /> + + + + + +
+
- - - {benefits.map(row => ( - - {row.category} - - {row.childs.map(benefit => ( - - - - ))} - - - ))} - Admin Fee - - {benefits.map(row => ( - - - - ))} - -
- -
-
+ + {/*
*/}
diff --git a/frontend/dashboard/src/pages/Corporates/Services/List.tsx b/frontend/dashboard/src/pages/Corporates/Services/List.tsx index 6da18595..fe65e97a 100644 --- a/frontend/dashboard/src/pages/Corporates/Services/List.tsx +++ b/frontend/dashboard/src/pages/Corporates/Services/List.tsx @@ -9,7 +9,7 @@ import CancelIcon from '@mui/icons-material/Cancel'; // hooks import React, { ChangeEvent, Component, useEffect, useMemo, useRef, useState } from 'react'; import useSettings from '../../../hooks/useSettings'; -import { useParams, useSearchParams } from 'react-router-dom'; +import { Link, useParams, useSearchParams } from 'react-router-dom'; // components import axios from '../../../utils/axios'; import { LaravelPaginatedData } from '../../../@types/paginated-data'; @@ -19,12 +19,34 @@ import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { RHFCheckbox } from '../../../components/hook-form'; import { CheckBox } from '@mui/icons-material'; +import { CorporateService } from '../../../@types/corporates'; export default function List() { const { themeStretch } = useSettings(); const { corporate_id } = useParams(); const [searchParams, setSearchParams] = useSearchParams(); const [importResult, setImportResult] = useState(null); + + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableLastRequest, setDataTableLastRequest] = useState(0); + const [dataTableResponseState, setDataTableResponseState] = useState('idle'); + const [dataTableData, setDataTableData] = useState({ + current_page: 1, + data: [], + path: "", + first_page_url: "", + last_page: 1, + last_page_url: "", + next_page_url: "", + prev_page_url: "", + per_page: 10, + from: 0, + to: 0, + total: 0 + }); + function SearchInput(props: any) { // SEARCH @@ -68,9 +90,9 @@ export default function List() { } // Called on every row to map the data to the columns - function createData( icd: Icd ): Icd { + function createData( corporateService: CorporateService ): CorporateService { return { - ...icd, + ...corporateService, } } @@ -82,17 +104,35 @@ export default function List() { const handleConfigChange = (event: ChangeEvent, service: any) => { console.log( event.target.name ,event.target.checked, service); - axios.post(`/corporates/${corporate_id}/services`, { + axios.put(`/corporates/${corporate_id}/services`, { service_code: service.service_code, config_name: event.target.name, config_value: event.target.checked }) } + + const handleActivate = (service: any, status: string) => { + axios.put(`/corporates/${corporate_id}/services/${service.service_code}`, { + service_code: service.service_code, + status + }).then((res) => { + setDataTableData({ + ...dataTableData, + data: dataTableData.data.map((service) => { + let updatedService = service + if (row.id == service.id) { + updatedService.status = res.data.status + } + return updatedService + }) + }) + }) + } return ( *': { borderBottom: 'unset' } }}> - + {/* {open ? : } - + */} + {row.service_code} {row.name} - - + + {( row.status == 'active' && )} + {( row.status == 'inactive' && )} + + + + {/* COLLAPSIBLE ROW */} - + {false && @@ -223,29 +269,11 @@ export default function List() { + } ); } - // Dummy Default Data - const [dataTableIsLoading, setDataTableLoading] = useState(true); - const [dataTableLastRequest, setDataTableLastRequest] = useState(0); - const [dataTableResponseState, setDataTableResponseState] = useState('idle'); - const [dataTableData, setDataTableData] = useState({ - current_page: 1, - data: [], - path: "", - first_page_url: "", - last_page: 1, - last_page_url: "", - next_page_url: "", - prev_page_url: "", - per_page: 10, - from: 0, - to: 0, - total: 0 - }); - const loadDataTableData = async (appliedFilter : any | null = null) => { setDataTableLoading(true); const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); @@ -423,7 +451,8 @@ export default function List() { - + {/* */} + Code Service Status diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index 0a47e0dd..0f977c1e 100644 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -118,6 +118,10 @@ export default function Router() { path: 'corporates/:corporate_id/services', element: , }, + { + path: 'corporates/:corporate_id/services/:service_code', + element: , + }, { path: 'corporates/:corporate_id/plans/create', @@ -173,6 +177,16 @@ export default function Router() { element: , }, + { + path: 'corporates/:corporate_id/hospitals', + element: , + }, + + { + path: 'corporates/:corporate_id/claim-history', + element: , + }, + { path: 'master/diagnosis', element: , @@ -269,3 +283,7 @@ const MasterFormularium = Loadable(lazy(() => import('../pages/Master/Formulariu const MasterFormulariumCreate = Loadable(lazy(() => import('../pages/Master/Formularium/Create'))); const CorporateServices = Loadable(lazy(() => import('../pages/Corporates/Services/Index'))); +const CorporateServicesCreate = Loadable(lazy(() => import('../pages/Corporates/Services/Create'))); + +const CorporateHospitals = Loadable(lazy(() => import('../pages/Corporates/Hospital/Index'))); +const CorporateClaimHistories = Loadable(lazy(() => import('../pages/Corporates/ClaimHistory/Index'))); diff --git a/storage/debugbar/.gitignore b/storage/debugbar/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/debugbar/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore