11 Commits

Author SHA1 Message Date
R
a5db01bd25 Fix Download LOG 2023-02-15 12:50:46 +07:00
R
5d4033a9ca Display Uploaded Files 2023-02-15 12:30:35 +07:00
R
27523b8cce [WIP] Fix Upload Document from Hospital Portal 2023-02-15 08:00:14 +07:00
R
f6117743ad Fix Filter 2023-02-14 16:32:38 +07:00
R
8c97df9fc4 Remove Dummy Notifications 2023-02-14 15:31:57 +07:00
R
4f2bb19d8a [Build] Dashboard & Hospital Portal 2023-02-14 13:48:59 +07:00
R
13764a3766 [Build] Staging - Hospital Portal 2023-02-14 13:31:49 +07:00
R
ed273fdafa Merge branch 'feature/hospital-portal' 2023-02-14 13:09:14 +07:00
R
d706bf0623 [WIP] Add Approve Button 2023-02-14 13:07:17 +07:00
R
13542cd3c0 [WIP] Add Claim Request 2023-02-14 12:39:51 +07:00
pajri
a7e688a52c hospital portal 2023-02-01 16:22:03 +07:00
504 changed files with 51577 additions and 501 deletions

View File

View File

@@ -0,0 +1,5 @@
<?php
return [
'name' => 'HospitalPortal'
];

View File

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\HospitalPortal\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
class HospitalPortalDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
// $this->call("OthersTableSeeder");
}
}

View File

View File

@@ -0,0 +1,128 @@
<?php
namespace Modules\HospitalPortal\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Crypt;
use Error;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Modules\Internal\Emails\SendVerifyEmail;
use Modules\Internal\Events\ForgetPassword;
class AuthController extends Controller
{
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required'
]);
$user = User::query()
->where('email', $request->email)
->first();
if (!$user) {
return response(['message' => 'User Tidak Ditemukan'], 404);
}
if (!Hash::check($request->password, $user->password)) {
return response(['message' => 'Password Salah'], 403);
}
return response([
'message' => 'Selamat Datang',
'user' => $user,
'token' => $user->createToken('app')->plainTextToken
]);
}
public function logout(Request $request)
{
$token = $request->bearerToken();
Auth::user()->tokens()->where('id', $token)->delete();
return response(['message' => 'Berhasil Logout.']);
}
public function resetPassword(Request $request)
{
$user = Auth::user();
$request->validate([
'old_password' => 'required',
'new_password' => 'required',
'confirm_new_password' => 'required'
]);
if (!Hash::check($request['old_password'], $user->password)) {
return response(['message' => 'Password Salah'], 403);
}
if ($request["new_password"] != $request["confirm_new_password"]) {
return response([
'message' => "Password Tidak Sama"
]);
}
$user->update([
'password' => Hash::make($request->confirm_new_password),
]);
return response()->json($user);
}
public function verifyEmail(Request $request)
{
$request->validate([
'email' => 'required|email',
]);
$user = User::query()
->where('email', $request->email)
->first();
if (!$user) {
return response(['message' => 'User Tidak Ditemukan'], 404);
}
Event(new ForgetPassword($user));
// Mail::to($user->email)->send(new SendVerifyEmail($user));
return response()->json($user);
}
public function forgetPassword(Request $request)
{
$request->validate([
'new_password' => 'required',
'confirm_new_password' => 'required'
]);
$token = Crypt::decryptString($request->token);
$email = explode('|', $token)[0];
$user = User::query()
->where('email', $email)
->first();
if (!$user) {
return response(['message' => 'User Tidak Ditemukan'], 404);
}
if ($request["new_password"] != $request["confirm_new_password"]) {
return response([
'message' => "Password Tidak Sama"
], 404);
}
$user->update([
'password' => Hash::make($request->confirm_new_password),
]);
return response()->json($user);
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Modules\HospitalPortal\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Models\ClaimRequest;
use App\Models\File;
use App\Models\Member;
use Exception;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use PDF;
class ClaimRequestController extends Controller
{
/**
* Display a listing of the resource.
* @return Renderable
*/
public function index(request $request)
{
$claimRequests = ClaimRequest::query()
->when($request->search, function ($q, $search) {
$q->where('code', 'LIKE', "%".$search."%");
})
->when($request->orderBy, function ($q, $orderBy) use ($request) {
if (in_array($orderBy, ['submission_date', 'code'])) {
$q->orderBy($orderBy, $request->order);
}
})
->when($request->status, function($q, $status) {
$q->where('status', $status);
})
->with(['member'])
->paginate();
return Helper::responseJson($claimRequests);
}
/**
* Show the form for creating a new resource.
* @return Renderable
*/
public function create()
{
return view('hospitalportal::create');
}
/**
* Store a newly created resource in storage.
* @param Request $request
* @return Renderable
*/
public function store(Request $request)
{
$request->validate([
// 'submission_date' => 'required',
'member_id' => 'required',
// 'files' => ''
]);
$newClaimRequest = ClaimRequest::create([
'member_id' => $request->member_id,
'submission_date' => now(),
'status' => 'requested'
]);
if ($request->hasFile('result_files')) {
foreach ($request->result_files as $file) {
$pathFile = File::storeFile('claim', $newClaimRequest->id, $file);
$newClaimRequest->files()->updateOrCreate([
'type' => 'result',
'name' => File::getFileName('claim', $newClaimRequest->id, $file),
'original_name' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'path' => $pathFile,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id,
]);
}
}
return ($request->result_files[0]->getClientOriginalName());
return Helper::responseJson(data: $request->toArray(), message: 'Claim Request berhasil ajukan!');
}
/**
* Show the specified resource.
* @param int $id
* @return Renderable
*/
public function show($id)
{
return view('hospitalportal::show');
}
/**
* Show the form for editing the specified resource.
* @param int $id
* @return Renderable
*/
public function edit($id)
{
return view('hospitalportal::edit');
}
/**
* Update the specified resource in storage.
* @param Request $request
* @param int $id
* @return Renderable
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
* @param int $id
* @return Renderable
*/
public function destroy($id)
{
//
}
public function generateLog($claim_request_id)
{
$claimRequest = ClaimRequest::findOrFail($claim_request_id);
if ($claimRequest->status != 'approved') {
throw new Exception("Belum Teverifikasi", 1);
}
$member = Member::findOrFail($claimRequest->member_id)
->load(['currentPlan', 'currentPolicy', 'currentPlan.corporateBenefits', 'currentPlan.corporateBenefits.benefit']);
$pdf = PDF::loadView('pdf.guaranted_leter', compact('member', 'claimRequest'));
return $pdf->download('Guaranted Letter - '.$member->full_name.'.pdf');
return $claimRequest;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Modules\HospitalPortal\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Models\Member;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class MemberController extends Controller
{
/**
* Display a listing of the resource.
* @return Renderable
*/
public function search(Request $request)
{
$request->validate([
'no_polis' => 'required',
'birth_date' => 'required'
]);
$member = Member::query()
->where('member_id', $request->no_polis)
->where('birth_date', $request->birth_date)
->with(['person', 'currentCorporate',
// 'currentCorporate.corporateServices' => function ($corporateService) {
// $corporateService->where('status', 'active');
// },
// 'currentCorporate.corporateServices.service'
// 'currentPlan.benefits',
// 'currentPlan.corporateBenefit.plan',
'currentPlan.corporateBenefits.benefit'
])
->firstOrFail();
return Helper::responseJson($member);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Modules\HospitalPortal\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Claim;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class ClaimController extends Controller
{
/**
* Display a listing of the resource.
* @return Renderable
*/
public function index()
{
$claims = Claim::where('deleted_at', 'ASD')->paginate(5);
return Helper::responseJson($claims);
}
/**
* Show the form for creating a new resource.
* @return Renderable
*/
public function create()
{
return view('hospitalportal::create');
}
/**
* Store a newly created resource in storage.
* @param Request $request
* @return Renderable
*/
public function store(Request $request)
{
//
}
/**
* Show the specified resource.
* @param int $id
* @return Renderable
*/
public function show($id)
{
return view('hospitalportal::show');
}
/**
* Show the form for editing the specified resource.
* @param int $id
* @return Renderable
*/
public function edit($id)
{
return view('hospitalportal::edit');
}
/**
* Update the specified resource in storage.
* @param Request $request
* @param int $id
* @return Renderable
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
* @param int $id
* @return Renderable
*/
public function destroy($id)
{
//
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Modules\HospitalPortal\Http\Controllers;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class HospitalPortalController extends Controller
{
/**
* Display a listing of the resource.
* @return Renderable
*/
public function index()
{
return view('hospitalportal::index');
}
/**
* Show the form for creating a new resource.
* @return Renderable
*/
public function create()
{
return view('hospitalportal::create');
}
/**
* Store a newly created resource in storage.
* @param Request $request
* @return Renderable
*/
public function store(Request $request)
{
//
}
/**
* Show the specified resource.
* @param int $id
* @return Renderable
*/
public function show($id)
{
return view('hospitalportal::show');
}
/**
* Show the form for editing the specified resource.
* @param int $id
* @return Renderable
*/
public function edit($id)
{
return view('hospitalportal::edit');
}
/**
* Update the specified resource in storage.
* @param Request $request
* @param int $id
* @return Renderable
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
* @param int $id
* @return Renderable
*/
public function destroy($id)
{
//
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Modules\HospitalPortal\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Factory;
class HospitalPortalServiceProvider extends ServiceProvider
{
/**
* @var string $moduleName
*/
protected $moduleName = 'HospitalPortal';
/**
* @var string $moduleNameLower
*/
protected $moduleNameLower = 'hospitalportal';
/**
* Boot the application events.
*
* @return void
*/
public function boot()
{
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->register(RouteServiceProvider::class);
}
/**
* Register config.
*
* @return void
*/
protected function registerConfig()
{
$this->publishes([
module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower . '.php'),
], 'config');
$this->mergeConfigFrom(
module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
);
}
/**
* Register views.
*
* @return void
*/
public function registerViews()
{
$viewPath = resource_path('views/modules/' . $this->moduleNameLower);
$sourcePath = module_path($this->moduleName, 'Resources/views');
$this->publishes([
$sourcePath => $viewPath
], ['views', $this->moduleNameLower . '-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);
}
/**
* Register translations.
*
* @return void
*/
public function registerTranslations()
{
$langPath = resource_path('lang/modules/' . $this->moduleNameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
} else {
$this->loadTranslationsFrom(module_path($this->moduleName, 'Resources/lang'), $this->moduleNameLower);
}
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [];
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (\Config::get('view.paths') as $path) {
if (is_dir($path . '/modules/' . $this->moduleNameLower)) {
$paths[] = $path . '/modules/' . $this->moduleNameLower;
}
}
return $paths;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Modules\HospitalPortal\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* The module namespace to assume when generating URLs to actions.
*
* @var string
*/
protected $moduleNamespace = 'Modules\HospitalPortal\Http\Controllers';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*
* @return void
*/
public function boot()
{
parent::boot();
}
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('HospitalPortal', '/Routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->moduleNamespace)
->group(module_path('HospitalPortal', '/Routes/api.php'));
}
}

View File

@@ -0,0 +1,9 @@
@extends('hospitalportal::layouts.master')
@section('content')
<h1>Hello World</h1>
<p>
This view is loaded from module: {!! config('hospitalportal.name') !!}
</p>
@endsection

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Module HospitalPortal</title>
{{-- Laravel Mix - CSS File --}}
{{-- <link rel="stylesheet" href="{{ mix('css/hospitalportal.css') }}"> --}}
</head>
<body>
@yield('content')
{{-- Laravel Mix - JS File --}}
{{-- <script src="{{ mix('js/hospitalportal.js') }}"></script> --}}
</body>
</html>

View File

View File

@@ -0,0 +1,43 @@
<?php
use Illuminate\Http\Request;
use Modules\HospitalPortal\Http\Controllers\Api\AuthController;
use Modules\HospitalPortal\Http\Controllers\Api\ClaimRequestController;
use Modules\HospitalPortal\Http\Controllers\Api\MemberController;
use Modules\HospitalPortal\Http\Controllers\ClaimController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::prefix('hospitalportal')->group(function () {
Route::post('login', [AuthController::class, 'login'])->name('login');
Route::post('forget-password', [AuthController::class, 'forgetPassword'])->name('forget-password');
Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email');
Route::middleware('auth:sanctum')->group(function () {
Route::post('logout', [AuthController::class, 'logout'])->name('logout');
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::put('reset-password', [AuthController::class, 'resetPassword'])->name('resetPassword');
Route::get('claims', [ClaimController::class, 'index']);
Route::post('search-member', [MemberController::class, 'search']);
Route::get('claim-requests', [ClaimRequestController::class, 'index'])->name('claim-requests.index');
Route::post('claim-requests', [ClaimRequestController::class, 'store'])->name('claim-requests.store');
Route::get('claim-requests/{claim_request_id}/log', [ClaimRequestController::class, 'generateLog'])->name('claim-requests.generate-log');
});
});

View File

@@ -0,0 +1,16 @@
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::prefix('hospitalportal')->group(function() {
Route::get('/', 'HospitalPortalController@index');
});

View File

@@ -0,0 +1,23 @@
{
"name": "nwidart/hospitalportal",
"description": "",
"authors": [
{
"name": "Nicolas Widart",
"email": "n.widart@gmail.com"
}
],
"extra": {
"laravel": {
"providers": [],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\HospitalPortal\\": ""
}
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "HospitalPortal",
"alias": "hospitalportal",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\HospitalPortal\\Providers\\HospitalPortalServiceProvider"
],
"aliases": {},
"files": [],
"requires": []
}

View File

@@ -0,0 +1,21 @@
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "mix --production"
},
"devDependencies": {
"axios": "^0.21.4",
"dotenv": "^10.0.0",
"dotenv-expand": "^5.1.0",
"laravel-mix": "^6.0.31",
"laravel-mix-merge-manifest": "^2.0.0",
"lodash": "^4.17.21",
"postcss": "^8.3.7"
}
}

View File

@@ -0,0 +1,14 @@
const dotenvExpand = require('dotenv-expand');
dotenvExpand(require('dotenv').config({ path: '../../.env'/*, debug: true*/}));
const mix = require('laravel-mix');
require('laravel-mix-merge-manifest');
mix.setPublicPath('../../public').mergeManifest();
mix.js(__dirname + '/Resources/assets/js/app.js', 'js/hospitalportal.js')
.sass( __dirname + '/Resources/assets/sass/app.scss', 'css/hospitalportal.css');
if (mix.inProduction()) {
mix.version();
}

View File

@@ -0,0 +1,113 @@
<?php
namespace Modules\Internal\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Models\ClaimRequest;
use App\Models\Member;
use Exception;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Knp\Snappy\Pdf;
use Modules\Internal\Transformers\ClaimRequestResource;
class ClaimRequestController extends Controller
{
/**
* Display a listing of the resource.
* @return Renderable
*/
public function index(Request $request)
{
$claimRequests = ClaimRequest::query()
->when($request->search, function ($q, $search) {
$q->where('code', 'LIKE', "%".$search."%");
})
->when($request->orderBy, function ($q, $orderBy) use ($request) {
if (in_array($orderBy, ['submission_date', 'code'])) {
$q->orderBy($orderBy, $request->order);
}
})
->when(empty($request->orderBy), function ($q) {
$q->orderBy('created_at', 'desc');
})
->when($request->status, function($q, $status) {
$q->where('status', $status);
})
->with(['member', 'files'])
->paginate();
return Helper::paginateResources(ClaimRequestResource::collection($claimRequests));
}
/**
* Show the form for creating a new resource.
* @return Renderable
*/
public function create()
{
return view('internal::create');
}
/**
* Store a newly created resource in storage.
* @param Request $request
* @return Renderable
*/
public function store(Request $request)
{
//
}
/**
* Show the specified resource.
* @param int $id
* @return Renderable
*/
public function show($id)
{
return view('internal::show');
}
/**
* Show the form for editing the specified resource.
* @param int $id
* @return Renderable
*/
public function edit($id)
{
return view('internal::edit');
}
/**
* Update the specified resource in storage.
* @param Request $request
* @param int $id
* @return Renderable
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
* @param int $id
* @return Renderable
*/
public function destroy($id)
{
//
}
public function approve($id)
{
$claimRequest = ClaimRequest::findOrFail($id);
$claimRequest->status = 'approved';
$claimRequest->save();
return $claimRequest;
}
}

View File

@@ -5,6 +5,7 @@ use Modules\Internal\Http\Controllers\Api\AuthController;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Modules\Internal\Http\Controllers\Api\BenefitController; use Modules\Internal\Http\Controllers\Api\BenefitController;
use Modules\Internal\Http\Controllers\Api\ClaimController; use Modules\Internal\Http\Controllers\Api\ClaimController;
use Modules\Internal\Http\Controllers\Api\ClaimRequestController;
use Modules\Internal\Http\Controllers\Api\CorporateBenefitController; use Modules\Internal\Http\Controllers\Api\CorporateBenefitController;
use Modules\Internal\Http\Controllers\Api\CorporateController; use Modules\Internal\Http\Controllers\Api\CorporateController;
use Modules\Internal\Http\Controllers\Api\CorporateFormulariumController; use Modules\Internal\Http\Controllers\Api\CorporateFormulariumController;
@@ -121,8 +122,10 @@ Route::prefix('internal')->group(function () {
Route::resource('organizations', OrganizationController::class); Route::resource('organizations', OrganizationController::class);
Route::resource('doctors', DoctorController::class); Route::resource('doctors', DoctorController::class);
Route::get('generate-log/{member_id}', [CorporateMemberController::class, 'generateLog']); Route::get('generate-log/{member_id}', [CorporateMemberController::class, 'generateLog']);
Route::get('claim-requests', [ClaimRequestController::class, 'index'])->name('claim-requests.index');
Route::post('claim-requests/{id}/approve', [ClaimRequestController::class, 'approve'])->name('claim-requests.approve');
}); });
// Route::resource('organizations', OrganizationController::class); // Route::resource('organizations', OrganizationController::class);

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\Internal\Transformers;
use Illuminate\Http\Resources\Json\JsonResource;
class ClaimRequestResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
$filesGroupByType = $this->files->mapToGroups(function($file) {
return [$file->type => $file];
});
$data = [
'id' => $this->id,
'code' => $this->code,
'submission_date' => $this->submission_date,
'member' => $this->member,
'status' => $this->status ?? 'unknown',
'service_type' => $this->service_type,
'files_by_type' => $filesGroupByType
];
return $data;
}
}

View File

@@ -18,6 +18,15 @@ class Benefit extends Model
'active' 'active'
]; ];
protected $hidden = [
"created_at",
"updated_at",
"deleted_at",
"created_by",
"updated_by",
"deleted_by",
];
public function scopeFilter($query, array $filters) public function scopeFilter($query, array $filters)
{ {
$query->when($filters['search'] ?? false, function ($query, $search) { $query->when($filters['search'] ?? false, function ($query, $search) {

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Models;
use App\Traits\Blameable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class ClaimRequest extends Model
{
use HasFactory, SoftDeletes, Blameable;
protected static $code_prefix = 'CRQ';
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
try {
$model->code = self::getNextCode();
} catch (\Exception $e) {
abort(500, $e->getMessage());
}
});
}
public static function getNextCode()
{
$last_number = self::withTrashed()->max('code');
$next_number = empty($last_number) ? 1 : ((int) explode('-', $last_number)[1] + 1);
return self::makeCode($next_number);
}
public static function makeCode($next_number)
{
return (string) self::$code_prefix .'-'. str_pad($next_number, 5, 0, STR_PAD_LEFT);
}
public $fillable = [
'submission_date',
'member_id',
'status'
];
public function files()
{
return $this->morphMany(File::class, 'fileable');
}
public function member()
{
return $this->belongsTo(Member::class, 'member_id', 'id');
}
}

View File

@@ -16,10 +16,20 @@ class File extends Model
'fileable_id', 'fileable_id',
'type', 'type',
'name', 'name',
'original_name',
'extension', 'extension',
'path', 'path',
]; ];
protected $hidden = [
'created_at',
'updated_at',
'deleted_at',
'created_by',
'updated_by',
'deleted_by',
];
public $appends = [ public $appends = [
'url' 'url'
]; ];
@@ -27,7 +37,8 @@ class File extends Model
public static $file_directories = [ public static $file_directories = [
'import-temp' => 'import-temp/', 'import-temp' => 'import-temp/',
'avatar' => 'user-avatar/', 'avatar' => 'user-avatar/',
'dataDiri' => 'data-diri/' 'dataDiri' => 'data-diri/',
'claim' => 'claim/'
]; ];
public function fileable() public function fileable()
@@ -45,6 +56,11 @@ class File extends Model
return $type . '-' . $id . '-' . Str::random(10); return $type . '-' . $id . '-' . Str::random(10);
} }
public function getNameAttribute($value)
{
return !empty($this->original_name) ? $this->original_name : ($value . '.' . $this->extension);
}
public function getUrlAttribute() public function getUrlAttribute()
{ {
return url(Storage::url($this->path)); return url(Storage::url($this->path));

View File

@@ -192,12 +192,13 @@ class Member extends Model
public function getNameAttribute() public function getNameAttribute()
{ {
return $this->person->name ?? null; return $this->person->name ?? ($this->name ?? null);
} }
public function getBirthDateAttribute() public function getBirthDateAttribute()
{ {
return Carbon::parse($this->person->birth_date ?? null)->format('Y-m-d') ?? null; $date = $this->person->birth_date ?? ($this->birth_date ?? null);
return !empty($date) ? Carbon::parse($date)->format('Y-m-d') : null;
} }
public function getGenderAttribute() public function getGenderAttribute()

View File

@@ -182,7 +182,63 @@ class Plan extends Model
return $this->belongsToMany(Benefit::class, 'corporate_benefits', 'plan_id', 'benefit_id') return $this->belongsToMany(Benefit::class, 'corporate_benefits', 'plan_id', 'benefit_id')
->withTimestamps() ->withTimestamps()
->withPivot([ ->withPivot([
// TODO corporate_benefits pivot 'corporate_id',
'plan_id',
'benefit_id',
'corporate_benefit_code',
'budget',
'budget_conditions',
'budget_code',
'primary_benefit_code',
'benefit_mode',
'room_class_coverage',
'max_bed_coverage',
'tolerance_parameter',
'max_room_class',
'limit_amount',
'area_limit',
'shared_benefit',
'shared_benefit_type',
'msc',
'genders',
'min_age',
'max_age',
'max_frequency_period',
'daily_frequency',
'weekly_frequency',
'monthly_frequency',
'yearly_frequency',
'custom_frequency_days',
'custom_duration_value',
'allowed_transaction_types',
'high_plan_factor',
'pre_post_treatment',
'pre_treatment_days',
'post_treatment_days',
'layer_type_1',
'layer_value_1',
'layer_type_2',
'layer_value_2',
'cashless_percentage',
'reimbursement_percentage',
'digital_percentage',
'co_share_m_percentage',
'co_share_s_percentage',
'co_share_c_percentage',
'cashless_deductible',
'reimbursement_deductible',
'digital_deductible',
'co_share_m_deductible',
'co_share_s_deductible',
'co_share_c_deductible',
'prorate_type',
'prorate_lookup',
'max_days_for_disability',
'max_period_for_disability',
'currency',
'show_benefit_item',
'show_benefit_value',
'active'
]); ]);
} }
@@ -190,4 +246,9 @@ class Plan extends Model
{ {
return $this->hasMany(CorporateBenefit::class, 'plan_id', 'id'); return $this->hasMany(CorporateBenefit::class, 'plan_id', 'id');
} }
public function service()
{
return $this->belongsTo(Service::class, 'service_code', 'code');
}
} }

View File

@@ -14,4 +14,9 @@ class Service extends Model
'name', 'name',
'description', 'description',
]; ];
public function corporateService()
{
return $this->hasMany(CorporateService::class, 'service_code', 'code');
}
} }

View File

@@ -7,10 +7,11 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable class User extends Authenticatable
{ {
use HasApiTokens, HasFactory, Notifiable; use HasApiTokens, HasFactory, Notifiable, HasRoles;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.

View File

@@ -18,7 +18,8 @@
"maatwebsite/excel": "^3.1", "maatwebsite/excel": "^3.1",
"nwidart/laravel-modules": "^9.0", "nwidart/laravel-modules": "^9.0",
"psr/simple-cache": "^1.0", "psr/simple-cache": "^1.0",
"pusher/pusher-php-server": "^7.2" "pusher/pusher-php-server": "^7.2",
"spatie/laravel-permission": "^5.9"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-debugbar": "^3.7", "barryvdh/laravel-debugbar": "^3.7",

1092
composer.lock generated Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -188,6 +188,7 @@ return [
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class, Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
Maatwebsite\Excel\ExcelServiceProvider::class, Maatwebsite\Excel\ExcelServiceProvider::class,
Barryvdh\Snappy\ServiceProvider::class, Barryvdh\Snappy\ServiceProvider::class,
Spatie\Permission\PermissionServiceProvider::class,
/* /*
* Application Service Providers... * Application Service Providers...

161
config/permission.php Normal file
View File

@@ -0,0 +1,161 @@
<?php
return [
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to know which
* Eloquent model should be used to retrieve your permissions. Of course, it
* is often just the "Permission" model but you may use whatever you like.
*
* The model you want to use as a Permission model needs to implement the
* `Spatie\Permission\Contracts\Permission` contract.
*/
'permission' => Spatie\Permission\Models\Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to know which
* Eloquent model should be used to retrieve your roles. Of course, it
* is often just the "Role" model but you may use whatever you like.
*
* The model you want to use as a Role model needs to implement the
* `Spatie\Permission\Contracts\Role` contract.
*/
'role' => Spatie\Permission\Models\Role::class,
],
'table_names' => [
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'roles' => 'roles',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your permissions. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'permissions' => 'permissions',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your models permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_permissions' => 'model_has_permissions',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your models roles. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_roles' => 'model_has_roles',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
/*
* Change this if you want to name the related pivots other than defaults
*/
'role_pivot_key' => null, //default 'role_id',
'permission_pivot_key' => null, //default 'permission_id',
/*
* Change this if you want to name the related model primary key other than
* `model_id`.
*
* For example, this would be nice if your primary keys are all UUIDs. In
* that case, name this `model_uuid`.
*/
'model_morph_key' => 'model_id',
/*
* Change this if you want to use the teams feature and your related model's
* foreign key is other than `team_id`.
*/
'team_foreign_key' => 'team_id',
],
/*
* When set to true, the method for checking permissions will be registered on the gate.
* Set this to false, if you want to implement custom logic for checking permissions.
*/
'register_permission_check_method' => true,
/*
* When set to true the package implements teams using the 'team_foreign_key'. If you want
* the migrations to register the 'team_foreign_key', you must set this to true
* before doing the migration. If you already did the migration then you must make a new
* migration to also add 'team_foreign_key' to 'roles', 'model_has_roles', and
* 'model_has_permissions'(view the latest version of package's migration file)
*/
'teams' => false,
/*
* When set to true, the required permission names are added to the exception
* message. This could be considered an information leak in some contexts, so
* the default setting is false here for optimum safety.
*/
'display_permission_in_exception' => false,
/*
* When set to true, the required role names are added to the exception
* message. This could be considered an information leak in some contexts, so
* the default setting is false here for optimum safety.
*/
'display_role_in_exception' => false,
/*
* By default wildcard permission lookups are disabled.
*/
'enable_wildcard_permission' => false,
'cache' => [
/*
* By default all permissions are cached for 24 hours to speed up performance.
* When permissions or roles are updated the cache is flushed automatically.
*/
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
/*
* The cache key used to store all permissions.
*/
'key' => 'spatie.permission.cache',
/*
* You may optionally indicate a specific cache driver to use for permission and
* role caching using any of the `store` drivers listed in the cache.php config
* file. Using 'default' here means to use the `default` set in cache.php.
*/
'store' => 'default',
],
];

View File

@@ -0,0 +1,41 @@
<?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::create('claim_requests', function (Blueprint $table) {
$table->id();
$table->string('code')->index();
$table->dateTime('submission_date')->nullable();
$table->foreignId('member_id');
$table->string('status')->nullable();
$table->foreignId('claim_id')->nullable()->comment('After Claim is Created');
$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('claim_requests');
}
};

View File

@@ -0,0 +1,141 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Spatie\Permission\PermissionRegistrar;
class CreatePermissionTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
$teams = config('permission.teams');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
if ($teams && empty($columnNames['team_foreign_key'] ?? null)) {
throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
Schema::create($tableNames['permissions'], function (Blueprint $table) {
$table->bigIncrements('id'); // permission id
$table->string('name'); // For MySQL 8.0 use string('name', 125);
$table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125);
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) {
$table->bigIncrements('id'); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
}
$table->string('name'); // For MySQL 8.0 use string('name', 125);
$table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125);
$table->timestamps();
if ($teams || config('permission.testing')) {
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
} else {
$table->unique(['name', 'guard_name']);
}
});
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) {
$table->unsignedBigInteger(PermissionRegistrar::$pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign(PermissionRegistrar::$pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
} else {
$table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
}
});
Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) {
$table->unsignedBigInteger(PermissionRegistrar::$pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign(PermissionRegistrar::$pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
} else {
$table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
}
});
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) {
$table->unsignedBigInteger(PermissionRegistrar::$pivotPermission);
$table->unsignedBigInteger(PermissionRegistrar::$pivotRole);
$table->foreign(PermissionRegistrar::$pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
$table->foreign(PermissionRegistrar::$pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
$table->primary([PermissionRegistrar::$pivotPermission, PermissionRegistrar::$pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key'));
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
}
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);
Schema::drop($tableNames['model_has_permissions']);
Schema::drop($tableNames['roles']);
Schema::drop($tableNames['permissions']);
}
}

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('files', function (Blueprint $table) {
$table->string('original_name')->nullable()->after('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('files', function (Blueprint $table) {
$table->dropColumn('original_name');
});
}
};

View File

@@ -17,18 +17,23 @@ class DummyMemberSeeder extends Seeder
public function run() public function run()
{ {
$userEmails = [ $userEmails = [
'admin@linksehat.dev', 'admin@linksehat.dev' => ['administrator'],
'manager+one@gmail.com', 'manager+one@gmail.com' => ['corporate-manager'],
'manager+two@gmail.com' 'manager+two@gmail.com' => ['corporate-manager'],
'hospitaladmin@gmail.com' => ['hospital-admin']
]; ];
foreach ($userEmails as $email) { foreach ($userEmails as $email => $roles) {
User::updateOrCreate([ $user = User::updateOrCreate([
'email' => $email 'email' => $email
], [ ], [
'email' => $email, 'email' => $email,
'password' => Hash::make('password') 'password' => Hash::make('password')
]); ]);
if (isset($roles) && count($roles)) {
$user->syncRoles($roles);
}
} }
} }
} }

View File

@@ -0,0 +1,30 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
class RoleSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$roles = [
'administrator',
'corporate-manager',
'hospital-admin'
];
foreach ($roles as $name) {
Role::create([
'name' => $name
]);
}
}
}

View File

@@ -58,6 +58,13 @@ const navConfig = [
{ title: 'Hospitals', path: '/hospitals' }, { title: 'Hospitals', path: '/hospitals' },
], ],
}, },
{
title: 'LOG REQUEST',
path: '/claim-requests',
// children: [
// { title: 'Request', path: '/case-request' },
// ],
},
{ {
title: 'CASE MANAGEMENT', title: 'CASE MANAGEMENT',
path: '/claims', path: '/claims',

View File

@@ -0,0 +1,64 @@
import * as Yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
import { Autocomplete, Button, Card, Collapse, Container, Divider, Grid, Stack, Table, TableBody, TableCell, TableRow, TextField, Typography } from '@mui/material';
import { Controller, useForm } from 'react-hook-form';
import { useParams, useNavigate } from 'react-router-dom';
import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs';
import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from '../../components/hook-form';
import Page from '../../components/Page';
import useSettings from '../../hooks/useSettings';
import { useEffect, useMemo, useRef, useState } from 'react';
import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog';
import { styled } from '@mui/system';
import axios from '../../utils/axios';
import { enqueueSnackbar } from 'notistack';
import { LoadingButton } from '@mui/lab';
import { fCurrency } from '../../utils/formatNumber';
import Iconify from '../../components/Iconify';
import Form from './Form';
export default function ClaimsCreateUpdate() {
const { themeStretch } = useSettings();
const { id } = useParams();
const isEdit = id ? true : false;
const [currentClaim, setCurrentClaim] = useState();
useEffect(() => {
if (isEdit) {
axios.get('/claims/' + id).then((res) => {
// console.log('Yeet', res.data);
setCurrentClaim(res.data);
});
}
}, [id]);
return (
<Page title={isEdit ? `Edit Claim : ${currentClaim?.id}` : "Create New Claim"}>
<Container maxWidth={themeStretch ? false : 'xl'}>
<Stack direction="row" alignItems="center">
<HeaderBreadcrumbs
heading={
!isEdit
? 'Create New Claim'
: `Edit Claim : ${currentClaim?.code}`
}
links={[
{ name: 'Dashboard', href: '/dashboard' },
{
name: 'Claim',
href: '/claims',
},
{ name: !isEdit ? 'Create' : currentClaim?.id ?? '' },
]}
/>
</Stack>
<Form isEdit={isEdit} currentClaim={currentClaim} />
</Container>
</Page>
);
}

View File

@@ -0,0 +1,596 @@
import * as Yup from 'yup';
import { useSnackbar } from 'notistack';
import { useNavigate } from 'react-router-dom';
import { yupResolver } from '@hookform/resolvers/yup';
import { Controller, useForm } from 'react-hook-form';
import React, { useEffect, useMemo, useState } from 'react';
import axios from '../../utils/axios';
import { FormProvider, RHFTextField } from '../../components/hook-form';
import {
Autocomplete,
Button,
Grid,
Stack,
Table,
TableBody,
TableCell,
TableRow,
TextField,
Typography,
useTheme,
List,
ListItem,
IconButton,
ListItemAvatar,
Avatar,
ListItemText,
} from '@mui/material';
import Iconify from '../../components/Iconify';
import { LoadingButton } from '@mui/lab';
import { fCurrency } from '../../utils/formatNumber';
import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog';
import { Add, DeleteOutline } from '@mui/icons-material';
type Props = {
isEdit: boolean;
currentClaim?: any;
};
export default function ClaimForm({ isEdit, currentClaim }: Props) {
const navigate = useNavigate();
const { enqueueSnackbar } = useSnackbar();
const NewCorporateSchema = 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'),
// file: Yup.boolean().required('Corporate Status is required'),
});
const defaultValues = useMemo(
() => ({
member: currentClaim?.member || {},
member_id: currentClaim?.member_id || null,
diagnosis_id: currentClaim?.diagnosis_id || null,
total_claim: currentClaim?.total_claim || 0,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[currentClaim]
);
const methods = useForm<any>({
resolver: yupResolver(NewCorporateSchema),
defaultValues,
});
const {
reset,
watch,
control,
setValue,
getValues,
setError,
handleSubmit,
formState: { isSubmitting },
} = methods;
const values = watch();
const [isCheckingLimit, setIsCheckingLimit] = useState(false);
const [isEligible, setIsEligible] = useState(false);
const [memberBenefits, setMemberBenefits] = useState([]);
const [diagnosisOption, setDiagnosisOption] = useState([]);
const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false);
const [member, setMember] = useState({})
useEffect(() => {
console.log('defaultValues', defaultValues);
if (isEdit && currentClaim) {
reset(defaultValues);
setMember(defaultValues.member)
}
if (!isEdit) {
reset(defaultValues);
setMember(defaultValues.member)
}
}, [isEdit, currentClaim]);
const fileSelected = (event, type) => {
const files = event.target.files;
const currentFiles = getValues(`uploaded_files.${type}`) ?? [];
setValue(`uploaded_files.${type}`, [...currentFiles, ...files]);
console.log('currentFiles', getValues('uploaded_files'));
};
const memberSelected = (member) => {
setMember(member)
};
const checkLimit = async () => {
console.log('CHECKING LIMIT');
};
const onSubmit = async (data: any) => {
try {
if (!isEdit) {
const response = await axios.post('/claims', data);
} else {
const response = await axios.put('/claims/' + currentClaim?.id ?? '', data);
}
reset();
enqueueSnackbar(
!isEdit ? 'Organizations Created Successfully!' : 'Organizations Udpated Successfully!',
{ variant: 'success' }
);
navigate('/claims');
} catch (error: any) {
if (error && error.response.status === 422) {
for (const [key, value] of Object.entries(error.response.data.errors)) {
setError(key, { message: value[0] });
enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' });
}
} else {
enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' });
}
}
const ascent = document?.querySelector('ascent');
if (ascent != null) {
ascent.innerHTML = '';
}
};
function generate(files, element: React.ReactElement) {
return files.map((value) =>
React.cloneElement(element, {
key: value,
})
);
}
const headStyle = {};
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}>
<Typography variant="h6">Member</Typography>
<Stack spacing={2} direction="row">
<Grid item xs={12}>
<RHFTextField
name="member_id"
label="Member"
variant="outlined"
fullWidth
value={member?.name || ''}
InputProps={{
readOnly: true,
}}
onClick={() => {
if (!isEdit) setIsMemberDialogOpen(true);
if (isEdit) enqueueSnackbar('Cannot Change Member', { variant: 'error' });
}}
/>
</Grid>
{/* <Grid item xs={2}>
<Button variant="outlined" fullWidth sx={{ p: 1.8 }} onClick={() => {
setIsMemberDialogOpen(true)
}}>
{member ? 'Change' : 'Search'}
</Button>
</Grid> */}
</Stack>
{member?.id && (
<Stack>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Table border="light-700">
<TableBody>
<TableRow>
<TableCell style={headStyle} align="left">
Name
</TableCell>
<TableCell align="left">{member?.full_name}</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
DOB
</TableCell>
<TableCell align="left">
{member?.birth_date} ({member?.age + ' years'})
</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Marital Status
</TableCell>
<TableCell align="left">{member?.marital_status}</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Record Type
</TableCell>
<TableCell align="left">{member?.record_type}</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Principal ID
</TableCell>
<TableCell align="left">
{member?.principal_id} (
{member?.relation_with_principal})
</TableCell>
</TableRow>
</TableBody>
</Table>
</Grid>
<Grid item xs={12} md={6}>
<Table border="light-700">
<TableBody>
<TableRow>
<TableCell style={headStyle} align="left">
Plan
</TableCell>
<TableCell align="left">{member?.current_plan?.code}</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Active
</TableCell>
<TableCell align="left">
{member?.current_plan?.start} -{' '}
{member?.current_plan?.end} (Active)
</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Corporate Limit
</TableCell>
<TableCell align="left">
{fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)}
</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Plan Usage
</TableCell>
<TableCell align="left">
{fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</Grid>
</Grid>
</Stack>
)}
<Controller
name="benefit"
control={control}
render={({ field: { onChange, value } }) => (
<Autocomplete
options={memberBenefits}
getOptionLabel={(option) =>
option ? `#${option.id} (${option.code}) ${option.description}` : ''
}
value={value || ''}
onChange={(event: any, newValue: any) => {
setValue('benefit_id', newValue?.id);
onChange(newValue);
}}
renderInput={(params) => (
<TextField
name="benefit"
{...params}
label="Benefit"
variant="outlined"
fullWidth
// onKeyPress={(event) => {
// if (event.key === 'Enter')
// searchDiagnosis(event.target.value)
// }}
/>
)}
/>
)}
/>
<Controller
name="diagnosis"
control={control}
render={({ field: { onChange, value } }) => (
<Autocomplete
options={diagnosisOption}
getOptionLabel={(option) => (option ? `(${option.code}) ${option.name}` : '')}
value={value || ''}
onChange={(event: any, newValue: any) => {
setValue('diagnosis_id', newValue?.id);
// setValue('diagnosis', newValue)
onChange(newValue);
}}
renderInput={(params) => (
<TextField
name="diagnosis"
{...params}
label="Diagnosis"
variant="outlined"
fullWidth
onKeyPress={(event) => {
if (event.key === 'Enter') searchDiagnosis(event.target.value);
}}
/>
)}
/>
)}
/>
{isCheckingLimit && (
<Stack
sx={{
backgroundColor: 'gray',
paddingY: 1,
paddingX: 1.5,
mb: 2,
borderRadius: '3-xl',
}}
>
{/* Checking */}
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
<Iconify
icon="bxs:info-circle"
width={12}
height={13}
sx={{ color: '#424242', marginRight: '6px' }}
/>
<Typography variant="caption" component="span">
Please Wait, Checking Eligibilty
</Typography>
</Typography>
</Stack>
)}
{false && isCheckingLimit == false && isEligible == null && (
<Stack
sx={{
backgroundColor: 'gray',
paddingY: 1,
paddingX: 1.5,
mb: 2,
borderRadius: '3-xl',
}}
>
{/* No Data Selected */}
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
<Iconify
icon="bxs:info-circle"
width={12}
height={13}
sx={{ color: '#424242', marginRight: '6px' }}
/>
<Typography variant="caption" component="span">
Please Select Diagnosis !
</Typography>
</Typography>
</Stack>
)}
{!isCheckingLimit && isEligible !== null && isEligible && (
<Stack
sx={{
backgroundColor: '#B2E8E8',
paddingY: 1,
paddingX: 1.5,
mb: 2,
borderRadius: '3-xl',
}}
>
{/* Eligible */}
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
<Iconify
icon="bxs:lock-alt"
width={12}
height={13}
sx={{ color: '#424242', marginRight: '6px' }}
/>
<Typography variant="caption" component="span">
Diagnosis is Eligible
</Typography>
</Typography>
<Typography sx={{ typography: 'caption', color: '#637381' }}>
125.000.000 / 125.000.000
</Typography>
</Stack>
)}
{!isCheckingLimit && isEligible !== null && !isEligible && (
<Stack
sx={{
backgroundColor: '#B2E8E8',
paddingY: 1,
paddingX: 1.5,
mb: 2,
borderRadius: '3-xl',
}}
>
{/* Not Eligible */}
{/* <Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
<Iconify
icon="bxs:lock-alt"
width={12}
height={13}
sx={{ color: '#424242', marginRight: '6px' }}
/>
<Typography variant="caption" component="span">
Not Eligible
</Typography>
</Typography>
<Typography sx={{ typography: 'caption', color: '#637381' }}>
125.000.000 / 125.000.000
</Typography> */}
</Stack>
)}
<RHFTextField type="number" name="total_claim" label="Total Claim" />
{isEdit && (
<React.Fragment>
<Typography variant="h6">Documents</Typography>
<List>
{(getValues('uploaded_files.invoice') && getValues('uploaded_files.invoice').length
? getValues('uploaded_files.invoice')
: []
).map((file, index) => (
<ListItem
secondaryAction={
<IconButton edge="end" aria-label="delete">
<DeleteOutline />
</IconButton>
}
>
<ListItemAvatar>
<Avatar>
{/* <FileIcon /> */}
I
</Avatar>
</ListItemAvatar>
<ListItemText primary={file.name} secondary={file.type} />
</ListItem>
))}
</List>
<Button
variant="outlined"
startIcon={<Add />}
component="label"
sx={{ paddingY: 2, width: '100%', ':hover': { border: 'none' } }}
>
Invoice
<input
name="invoice"
hidden
accept="image/*,application/pdf"
multiple
type="file"
onChange={(event) => {
fileSelected(event, 'invoice');
}}
/>
</Button>
<List>
{(getValues('uploaded_files.prescription') && getValues('uploaded_files.prescription').length
? getValues('uploaded_files.prescription')
: []
).map((file, index) => (
<ListItem
secondaryAction={
<IconButton edge="end" aria-label="delete">
<DeleteOutline />
</IconButton>
}
>
<ListItemAvatar>
<Avatar>
{/* <FileIcon /> */}
P
</Avatar>
</ListItemAvatar>
<ListItemText primary={file.name} secondary={file.type} />
</ListItem>
))}
</List>
<Button
variant="outlined"
startIcon={<Add />}
component="label"
sx={{ paddingY: 2, width: '100%', ':hover': { border: 'none' } }}
>
Prescription
<input
name="prescription"
hidden
accept="image/*,application/pdf"
multiple
type="file"
onChange={(event) => {
fileSelected(event, 'prescription');
}}
/>
</Button>
<List>
{(getValues('uploaded_files.diagnosis') && getValues('uploaded_files.diagnosis').length
? getValues('uploaded_files.diagnosis')
: []
).map((file, index) => (
<ListItem
secondaryAction={
<IconButton edge="end" aria-label="delete">
<DeleteOutline />
</IconButton>
}
>
<ListItemAvatar>
<Avatar>
{/* <FileIcon /> */}
DR
</Avatar>
</ListItemAvatar>
<ListItemText primary={file.name} secondary={file.type} />
</ListItem>
))}
</List>
<Button
variant="outlined"
startIcon={<Add />}
component="label"
sx={{ paddingY: 2, width: '100%', ':hover': { border: 'none' } }}
>
Doctor Result
<input
name="invoice"
hidden
accept="image/*,application/pdf"
multiple
type="file"
onChange={(event) => {
fileSelected(event, 'diagnosis');
}}
/>
</Button>
</React.Fragment>
)}
{isEligible === true ? (
<LoadingButton
onClick={handleSubmit(onSubmit)}
variant="contained"
color="success"
style={{ color: '#ffffff' }}
size="large"
fullWidth={true}
loading={isCheckingLimit}
>
Create Claim
</LoadingButton>
) : (
<LoadingButton
onClick={checkLimit}
variant="outlined"
size="large"
fullWidth={true}
loading={isCheckingLimit}
>
Check Limit
</LoadingButton>
)}
</Stack>
<MemberSelectDialog
openDialog={isMemberDialogOpen}
setOpenDialog={setIsMemberDialogOpen}
onSelect={memberSelected}
></MemberSelectDialog>
</FormProvider>
);
}

View File

@@ -0,0 +1,30 @@
import { Card, Stack } from "@mui/material";
import HeaderBreadcrumbs from "../../components/HeaderBreadcrumbs";
import Page from "../../components/Page";
import List from "./List";
export default function Claims() {
const pageTitle = 'Claim Request';
return (
<Page title={ pageTitle } sx={{ mx: 2}}>
<HeaderBreadcrumbs
heading={ pageTitle }
links={[
{ name: 'Dashboard', href: '/dashboard' },
{
name: 'Claim Request',
href: '/claim-requests',
},
]}
/>
{/* <Stack> */}
<List />
{/* </Stack> */}
</Page>
);
}

View File

@@ -0,0 +1,294 @@
// @mui
import {
Box,
Button,
Card,
Collapse,
IconButton,
MenuItem,
Table,
TableBody,
TableCell,
TableRow,
TextField,
Typography,
Stack,
Menu,
ButtonGroup,
Link,
Chip,
} 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, useEffect, useRef, useState } from 'react';
import { Navigate, useNavigate, useSearchParams } from 'react-router-dom';
// components
import axios from '../../utils/axios';
import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../@types/paginated-data';
import DataTable from '../../components/LaravelTable';
import { fCurrency } from '../../utils/formatNumber';
import EditRoundedIcon from '@mui/icons-material/EditRounded';
import { LoadingButton } from '@mui/lab';
import { enqueueSnackbar } from 'notistack';
import { Divider } from '@mui/material';
import Iconify from '@/components/Iconify';
// import LoadingButton from '@/theme/overrides/LoadingButton';
export default function List() {
const [searchParams, setSearchParams] = useSearchParams();
const [importResult, setImportResult] = useState(null);
const navigate = useNavigate();
function SearchInput(props: any) {
// SEARCH
const searchInput = useRef<HTMLInputElement>(null);
const [searchText, setSearchText] = useState('');
const handleSearchChange = (event: any) => {
const newSearchText = event.target.value ?? '';
setSearchText(newSearchText);
};
const handleSearchSubmit = (event: any) => {
event.preventDefault();
props.onSearch({ search: searchText }); // Trigger to Parent
};
useEffect(() => {
// Trigger First Search
setSearchText(searchParams.get('search') ?? '');
}, []);
return (
<form onSubmit={handleSearchSubmit} style={{ width: '100%' }}>
<TextField
id="search-input"
ref={searchInput}
label="Search"
variant="outlined"
fullWidth
onChange={handleSearchChange}
value={searchText}
/>
</form>
);
}
function ImportForm(props: any) {
// IMPORT
// Create Button Menu
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const createMenu = Boolean(anchorEl);
const importForm = useRef<HTMLInputElement>(null);
const [currentImportFileName, setCurrentImportFileName] = useState(null);
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
<SearchInput onSearch={applyFilter} />
{/* <Button
variant="outlined"
startIcon={<AddIcon />}
sx={{ p: 1.8 }}
onClick={() => {
navigate('/claims/create');
}}
>
Create
</Button> */}
</Stack>
</div>
);
}
// Dummy Default Data
const [dataTableIsLoading, setDataTableLoading] = useState(true);
const [dataTableData, setDataTableData] = useState<LaravelPaginatedData>(
LaravelPaginatedDataDefault
);
const loadDataTableData = async (appliedFilter: any | null = null) => {
setDataTableLoading(true);
const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]);
const response = await axios.get('/claim-requests', { params: filter });
// console.log(response.data);
setDataTableLoading(false);
setDataTableData(response.data);
};
const applyFilter = async (searchFilter: { search: string }) => {
await loadDataTableData(searchFilter);
setSearchParams(searchFilter);
};
const handlePageChange = (event: ChangeEvent, value: number): void => {
const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]);
loadDataTableData(filter);
setSearchParams(filter);
};
const handleApprove = (claimRequest) => {
axios.post(`claim-requests/${claimRequest.id}/approve`)
.then((response) => {
enqueueSnackbar('Success Approve', {variant: 'success'})
loadDataTableData()
})
.catch(({response}) => {
enqueueSnackbar(response.data.message ?? 'Something went wrong!', {variant : "error"})
})
}
useEffect(() => {
loadDataTableData();
}, []);
const headStyle = {
fontWeight: 'bold',
};
// Called on every row to map the data to the columns
function createData(data: any): any {
return {
...data,
};
}
{
/* ------------------ TABLE ROW ------------------ */
}
function Row(props: { row: ReturnType<typeof createData> }) {
const { row } = props;
const [open, setOpen] = React.useState(false);
const [loadingApprove, setLoadingApprove] = React.useState(false);
return (
<React.Fragment>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
<TableCell>
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
</IconButton>
</TableCell>
<TableCell align="left">{row.code}</TableCell>
<TableCell align="left">{row.member?.full_name}</TableCell>
<TableCell align="left">{row.submission_date}</TableCell>
<TableCell align="left">{row.service_type}</TableCell>
<TableCell align="right"><Chip label={row.status}/></TableCell>
<TableCell align="right">{ row.status == 'requested' && (<LoadingButton loading={loadingApprove} variant="outlined" onClick={() => {handleApprove(row)}}>Approve</LoadingButton> )}</TableCell>
</TableRow>
{/* COLLAPSIBLE ROW */}
<TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={99}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ borderBottom: 1 }}>
<Stack
divider={<Divider orientation="horizontal" flexItem />}
spacing={1}
sx={{ marginY: 2 }}
>
<Box>
<Typography fontWeight={600}>Berkas Hasil Penunjang</Typography>
{row.files_by_type?.result &&
row.files_by_type?.result.map((file, index) => (
<Stack direction="row" key={index}>
<Typography sx={{marginRight: 2}}>-</Typography> <a href={file.url} target="_blank">{file.name}</a>
</Stack>
))}
{ !row.files_by_type?.result && (
<Typography>Tidak ada berkas</Typography>
)}
</Box>
</Stack>
</Box>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
);
}
{
/* ------------------ END TABLE ROW ------------------ */
}
function TableContent() {
return (
<Table aria-label="collapsible table">
{/* ------------------ TABLE HEADER ------------------ */}
<TableBody>
<TableRow>
<TableCell style={headStyle} align="left" />
<TableCell style={headStyle} align="left">
Code
</TableCell>
<TableCell style={headStyle} align="left">
Member Name
</TableCell>
<TableCell style={headStyle} align="left">
Submission Date
</TableCell>
<TableCell style={headStyle} align="left">
Jenis Layanan
</TableCell>
<TableCell style={headStyle} align="left">
Status
</TableCell>
<TableCell style={headStyle} align="right">
Action
</TableCell>
</TableRow>
</TableBody>
{/* ------------------ END TABLE HEADER ------------------ */}
{/* ------------------ TABLE ROW ------------------ */}
{dataTableIsLoading ? (
<TableBody>
<TableRow>
<TableCell colSpan={8} align="center">
Loading
</TableCell>
</TableRow>
</TableBody>
) : dataTableData.data.length === 0 ? (
<TableBody>
<TableRow>
<TableCell colSpan={8} align="center">
No Data
</TableCell>
</TableRow>
</TableBody>
) : (
<TableBody>
{dataTableData.data.map((row) => (
<Row key={row.id} row={row} />
))}
</TableBody>
)}
{/* ------------------ END TABLE ROW ------------------ */}
</Table>
);
}
return (
<Card>
<ImportForm />
<DataTable
isLoading={dataTableIsLoading}
lastRequest={0}
data={dataTableData}
handlePageChange={handlePageChange}
TableContent={<TableContent />}
/>
</Card>
);
}

View File

@@ -221,6 +221,10 @@ export default function Router() {
path: 'claims', path: 'claims',
element: <Claims />, element: <Claims />,
}, },
{
path: 'claim-requests',
element: <ClaimRequests />,
},
{ {
path: 'claims/create', path: 'claims/create',
element: <ClaimsCreate />, element: <ClaimsCreate />,
@@ -339,3 +343,5 @@ const Profile = Loadable(lazy(() => import('../pages/Profile/Index')));
const Claims = Loadable(lazy(() => import('../pages/Claims/Index'))); const Claims = Loadable(lazy(() => import('../pages/Claims/Index')));
const ClaimsCreate = Loadable(lazy(() => import('../pages/Claims/CreateUpdate'))); const ClaimsCreate = Loadable(lazy(() => import('../pages/Claims/CreateUpdate')));
const ClaimRequests = Loadable(lazy(() => import('../pages/ClaimRequests/Index')));

View File

@@ -0,0 +1,4 @@
REACT_APP_HOST_API_URL="http://localhost:8000"
VITE_API_URL="http://localhost:8000/api/hospitalportal"

View File

@@ -0,0 +1 @@
VITE_API_URL="https://aso-api.linksehat.dev/api/hospitalportal"

View File

@@ -0,0 +1,8 @@
// .eslintignore
build/*
public/*
src/react-app-env.d.ts
src/reportWebVitals.ts
src/service-worker.ts
src/serviceWorkerRegistration.ts
src/setupTests.ts

View File

@@ -0,0 +1,54 @@
{
"plugins": [
"prettier",
"@typescript-eslint"
],
"extends": [
"airbnb-typescript",
"react-app",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"**/tsconfig.json"
]
},
"settings": {
"import/resolver": {
"typescript": {
"alwaysTryTypes": true,
"exceptAfterSingleLine": true
}
}
},
"rules": {
"react/jsx-key": 2,
"arrow-body-style": 1,
"import/no-duplicates": 1,
"react/self-closing-comp": 1,
"@typescript-eslint/no-shadow": 0,
"import/no-useless-path-segments": 1,
"import/no-extraneous-dependencies": 0,
"@typescript-eslint/naming-convention": 0,
"import/extensions": "never",
"object-curly-spacing": [
1,
"always"
],
"@typescript-eslint/no-unused-vars": [
1,
{
"vars": "all",
"args": "none"
}
],
"prefer-destructuring": [
1,
{
"object": true,
"array": false
}
]
}
}

26
frontend/hospital-portal/.gitignore vendored Executable file
View File

@@ -0,0 +1,26 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
/.env
/.env.local
/.env.development.local
/.env.test.local
/.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.eslintcache

View File

@@ -0,0 +1,12 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) /index.html [QSA,L]
</IfModule>
<IfModule pagespeed_module>
ModPagespeed off
</IfModule>

View File

@@ -0,0 +1,22 @@
{
"0 debug pnpm:scope": {
"selected": 1
},
"1 error pnpm": {
"code": "ELIFECYCLE",
"errno": "ENOENT",
"syscall": "spawn",
"file": "sh",
"pkgid": "@minimal/material-kit-react@3.2.0",
"stage": "start",
"script": "vite",
"pkgname": "@minimal/material-kit-react",
"err": {
"name": "pnpm",
"message": "@minimal/material-kit-react@3.2.0 start: `vite`\nspawn ENOENT",
"code": "ELIFECYCLE",
"stack": "pnpm: @minimal/material-kit-react@3.2.0 start: `vite`\nspawn ENOENT\n at ChildProcess.<anonymous> (/home/dell/.nvm/versions/node/v16.13.0/pnpm-global/5/node_modules/.pnpm/pnpm@7.0.0/node_modules/pnpm/dist/pnpm.cjs:93294:22)\n at ChildProcess.emit (node:events:390:28)\n at maybeClose (node:internal/child_process:1064:16)\n at Process.ChildProcess._handle.onexit (node:internal/child_process:301:5)"
}
},
"2 warn pnpm:global": " Local package.json exists, but node_modules missing, did you mean to install?"
}

View File

@@ -0,0 +1,6 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2
}

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- Favicon -->
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="/manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<!-- Using Google Font -->
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- Using Local Font -->
<link rel="stylesheet" type="text/css" href="/fonts/index.css" />
<title>Dashboard</title>
<meta name="description"
content="The starting point for your next project with Minimal UI Kit, built on the newest version of Material-UI ©, ready to be customized to your style" />
<meta name="keywords" content="react,material,kit,application,dashboard,admin,template" />
<meta name="author" content="Minimal UI Kit" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

15880
frontend/hospital-portal/package-lock.json generated Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
{
"name": "@minimal/material-kit-react",
"author": "minimals.cc",
"version": "3.2.0",
"description": "Simple React Scripts & Typescript",
"private": true,
"scripts": {
"lint": "eslint --ext .ts,.tsx ./src",
"lint:fix": "eslint --fix --ext .ts,.tsx ./src",
"start": "vite --port=3000",
"build": "vite build --mode production && cp .htaccess build/.htaccess && rm -f -r ../../public/hospital-portal && cp -r build ../../public/hospital-portal",
"build-staging": "vite build --mode staging && cp .htaccess build/.htaccess && rm -f -r ../../public/hospital-portal-staging && cp -r build ../../public/hospital-portal-staging",
"serve": "vite preview",
"clear-all": "rm -rf build node_modules",
"re-start": "rm -rf build node_modules && yarn install && yarn start",
"re-build": "rm -rf build node_modules && yarn install && yarn build"
},
"eslintConfig": {
"extends": [
"react-app"
]
},
"babel": {
"presets": [
"@babel/preset-react"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"@ajoelp/json-to-formdata": "^1.4.0",
"@date-io/date-fns": "^2.16.0",
"@emotion/cache": "^11.10.5",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@hookform/resolvers": "^2.9.10",
"@iconify/react": "^3.2.2",
"@mui/icons-material": "^5.11.0",
"@mui/lab": "5.0.0-alpha.80",
"@mui/material": "^5.11.8",
"@mui/system": "^5.11.7",
"@mui/utils": "^5.11.7",
"@mui/x-data-grid": "^5.17.21",
"@mui/x-date-pickers": "5.0.0-beta.2",
"@vitejs/plugin-react": "^1.3.2",
"apexcharts": "^3.36.3",
"axios": "^0.27.2",
"change-case": "^4.1.2",
"csstype": "^3.1.1",
"date-fns": "^2.29.3",
"framer-motion": "^6.5.1",
"highlight.js": "^11.7.0",
"history": "^5.3.0",
"jsx-runtime": "^1.2.0",
"lodash": "^4.17.21",
"notistack": "3.0.0-alpha.11",
"nprogress": "^0.2.0",
"numeral": "^2.0.6",
"react": "^17.0.2",
"react-apexcharts": "^1.4.0",
"react-dom": "^17.0.2",
"react-dropzone": "^14.2.3",
"react-helmet-async": "^1.3.0",
"react-hook-form": "^7.43.0",
"react-intersection-observer": "^8.34.0",
"react-lazy-load-image-component": "^1.5.6",
"react-quill": "2.0.0-beta.4",
"react-router": "^6.8.0",
"react-router-dom": "^6.8.0",
"simplebar": "^5.3.9",
"simplebar-react": "^2.4.3",
"stylis": "^4.1.3",
"stylis-plugin-rtl": "^2.1.1",
"vite": "^3.2.5",
"vite-plugin-svgr": "^2.4.0",
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-syntax-flow": "^7.18.6",
"@babel/plugin-transform-react-jsx": "^7.20.13",
"@types/lodash": "^4.14.191",
"@types/nprogress": "^0.2.0",
"@types/react": "^17.0.53",
"@types/react-dom": "^17.0.18",
"@types/react-lazy-load-image-component": "^1.5.2",
"@types/stylis": "^4.0.2",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"eslint": "^8.33.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "^16.2.0",
"eslint-config-prettier": "^8.6.0",
"eslint-config-react-app": "7.0.0",
"eslint-import-resolver-typescript": "^2.7.1",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "4.3.0",
"prettier": "^2.8.3",
"typescript": "^4.9.5",
"vite-plugin-pwa": "^0.12.8"
}
}

6202
frontend/hospital-portal/pnpm-lock.yaml generated Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
/* /index.html 200

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,18 @@
@font-face {
font-family: 'CircularStd';
font-weight: 400;
font-style: normal;
src: local('CircularStd'), url('CircularStd-Book.otf') format('opentype');
}
@font-face {
font-family: 'CircularStd';
font-weight: 500;
font-style: normal;
src: local('CircularStd'), url('CircularStd-Medium.otf') format('opentype');
}
@font-face {
font-family: 'CircularStd';
font-weight: 700;
font-style: normal;
src: local('CircularStd'), url('CircularStd-Bold.otf') format('opentype');
}

View File

@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m20.247634 1c1.0125221 0 1.8333334.82081129 1.8333334 1.83333333s-.8208113 1.83333334-1.8333334 1.83333334c-.3158442 0-.6130339-.07986936-.8724738-.22051281l-3.0249251 3.47961717c.1346337.25513483.2108509.5458717.2108509.85441003 0 1.01252204-.8208113 1.83333334-1.8333334 1.83333334-.9820883 0-1.7838173-.7722101-1.8311257-1.74256896l-2.2033918-.75849737c-.336256.40778098-.84535009.66773299-1.41515923.66773299-.32712483 0-.63423886-.08567643-.90012689-.2358141l-2.87560465 2.41277624c.05416355.1730906.08335496.3572185.08335496.5481644 0 1.012522-.8208113 1.8333333-1.83333334 1.8333333s-1.83333333-.8208113-1.83333333-1.8333333c0-1.0125221.82081129-1.83333335 1.83333333-1.83333335.33090488 0 .64133381.08766791.90932763.24104456l2.86960725-2.40787374c-.05621505-.1760311-.0865583-.3636207-.0865583-.55829735 0-1.01252204.8208113-1.83333333 1.83333334-1.83333333.97577423 0 1.77350093.76231258 1.83011983 1.7238777l2.2160025.76325559c.336304-.39976002.8402621-.65379996 1.4035544-.65379996.2130474 0 .4176071.03634016.6078186.10315996l3.1693503-3.64581344c-.0588143-.17965899-.0906208-.37154554-.0906208-.57086091 0-1.01252204.8208113-1.83333333 1.8333333-1.83333333z" opacity=".48"/><path d="m21.1666667 9.60855714c.506261 0 .9166666.41040566.9166666.91666666v10.7540685c0 .2761423-.2238576.5-.5.5h-2.6666666c-.2761424 0-.5-.2238577-.5-.5v-10.7540685c0-.506261.4104056-.91666666.9166666-.91666666zm-5.5 6.42549146c.506261 0 .9166666.4104057.9166666.9166667v4.328577c0 .2761423-.2238576.5-.5.5h-2.6666666c-.2761424 0-.5-.2238577-.5-.5v-4.328577c0-.506261.4104056-.9166667.9166666-.9166667zm-5.5-1.8405511c.506261 0 .9166666.4104057.9166666.9166667v6.1691281c0 .2761423-.2238576.5-.5.5h-2.66666663c-.27614238 0-.5-.2238577-.5-.5v-6.1691281c0-.506261.41040564-.9166667.91666666-.9166667zm-5.50000003 4.7135227c.50626102 0 .91666666.4104057.91666666.9166667v1.4556054c0 .2761423-.22385762.5-.5.5h-2.66666666c-.27614238 0-.5-.2238577-.5-.5v-1.4556054c0-.506261.41040564-.9166667.91666666-.9166667z"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.5367 20.5812H2.45436C1.92714 20.5812 1.5 21.0083 1.5 21.536C1.5 22.0628 1.92714 22.4899 2.45436 22.4899H21.5362C22.0635 22.4899 22.4906 22.0628 22.4906 21.536C22.4902 21.0083 22.063 20.5812 21.5367 20.5812Z" fill="#637381"/>
<path d="M3.64772 18.1001C3.1205 18.1001 2.69336 18.5273 2.69336 19.0545C2.69336 19.5817 3.1205 20.0093 3.64772 20.0093H20.3446C20.8718 20.0093 21.2989 19.5817 21.2989 19.0545C21.2989 18.5273 20.8718 18.1001 20.3446 18.1001H20.1064V9.51266H20.3446C20.6086 9.51266 20.8213 9.29909 20.8213 9.03592C20.8213 8.77276 20.6077 8.55919 20.3446 8.55919H3.64772C3.38411 8.55919 3.17099 8.77276 3.17099 9.03592C3.17099 9.29909 3.38456 9.51266 3.64772 9.51266H3.88631V18.0997H3.64772V18.1001ZM18.1977 9.51266V18.0997H15.3355V9.51266H18.1977ZM13.4268 9.51266V18.0997H10.5646V9.51266H13.4268ZM5.79414 9.51266H8.65633V18.0997H5.79414V9.51266Z" fill="#637381"/>
<path opacity="0.48" d="M2.45438 7.70134H21.5363C21.5394 7.70134 21.543 7.70134 21.5456 7.70134C22.0733 7.70134 22.5 7.2742 22.5 6.74698C22.5 6.32788 22.2301 5.97268 21.8553 5.844L12.3876 1.58377C12.1387 1.47208 11.8541 1.47208 11.6048 1.58377L2.06298 5.87706C1.65238 6.06204 1.42674 6.50794 1.52146 6.94759C1.61574 7.38724 2.00445 7.70134 2.45438 7.70134Z" fill="#637381"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m14.984127 0c2.7619047 0 5.015873 2.25396825 5.015873 5.01587302v9.96825398c0 2.7619047-2.2539683 5.015873-5.015873 5.015873h-9.984127c-2.74603175 0-5-2.2539683-5-5v-9.98412698c0-2.76190477 2.25396825-5.01587302 5-5.01587302zm-4.5079365 3.80952381h-2.76190479c-2.17460317 0-3.9047619 1.73015873-3.9047619 3.88888889v4.6031746c0 2.1587302 1.73015873 3.8888889 3.9047619 3.8888889h4.53968259c2.1746031 0 3.9523809-1.7460318 3.9682539-3.9047619v-3.15873017l-.031746-.15873016-.1111111-.22222222-.1746032-.14285715c-.2380952-.17460317-1.3968254.01587302-1.7142857-.26984127-.2222222-.2063492-.2539683-.57142857-.3174603-1.06349206-.1111111-.96825397-.2063492-1.01587302-.3492064-1.33333333-.5396825-1.15873016-2.031746-2.12698413-3.047619-2.12698413zm1.7460317 7.61904759c.4126984 0 .7460318.3809524.7460318.7936508s-.3333334.7936508-.7460318.7936508h-4.46031744c-.42857143 0-.76190476-.3809524-.76190476-.7936508s.34920635-.7936508.76190476-.7936508zm-2.26984125-4.44444442c.42857145 0 .76190475.30158731.76190475.71428572s-.3492063.71428571-.76190475.71428571h-2.19047619c-.41269841 0-.76190476-.3015873-.76190476-.71428571s.34920635-.71428572.76190476-.71428572z" transform="translate(2 2)"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g fill="#637381"><path d="m16.8125 2.83333h-1.1459v-.91667c0-.50599-.4097-.91666-.9166-.91666s-.9167.41067-.9167.91666v.91667h-7.33331v-.91667c0-.50599-.40975-.91666-.91667-.91666-.50691 0-.91666.41067-.91666.91666v.91667h-1.14583c-1.39058 0-2.52083 1.13025-2.52083 2.52083v10.31244c0 1.5162 1.23383 2.75 2.74999 2.75h4.58333c.50691 0 .91666-.4106.91666-.9166s-.40975-.9167-.91666-.9167h-4.58333c-.50599 0-.91666-.4116-.91666-.9167v-7.33328h14.66667c0 .506.4097.91666.9166.91666s.9167-.41066.9167-.91666v-2.97916c0-1.39058-1.1302-2.52083-2.5208-2.52083z"/><path d="m17.0413 11.0834c-3.2853 0-5.9583 2.673-5.9583 5.9583s2.673 5.9583 5.9583 5.9583 5.9583-2.673 5.9583-5.9583-2.673-5.9583-5.9583-5.9583zm2.7555 4.9545-2.9791 3.4375c-.1669.1925-.4061.3062-.66.3163-.011 0-.022 0-.033 0-.243 0-.4758-.0963-.6481-.2686l-1.6042-1.6042c-.3584-.3584-.3584-.9377 0-1.2961.3584-.3585.9378-.3585 1.2962 0l.9084.9084 2.3338-2.6932c.3318-.3831.9112-.4226 1.2934-.0926.3823.331.4235.9103.0926 1.2925z" opacity=".48"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g transform="translate(1.5 1.5)"><path d="m18.9 3.15h-16.8c-1.15931815.00115758-2.09884242.94068185-2.1 2.1v12.95c.00115758 1.1593181.94068185 2.0988424 2.1 2.1h16.8c1.1593181-.0011576 2.0988424-.9406819 2.1-2.1v-12.95c-.0011576-1.15931815-.9406819-2.09884242-2.1-2.1zm0 16.45h-16.8c-.77319865 0-1.4-.6268014-1.4-1.4v-10.5h19.6v10.5c0 .7731986-.6268014 1.4-1.4 1.4zm-2.45-17.15v-1.75c0-.38659932-.3134007-.7-.7-.7s-.7.31340068-.7.7v1.75zm-10.5 0v-1.75c0-.38659932-.31340068-.7-.7-.7s-.7.31340068-.7.7v1.75z"/><path d="m5.99840724 14.441644c.55228475 0 1 .4477153 1 1v1.6c0 .5522848-.44771525 1-1 1h-2.2c-.55228475 0-1-.4477152-1-1v-1.6c0-.5522847.44771525-1 1-1zm5.60159276 0c.5522847 0 1 .4477153 1 1v1.6c0 .5522848-.4477153 1-1 1h-2.2c-.55228475 0-1-.4477152-1-1v-1.6c0-.5522847.44771525-1 1-1zm-5.60159276-5.0001284c.55228475 0 1 .44771525 1 1v1.6c0 .5522848-.44771525 1-1 1h-2.2c-.55228475 0-1-.4477152-1-1v-1.6c0-.55228475.44771525-1 1-1zm5.60159276 0c.5522847 0 1 .44771525 1 1v1.6c0 .5522848-.4477153 1-1 1h-2.2c-.55228475 0-1-.4477152-1-1v-1.6c0-.55228475.44771525-1 1-1zm5.6015928 0c.5522847 0 1 .44771525 1 1v1.6c0 .5522848-.4477153 1-1 1h-2.2c-.5522848 0-1-.4477152-1-1v-1.6c0-.55228475.4477152-1 1-1z" opacity=".48"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g transform="translate(2 2)"><path d="m9.58985382 1.32162476c1.66180188 0 3.06430158 1.12239456 3.49720258 2.64777104h-6.99440518c.432901-1.52537648 1.83540001-2.64777104 3.4972026-2.64777104zm4.62891648-1.32162476c.9093636 0 1.6555835.7429914 1.6555835 1.65235392 0 .90937489-.7462199 1.6529999-1.6555835 1.6529999-.909364 0-1.6529999-.74362501-1.6529999-1.6529999 0-.90936252.7436359-1.65235392 1.6529999-1.65235392z" opacity=".48"/><path d="m7.60483293 15.8737079c.90936397 0 1.6529999.7436373 1.6529999 1.6529999 0 .9093749-.74362997 1.6549376-1.6529999 1.6549376s-1.65235399-.7455627-1.65235399-1.6549376c0-.9093626.74299001-1.6529999 1.65235399-1.6529999zm6.61393737 0c.9093636 0 1.6555835.7436373 1.6555835 1.6529999 0 .9093749-.7462136 1.6549376-1.6555835 1.6549376-.90937 0-1.6529999-.7455627-1.6529999-1.6549376 0-.9093626.7436349-1.6529999 1.6529999-1.6529999zm-10.58074958-13.22722875c.47021948.00126272.87961059.33725114.97151692.79840103l.23706567 1.18597394h13.34220869c.6357812 0 1.1062464.60903974.9644116 1.2234396l-1.9850212 8.59895818c-.1036535.4490251-.5105252.7589977-.9644112.7589977h-10.58333325c-.47083827 0-.88484536-.3341221-.97216282-.8009847l-1.82805386-9.78234852h-1.82869973c-.30322176 0-.57131938-.12557224-.74220274-.31781022-.17088343-.19224988-.2493388-.43483146-.2493388-.67373135 0-.2388999.07845541-.48148147.2493388-.67373136.17088336-.19223764.43898098-.3171643.74220274-.3171643z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 2.5)"><path d="m8.3418125.28845833c-4.72554167 0-8.3418125 3.20658334-8.3418125 6.70210417 0 3.4955208 3.12225 5.5209583 3.12225 5.5209583l-.5074375 1.923375c-.06708333.2534792.20460417.462875.4326875.3335l2.28083333-1.2923125c1.00864584.3210417 3.14908334.4235834 3.35752084.4331667-.1495-.5184583-.22329167-1.02925-.22329167-1.5060208 0-2.69770837 2.348875-6.49750003 7.5703542-6.49750003.1188333 0 .2376666.002875.3565.00766666-.6947917-3.32541666-4.1476667-5.6249375-8.0476042-5.6249375z"/><path d="m23 11.9810833c0-3.06187497-3.5923125-5.3038958-6.9675625-5.3038958-4.7734583 0-6.79889583 3.4476042-6.79889583 5.7260417 0 2.2832291 2.02495833 5.7260416 6.79889583 5.7260416 1.0560833 0 1.9851875-.1514166 2.7930625-.41975l1.7954375 1.1394584c.1418333.0900833.3210417-.0368959.2831875-.2007709l-.4072917-1.768125c1.69625-1.181625 2.5031667-3.0441458 2.5031667-4.899z" opacity=".48"/></g></svg>

After

Width:  |  Height:  |  Size: 1007 B

View File

@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g transform="translate(2 4)"><path d="m18.38 4.57-1.23 1.85c1.2049 2.40314304 1.1222508 5.2507848-.22 7.58h-13.86c-1.76350331-3.059309-1.31261601-6.91298595 1.10947843-9.48257235s6.24242627-3.24722187 9.40052157-1.66742765l1.85-1.23c-3.8761922-2.48556317-8.94860517-2.00294347-12.28650726 1.16901179-3.3379021 3.17195526-4.07833512 8.21319061-1.79349274 12.21098821.35510459.6150891 1.00977788.9957131 1.72 1.0000158h13.85c.7173695.0028322 1.3813181-.3787474 1.74-1.0000158 1.8786438-3.25433 1.7743473-7.28712667-.27-10.44z"/><path d="m8.59 11.41c.37513651.3755541.8841815.5865733 1.415.5865733s1.0398635-.2110192 1.415-.5865733l5.66-8.49-8.49 5.66c-.37555409.37513651-.58657331.8841815-.58657331 1.415s.21101922 1.0398635.58657331 1.415z" opacity=".48"/></g></svg>

After

Width:  |  Height:  |  Size: 849 B

View File

@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m7.94101676 5.6427832v2.74464258c0 .33989649.27513281.6150293.6150293.6150293.30862393 0 .56440212-.22738249.60835663-.52409402l.00667266-.09093528v-2.74464258h5.65814645v2.74464258c0 .33989649.275543.6150293.6150293.6150293.3089968 0 .5644699-.22738249.6083659-.52409402l.0066634-.09093528v-2.74464258h1.8524297c.7157099 0 1.3265895.52397061 1.44811 1.21681899l.0176654.14071617.9376992 11.81697074c.0730078.9483222-.2554043 1.8905332-.900334 2.5867324-.5953575.6426454-1.4202201 1.03272-2.2901578 1.0889289l-.2183168.0070496h-9.81241403c-.94918359 0-1.8635039-.3997793-2.50847461-1.0959375-.59531971-.6426454-.92094397-1.4949103-.91044964-2.3668484l.00966449-.2182844.93810937-11.82025197c.0545636-.71224914.62412163-1.28073532 1.3241451-1.34901387l.14163029-.00688066zm4.09817114 3.97089698c-.2266736-.00327725-.4135824.17768425-.4169046.40501402v.7555922c-1.4912918.1060463-2.50536524 1.0273421-2.50536524 2.253508 0 1.5045398 1.27919924 1.9221212 2.50536524 2.253508v2.6511967c-.6469005-.0868303-1.2566612-.3552579-1.7564176-.7754643-.09411064-.0748948-.21077995-.1166488-.33140728-.1192943-.30554685.0212051-.54083371.2770409-.5369249.5832645-.00060704.1338754.05174957.2624598.14586021.3579034.68467596.6164053 1.56487397.9710069 2.48549307 1.0008254l.0006563.7430004c.0106025.2266735.2034791.4016467.4301527.3903879.2233717 0 .4043127-.180941.4043127-.4043128v-.7423236c1.8094305-.1192943 2.538506-1.2195419 2.538506-2.3860709 0-1.5642177-1.3122785-2.041436-2.5384649-2.3728433v-2.3330375c.4990796.0841848.9696739.2889765 1.3719974.5965126.0802063.0550021.1743169.0848411.2717499.0861536.3121708 0 .5666941-.251201.5699959-.5633719.0006562-.1338754-.0517004-.2624598-.145811-.3579035-.5812753-.4977671-1.3103508-.7913784-2.0745563-.8351217v-.7821089c0-.22337175-.180941-.40431274-.4043128-.40431274-.0046347-.00070128-.00929-.00070128-.0139248-.00070128zm.4248617 5.91949992c.7489681.2120925 1.3322121.4970904 1.3255881 1.1930252 0 .503735-.3446554 1.1002476-1.3255881 1.2195419zm-.8351218-3.7050349v2.1673339c-.7224514-.2120925-1.2858233-.430809-1.2858233-1.0472142 0-.6164053.510359-1.0604828 1.2858233-1.1201197zm.3712413-10.3281452c2.2382637 0 4.0591523 1.82125781 4.0591523 4.05952148v.08322071h-1.2300175v-.08322071c0-1.56011132-1.2689825-2.8295039-2.8290938-2.8295039s-2.82909373 1.26939258-2.82909373 2.8295039v.08322071h-1.23005859v-.08322071c0-2.23826367 1.82084765-4.05952148 4.05911132-4.05952148z"/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ic_kanban</title>
<g id="ic_kanban" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M20,3 C21.1045695,3 22,3.8954305 22,5 L22,15 C22,16.1045695 21.1045695,17 20,17 L4,17 C2.8954305,17 2,16.1045695 2,15 L2,5 C2,3.8954305 2.8954305,3 4,3 L20,3 Z M11.5,6 L6.5,6 C5.67157288,6 5,6.67157288 5,7.5 L5,7.5 L5,9.5 C5,10.3284271 5.67157288,11 6.5,11 L6.5,11 L11.5,11 C12.3284271,11 13,10.3284271 13,9.5 L13,9.5 L13,7.5 C13,6.67157288 12.3284271,6 11.5,6 L11.5,6 Z" id="Combined-Shape" fill="#000000"></path>
<path d="M8,21 L16,21 M12,17 L12,21" id="Combined-Shape" stroke="#000000" stroke-width="2" opacity="0.48" stroke-linecap="round" stroke-linejoin="round"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 915 B

View File

@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g transform="translate(1.5 4)"><path d="m11.0988387 10.3846452c-.1981935.1238709-.4211613.1734193-.6193548.1734193-.1981936 0-.4211613-.0495484-.61935487-.1734193l-9.86012903-6.02012907v8.00206447c0 1.7094194 1.38735484 3.0967742 3.09677419 3.0967742h14.79019351c1.7094194 0 3.0967742-1.3873548 3.0967742-3.0967742v-8.00206447z"/><path d="m17.8869677.00425806h-14.79019351c-1.46167742 0-2.70038709 1.04051613-2.99767742 2.42787097l10.40516133 6.34219355 10.3803871-6.34219355c-.2972904-1.38735484-1.536-2.42787097-2.9976775-2.42787097z" opacity=".48"/></g></svg>

After

Width:  |  Height:  |  Size: 646 B

View File

@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g transform="translate(5 3)"><path d="m7 8c2.209139 0 4-1.790861 4-4s-1.790861-4-4-4-4 1.790861-4 4 1.790861 4 4 4z" opacity=".48"/><path d="m13 18.0000001c.5522847 0 1-.4477154 1-1 0-3.8659933-3.1340068-7-7-7-3.86599321 0-7 3.1340067-7 7 0 .5522846.44771525 1 1 1z"/></g></svg>

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Some files were not shown because too many files have changed in this diff Show More