|
@ -1,4 +1,4 @@
|
||||||
APP_NAME=Laravel
|
APP_NAME=GoGoSantri
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Auth\LoginRequest;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class AuthenticatedSessionController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the login view.
|
||||||
|
*/
|
||||||
|
public function create(): Response
|
||||||
|
{
|
||||||
|
return Inertia::render('Auth/Login', [
|
||||||
|
'canResetPassword' => Route::has('password.request'),
|
||||||
|
'status' => session('status'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming authentication request.
|
||||||
|
*/
|
||||||
|
public function store(LoginRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->authenticate();
|
||||||
|
|
||||||
|
$request->session()->regenerate();
|
||||||
|
|
||||||
|
session()->flash('success', 'Login berhasil!');
|
||||||
|
|
||||||
|
return redirect()->intended(RouteServiceProvider::HOME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy an authenticated session.
|
||||||
|
*/
|
||||||
|
public function destroy(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
Auth::guard('web')->logout();
|
||||||
|
|
||||||
|
$request->session()->invalidate();
|
||||||
|
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
session()->flash('success', 'Logout berhasil!');
|
||||||
|
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class ConfirmablePasswordController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Show the confirm password view.
|
||||||
|
*/
|
||||||
|
public function show(): Response
|
||||||
|
{
|
||||||
|
return Inertia::render('Auth/ConfirmPassword');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm the user's password.
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
if (! Auth::guard('web')->validate([
|
||||||
|
'email' => $request->user()->email,
|
||||||
|
'password' => $request->password,
|
||||||
|
])) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'password' => __('auth.password'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->session()->put('auth.password_confirmed_at', time());
|
||||||
|
|
||||||
|
return redirect()->intended(RouteServiceProvider::HOME);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EmailVerificationNotificationController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Send a new email verification notification.
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
|
return redirect()->intended(RouteServiceProvider::HOME);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->user()->sendEmailVerificationNotification();
|
||||||
|
|
||||||
|
return back()->with('status', 'verification-link-sent');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class EmailVerificationPromptController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the email verification prompt.
|
||||||
|
*/
|
||||||
|
public function __invoke(Request $request): RedirectResponse|Response
|
||||||
|
{
|
||||||
|
return $request->user()->hasVerifiedEmail()
|
||||||
|
? redirect()->intended(RouteServiceProvider::HOME)
|
||||||
|
: Inertia::render('Auth/VerifyEmail', ['status' => session('status')]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Auth\Events\PasswordReset;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\Rules;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class NewPasswordController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the password reset view.
|
||||||
|
*/
|
||||||
|
public function create(Request $request): Response
|
||||||
|
{
|
||||||
|
return Inertia::render('Auth/ResetPassword', [
|
||||||
|
'nis' => $request->nis,
|
||||||
|
'token' => $request->route('token'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming new password request.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'token' => 'required',
|
||||||
|
'nis' => 'required|nis',
|
||||||
|
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Here we will attempt to reset the user's password. If it is successful we
|
||||||
|
// will update the password on an actual user model and persist it to the
|
||||||
|
// database. Otherwise we will parse the error and return the response.
|
||||||
|
$status = Password::reset(
|
||||||
|
$request->only('nis', 'password', 'password_confirmation', 'token'),
|
||||||
|
function ($user) use ($request) {
|
||||||
|
$user->forceFill([
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
'remember_token' => Str::random(60),
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
event(new PasswordReset($user));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the password was successfully reset, we will redirect the user back to
|
||||||
|
// the application's home authenticated view. If there is an error we can
|
||||||
|
// redirect them back to where they came from with their error message.
|
||||||
|
if ($status == Password::PASSWORD_RESET) {
|
||||||
|
return redirect()->route('login')->with('status', __($status));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'nis' => [trans($status)],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
class PasswordController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Update the user's password.
|
||||||
|
*/
|
||||||
|
public function update(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'current_password' => ['required', 'current_password'],
|
||||||
|
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request->user()->update([
|
||||||
|
'password' => Hash::make($validated['password']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class PasswordResetLinkController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the password reset link request view.
|
||||||
|
*/
|
||||||
|
public function create(): Response
|
||||||
|
{
|
||||||
|
return Inertia::render('Auth/ForgotPassword', [
|
||||||
|
'status' => session('status'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming password reset link request.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'email' => 'required|email',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// We will send the password reset link to this user. Once we have attempted
|
||||||
|
// to send the link, we will examine the response then see the message we
|
||||||
|
// need to show to the user. Finally, we'll send out a proper response.
|
||||||
|
$status = Password::sendResetLink(
|
||||||
|
$request->only('email')
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($status == Password::RESET_LINK_SENT) {
|
||||||
|
return back()->with('status', __($status));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'email' => [trans($status)],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Auth\Events\Registered;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Validation\Rules;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class RegisteredUserController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the registration view.
|
||||||
|
*/
|
||||||
|
public function create(): Response
|
||||||
|
{
|
||||||
|
return Inertia::render('Auth/Register');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming registration request.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
// 'name' => 'required|string|max:255',
|
||||||
|
'nis' => 'required|string|alpha_num|max:255|unique:' . User::class,
|
||||||
|
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::create([
|
||||||
|
// 'name' => $request->name,
|
||||||
|
'nis' => $request->nis,
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
'level' => 2
|
||||||
|
]);
|
||||||
|
|
||||||
|
event(new Registered($user));
|
||||||
|
|
||||||
|
Auth::login($user);
|
||||||
|
|
||||||
|
return redirect(RouteServiceProvider::HOME);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Auth\Events\Verified;
|
||||||
|
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
|
||||||
|
class VerifyEmailController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Mark the authenticated user's email address as verified.
|
||||||
|
*/
|
||||||
|
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
|
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->user()->markEmailAsVerified()) {
|
||||||
|
event(new Verified($request->user()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,65 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Models\DetailPayment;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class DetailPaymentController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Display a listing of the resource.
|
|
||||||
*/
|
|
||||||
public function index()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for creating a new resource.
|
|
||||||
*/
|
|
||||||
public function create()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
*/
|
|
||||||
public function store(Request $request)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the specified resource.
|
|
||||||
*/
|
|
||||||
public function show(DetailPayment $detailPayment)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
*/
|
|
||||||
public function edit(DetailPayment $detailPayment)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the specified resource in storage.
|
|
||||||
*/
|
|
||||||
public function update(Request $request, DetailPayment $detailPayment)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
*/
|
|
||||||
public function destroy(DetailPayment $detailPayment)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Wallet;
|
||||||
|
use App\Models\DetailPayment;
|
||||||
|
|
||||||
|
class HomeController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
$month = $now->month;
|
||||||
|
$year = $now->year;
|
||||||
|
|
||||||
|
$monthlyIncome = DetailPayment::where('status', 'paid')
|
||||||
|
->where('payment_month', $month)
|
||||||
|
->where('payment_year', $year)
|
||||||
|
->whereHas('payments', fn($q) => $q->where('payment_status', 'success'))
|
||||||
|
->sum(DB::raw('IFNULL(amount,0) + IFNULL(penalty,0)'));
|
||||||
|
|
||||||
|
|
||||||
|
$studentCount = User::where('level', 2)
|
||||||
|
->where('status_santri', 'aktif')
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$totalBalance = Wallet::sum('saldo');
|
||||||
|
|
||||||
|
$paymentTrend = DetailPayment::select('payment_month', 'payment_year')
|
||||||
|
->selectRaw('SUM(IFNULL(amount,0) + IFNULL(penalty,0)) as total')
|
||||||
|
->where('status', 'paid')
|
||||||
|
->whereHas('payments', fn($q) => $q->where('payment_status', 'success'))
|
||||||
|
->whereBetween(DB::raw("STR_TO_DATE(CONCAT(payment_year,'-',payment_month,'-01'), '%Y-%m-%d')"), [
|
||||||
|
now()->subMonths(11)->startOfMonth()->toDateString(),
|
||||||
|
now()->endOfMonth()->toDateString(),
|
||||||
|
])
|
||||||
|
->groupBy('payment_year', 'payment_month')
|
||||||
|
->orderBy('payment_year')
|
||||||
|
->orderBy('payment_month')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$labels = [];
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
foreach ($paymentTrend as $pt) {
|
||||||
|
$labels[] = Carbon::createFromDate($pt->payment_year, $pt->payment_month, 1)->format('M Y');
|
||||||
|
$data[] = (int)$pt->total;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Inertia()->render('Dashboard', [
|
||||||
|
'monthlyIncome' => $monthlyIncome,
|
||||||
|
'studentCount' => $studentCount,
|
||||||
|
'totalBalance' => $totalBalance,
|
||||||
|
'paymentTrend' => [
|
||||||
|
'labels' => $labels,
|
||||||
|
'data' => $data,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,62 +4,161 @@
|
||||||
|
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\PaymentType;
|
||||||
|
use App\Models\DetailPayment;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use App\Services\cekDenda;
|
||||||
|
use App\Services\GenerateMonthlyBill;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
class PaymentController extends Controller
|
class PaymentController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
public function indexManualPayment(cekDenda $cekDenda, GenerateMonthlyBill $generateMonthlyBill)
|
||||||
* Display a listing of the resource.
|
|
||||||
*/
|
|
||||||
public function index()
|
|
||||||
{
|
{
|
||||||
//
|
|
||||||
|
$paymentTypes = PaymentType::get(['id', 'payment_type', 'nominal']);
|
||||||
|
|
||||||
|
$paymentPenalties = DetailPayment::with('paymentType')
|
||||||
|
->get()
|
||||||
|
->pluck('penalty', 'type_id');
|
||||||
|
|
||||||
|
$santri = User::with([
|
||||||
|
'payments.detailPayments.paymentType',
|
||||||
|
])->where('level', 2)->paginate(10);
|
||||||
|
|
||||||
|
return Inertia::render('list-admin/payment/ManualPayment', [
|
||||||
|
'santri' => $santri,
|
||||||
|
'penalty' => 0,
|
||||||
|
'fields' => [
|
||||||
|
'nis' => ['type' => 'text', 'readonly' => true],
|
||||||
|
'nama' => ['type' => 'text', 'readonly' => true],
|
||||||
|
'status_santri' => ['type' => 'text', 'readonly' => true],
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
'payment_type' => $paymentTypes->pluck('payment_type', 'id'),
|
||||||
|
'payment_nominal' => $paymentTypes->pluck('nominal', 'id'),
|
||||||
|
'payment_penalty' => $paymentPenalties
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function manualPayment(Request $request, $userId)
|
||||||
* Show the form for creating a new resource.
|
|
||||||
*/
|
|
||||||
public function create()
|
|
||||||
{
|
{
|
||||||
//
|
$validator = Validator::make($request->all(), [
|
||||||
|
'items' => 'required|array',
|
||||||
|
'items.*.type_id' => 'required|exists:payment_types,id',
|
||||||
|
'items.*.range' => 'required|integer|min:1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return redirect()->back()->withErrors($validator)->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$paymentTypes = PaymentType::pluck('nominal', 'id');
|
||||||
|
$user = User::findOrFail($userId);
|
||||||
|
|
||||||
|
$existingPayment = Payment::where('user_id', $userId)
|
||||||
|
->where('payment_status', 'pending')
|
||||||
|
->lockForUpdate()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingPayment) {
|
||||||
|
$totalAmountExisting = DetailPayment::where('payment_id', $existingPayment->id)->sum('amount');
|
||||||
|
$hasUnpaid = DetailPayment::where('payment_id', $existingPayment->id)
|
||||||
|
->where('status', 'unpaid')
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
$existingPayment->update([
|
||||||
|
'amount_payment' => $totalAmountExisting,
|
||||||
|
'payment_status' => $hasUnpaid ? 'pending' : 'success',
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Pembayaran yang pending sudah diupdate.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPayment = Payment::create([
|
||||||
|
'payment_status' => 'success',
|
||||||
|
'amount_payment' => 0,
|
||||||
|
'transaction_type' => 'payment',
|
||||||
|
'user_id' => $userId,
|
||||||
|
'order_id' => 'TRX' . uniqid(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$totalAmount = 0;
|
||||||
|
|
||||||
|
foreach ($request->items as $item) {
|
||||||
|
$typeId = $item['type_id'];
|
||||||
|
$range = (int) $item['range'];
|
||||||
|
$nominal = $paymentTypes[$typeId] ?? 0;
|
||||||
|
|
||||||
|
$lastDetail = DetailPayment::whereHas('payments', function ($q) use ($userId) {
|
||||||
|
$q->where('user_id', $userId);
|
||||||
|
})
|
||||||
|
->where('type_id', $typeId)
|
||||||
|
->orderBy('payment_year', 'desc')
|
||||||
|
->orderBy('payment_month', 'desc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($lastDetail) {
|
||||||
|
$bulan = $lastDetail->payment_month;
|
||||||
|
$tahun = $lastDetail->payment_year;
|
||||||
|
} else {
|
||||||
|
$bulan = now()->month;
|
||||||
|
$tahun = now()->year;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < $range; $i++) {
|
||||||
|
if ($i > 0 || $lastDetail) {
|
||||||
|
$bulan++;
|
||||||
|
if ($bulan > 12) {
|
||||||
|
$bulan = 1;
|
||||||
|
$tahun++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DetailPayment::create([
|
||||||
|
'payment_id' => $newPayment->id,
|
||||||
|
'payment_month' => $bulan,
|
||||||
|
'payment_year' => $tahun,
|
||||||
|
'amount' => $nominal,
|
||||||
|
'penalty' => 0,
|
||||||
|
'status' => 'paid',
|
||||||
|
'type_id' => $typeId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$totalAmount += $nominal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPayment->update(['amount_payment' => $totalAmount]);
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Pembayaran baru berhasil dibuat');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return redirect()->back()->with('error', 'Gagal membuat pembayaran: ' . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
*/
|
|
||||||
public function store(Request $request)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public function transaction()
|
||||||
* Display the specified resource.
|
|
||||||
*/
|
|
||||||
public function show(Payment $payment)
|
|
||||||
{
|
{
|
||||||
//
|
$transaction = User::with('payments', 'payments.detailPayments', 'wallet.walletTransactions', 'payments.detailPayments.paymentType')
|
||||||
}
|
->where('level', 10)
|
||||||
|
->paginate(2);
|
||||||
|
|
||||||
/**
|
// dd($transaction);
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
*/
|
|
||||||
public function edit(Payment $payment)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return Inertia::render('list-admin/payment/Transaction', compact('transaction'));
|
||||||
* Update the specified resource in storage.
|
|
||||||
*/
|
|
||||||
public function update(Request $request, Payment $payment)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
*/
|
|
||||||
public function destroy(Payment $payment)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use App\Models\PaymentType;
|
use App\Models\PaymentType;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class PaymentTypeController extends Controller
|
class PaymentTypeController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -12,54 +13,64 @@ class PaymentTypeController extends Controller
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
//
|
$paymentType = PaymentType::all();
|
||||||
|
return Inertia::render('list-admin/payment/PaymentType', [
|
||||||
|
'paymentType' => $paymentType,
|
||||||
|
'fields' => [
|
||||||
|
'payment_type' => 'text',
|
||||||
|
'nominal' => 'number',
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for creating a new resource.
|
|
||||||
*/
|
|
||||||
public function create()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
*/
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
//
|
$request->validate([
|
||||||
|
'payment_type' => 'required|string',
|
||||||
|
'nominal' => 'required|numeric'
|
||||||
|
], [
|
||||||
|
'payment_type.required' => 'wajib mengisi payment type',
|
||||||
|
'nominal.required' => 'wajib mengisi nominal pembayaran'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
PaymentType::create([
|
||||||
|
'payment_type' => $request->payment_type,
|
||||||
|
'nominal' => $request->nominal
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'berhasil insert data tipe pembayaran');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return redirect()->back()->with('error', 'Data gagal di tambahkan' . $th->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function update(Request $request, PaymentType $paymentType, $id)
|
||||||
* Display the specified resource.
|
|
||||||
*/
|
|
||||||
public function show(PaymentType $paymentType)
|
|
||||||
{
|
{
|
||||||
//
|
$request->validate([
|
||||||
|
'payment_type' => 'required|string'
|
||||||
|
], [
|
||||||
|
'payment_type.required' => 'wajib mengisi payment type'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$paymentType = PaymentType::findOrFail($id);
|
||||||
|
|
||||||
|
$paymentType->payment_type = $request->payment_type;
|
||||||
|
$paymentType->update();
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Berhasil Update Data');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return redirect()->back()->with('error', 'Gagal Mengupdate Data');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function destroy(PaymentType $paymentType, $id)
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
*/
|
|
||||||
public function edit(PaymentType $paymentType)
|
|
||||||
{
|
{
|
||||||
//
|
$paymentType = PaymentType::findOrFail($id);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$paymentType->delete();
|
||||||
* Update the specified resource in storage.
|
|
||||||
*/
|
|
||||||
public function update(Request $request, PaymentType $paymentType)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return redirect()->back()->with('success', 'Berhasil Menghapus Data');
|
||||||
* Remove the specified resource from storage.
|
|
||||||
*/
|
|
||||||
public function destroy(PaymentType $paymentType)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\ProfileUpdateRequest;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class ProfileController extends Controller
|
||||||
|
{
|
||||||
|
public function edit(Request $request): Response
|
||||||
|
{
|
||||||
|
// $user = User::all();
|
||||||
|
return Inertia::render('Profile/Profile');
|
||||||
|
}
|
||||||
|
public function updateProfile(Request $request, $id)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'password' => 'nullable',
|
||||||
|
'nama' => 'required',
|
||||||
|
'alamat' => 'required',
|
||||||
|
'jk' => 'required',
|
||||||
|
'tanggal_lahir' => 'required|date',
|
||||||
|
'foto' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048'
|
||||||
|
], [
|
||||||
|
'nama.required' => 'wajib mengisi nama santri',
|
||||||
|
'alamat.required' => 'wajib mengisi alamat santri',
|
||||||
|
'jk.required' => 'wajib mengisi gender',
|
||||||
|
'tanggal_lahir.required' => 'wajib mengisi tanggal lahir santri',
|
||||||
|
'tanggal_lahir.date' => 'tanggal lahir harus dalam format tanggal yang benar',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$santri = User::findOrFail($id);
|
||||||
|
try {
|
||||||
|
|
||||||
|
$updateData = [
|
||||||
|
'nama' => $request->nama,
|
||||||
|
'alamat' => $request->alamat,
|
||||||
|
'jk' => $request->jk,
|
||||||
|
'tanggal_lahir' => $request->tanggal_lahir,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->hasFile('foto')) {
|
||||||
|
if ($santri->foto && File::exists(public_path($santri->foto))) {
|
||||||
|
File::delete(public_path($santri->foto));
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto = $request->file('foto');
|
||||||
|
$fotoName = time() . '_' . $foto->getClientOriginalName();
|
||||||
|
$foto->move(public_path('fotoSantri'), $fotoName);
|
||||||
|
$updateData['foto'] = 'fotoSantri/' . $fotoName;
|
||||||
|
}
|
||||||
|
if ($request->filled('password')) {
|
||||||
|
$updateData['password'] = Hash::make($request->password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// dd($updateData);
|
||||||
|
// return $updateData;
|
||||||
|
|
||||||
|
$santri->update($updateData);
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Data Berhasil Diubah');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return redirect()->back()->with('error', 'Gagal memperbarui data: ' . $th->getMessage());
|
||||||
|
// return $th->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,61 +2,169 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Santri;
|
use App\Models\User;
|
||||||
|
use App\Models\Wallet;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
class SantriController extends Controller
|
class SantriController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
public function index()
|
||||||
* Display a listing of the resource.
|
|
||||||
*/
|
|
||||||
public function index() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for creating a new resource.
|
|
||||||
*/
|
|
||||||
public function create()
|
|
||||||
{
|
{
|
||||||
//
|
$santri = User::where('level', 2)->paginate(10);
|
||||||
|
return Inertia::render('list-admin/santri/IndexSantri', [
|
||||||
|
'santri' => $santri,
|
||||||
|
'fields' => [
|
||||||
|
'nama' => 'text',
|
||||||
|
'nis' => 'text',
|
||||||
|
'password' => 'password',
|
||||||
|
'alamat' => 'text',
|
||||||
|
'no_telp' => 'text',
|
||||||
|
'status_santri' => 'select',
|
||||||
|
'jk' => 'select',
|
||||||
|
'level' => 'select',
|
||||||
|
'tanggal_lahir' => 'date',
|
||||||
|
'foto' => 'file',
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
'status_santri' => ['lulus' => 'Lulus', 'aktif' => 'Aktif'],
|
||||||
|
'role_santri' => ['santri' => 'Santri', 'pengurus' => 'Pengurus'],
|
||||||
|
'jk' => ['laki laki' => 'Laki-Laki', 'perempuan' => 'Perempuan'],
|
||||||
|
'level' => [1 => 'Admin', 2 => 'User']
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
*/
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
//
|
$request->validate([
|
||||||
|
'nis' => 'required',
|
||||||
|
'password' => 'required',
|
||||||
|
'level' => 'required',
|
||||||
|
'nama' => 'required',
|
||||||
|
'alamat' => 'required',
|
||||||
|
'status_santri' => 'required',
|
||||||
|
'jk' => 'required',
|
||||||
|
'tanggal_lahir' => 'required|date',
|
||||||
|
'no_telp' => 'required',
|
||||||
|
|
||||||
|
'foto' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048'
|
||||||
|
], [
|
||||||
|
'nis.required' => 'wajib mengisi nis santri',
|
||||||
|
'password.required' => 'wajib mengisi password santri',
|
||||||
|
'level.required' => 'wajib mengisi level santri',
|
||||||
|
'nama.required' => 'wajib mengisi nama santri',
|
||||||
|
'alamat.required' => 'wajib mengisi alamat santri',
|
||||||
|
'status_santri.required' => 'wajib mengisi status santri',
|
||||||
|
'jk.required' => 'wajib mengisi gender',
|
||||||
|
'no_telp' => 'wajib mengisi no telp',
|
||||||
|
'tanggal_lahir.required' => 'wajib mengisi tanggal lahir santri',
|
||||||
|
'tanggal_lahir.date' => 'tanggal lahir harus dalam format tanggal yang benar',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$fotoPath = null;
|
||||||
|
|
||||||
|
if ($request->hasFile('foto')) {
|
||||||
|
$foto = $request->file('foto');
|
||||||
|
$fotoName = time() . '_' . $foto->getClientOriginalName();
|
||||||
|
$foto->move(public_path('fotoSantri'), $fotoName);
|
||||||
|
|
||||||
|
$fotoPath = 'fotoSantri/' . $fotoName;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$santri = User::create([
|
||||||
|
'nis' => $request->nis,
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
'level' => $request->level,
|
||||||
|
'nama' => $request->nama,
|
||||||
|
'alamat' => $request->alamat,
|
||||||
|
'status_santri' => $request->status_santri,
|
||||||
|
'jk' => $request->jk,
|
||||||
|
'tanggal_lahir' => $request->tanggal_lahir,
|
||||||
|
'no_telp' => $request->no_telp,
|
||||||
|
'foto' => $fotoPath
|
||||||
|
]);
|
||||||
|
|
||||||
|
$santri->wallet()->create(['saldo' => 0]);
|
||||||
|
|
||||||
|
// dd($santri);
|
||||||
|
return redirect()->back()->with('success', 'Data berhasil ditambahkan');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
// dd($th->getMessage());
|
||||||
|
return redirect()->back()->with('error', 'Data gagal ditambahkan : ' . $th->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function update(Request $request, $id)
|
||||||
* Display the specified resource.
|
|
||||||
*/
|
|
||||||
public function show(Santri $santri)
|
|
||||||
{
|
{
|
||||||
//
|
$request->validate([
|
||||||
|
'nis' => 'required',
|
||||||
|
'password' => 'nullable',
|
||||||
|
'level' => 'required',
|
||||||
|
'nama' => 'required',
|
||||||
|
'alamat' => 'required',
|
||||||
|
'status_santri' => 'required',
|
||||||
|
'jk' => 'required',
|
||||||
|
'tanggal_lahir' => 'required|date',
|
||||||
|
'foto' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048'
|
||||||
|
], [
|
||||||
|
'nis.required' => 'wajib mengisi nis santri',
|
||||||
|
'level.required' => 'wajib mengisi level santri',
|
||||||
|
'nama.required' => 'wajib mengisi nama santri',
|
||||||
|
'alamat.required' => 'wajib mengisi alamat santri',
|
||||||
|
'status_santri.required' => 'wajib mengisi status santri',
|
||||||
|
'jk.required' => 'wajib mengisi gender',
|
||||||
|
'tanggal_lahir.required' => 'wajib mengisi tanggal lahir santri',
|
||||||
|
'tanggal_lahir.date' => 'tanggal lahir harus dalam format tanggal yang benar',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$santri = User::findOrFail($id);
|
||||||
|
$updateData = [
|
||||||
|
'nis' => $request->nis,
|
||||||
|
'level' => $request->level,
|
||||||
|
'nama' => $request->nama,
|
||||||
|
'alamat' => $request->alamat,
|
||||||
|
'status_santri' => $request->status_santri,
|
||||||
|
'jk' => $request->jk,
|
||||||
|
'tanggal_lahir' => $request->tanggal_lahir,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->hasFile('foto')) {
|
||||||
|
if ($santri->foto && File::exists(public_path($santri->foto))) {
|
||||||
|
File::delete(public_path($santri->foto));
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto = $request->file('foto');
|
||||||
|
$fotoName = time() . '_' . $foto->getClientOriginalName();
|
||||||
|
$foto->move(public_path('fotoSantri'), $fotoName);
|
||||||
|
$updateData['foto'] = 'fotoSantri/' . $fotoName;
|
||||||
|
}
|
||||||
|
if ($request->filled('password')) {
|
||||||
|
$updateData['password'] = Hash::make($request->password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return $updateData;
|
||||||
|
|
||||||
|
$updateStatus = $santri->update($updateData);
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Data Berhasil Diubah');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return redirect()->back()->with('error', 'Gagal memperbarui data: ' . $th->getMessage());
|
||||||
|
// return $th->getMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function destroy($id)
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
*/
|
|
||||||
public function edit(Santri $santri)
|
|
||||||
{
|
{
|
||||||
//
|
$santri = User::findOrFail($id);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$santri->delete();
|
||||||
* Update the specified resource in storage.
|
File::delete('fotoSantri/' . basename($santri->foto));
|
||||||
*/
|
return redirect()->back()->with('success', 'data berhasil di hapus');
|
||||||
public function update(Request $request, Santri $santri)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
*/
|
|
||||||
public function destroy(Santri $santri)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,64 +2,18 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Wallet;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class WalletController extends Controller
|
class WalletController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
public function walletUser()
|
||||||
* Display a listing of the resource.
|
|
||||||
*/
|
|
||||||
public function index()
|
|
||||||
{
|
{
|
||||||
//
|
$wallet = User::with('wallet')
|
||||||
}
|
->where('level', 2)
|
||||||
|
->paginate(10);
|
||||||
|
|
||||||
/**
|
return Inertia::render('list-admin/payment/WalletUser', compact('wallet'));
|
||||||
* Show the form for creating a new resource.
|
|
||||||
*/
|
|
||||||
public function create()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
*/
|
|
||||||
public function store(Request $request)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the specified resource.
|
|
||||||
*/
|
|
||||||
public function show(Wallet $wallet)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
*/
|
|
||||||
public function edit(Wallet $wallet)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the specified resource in storage.
|
|
||||||
*/
|
|
||||||
public function update(Request $request, Wallet $wallet)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
*/
|
|
||||||
public function destroy(Wallet $wallet)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Models\WalletTransaction;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class WalletTransactionController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Display a listing of the resource.
|
|
||||||
*/
|
|
||||||
public function index()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for creating a new resource.
|
|
||||||
*/
|
|
||||||
public function create()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
*/
|
|
||||||
public function store(Request $request)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the specified resource.
|
|
||||||
*/
|
|
||||||
public function show(WalletTransaction $walletTransaction)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
*/
|
|
||||||
public function edit(WalletTransaction $walletTransaction)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the specified resource in storage.
|
|
||||||
*/
|
|
||||||
public function update(Request $request, WalletTransaction $walletTransaction)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
*/
|
|
||||||
public function destroy(WalletTransaction $walletTransaction)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -36,6 +36,8 @@ class Kernel extends HttpKernel
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
\App\Http\Middleware\HandleInertiaRequests::class,
|
||||||
|
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'api' => [
|
'api' => [
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Middleware;
|
||||||
|
|
||||||
|
class HandleInertiaRequests extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The root template that is loaded on the first page visit.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rootView = 'app';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the current asset version.
|
||||||
|
*/
|
||||||
|
public function version(Request $request): string|null
|
||||||
|
{
|
||||||
|
return parent::version($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the props that are shared by default.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function share(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
...parent::share($request),
|
||||||
|
'auth' => [
|
||||||
|
'user' => $request->user(),
|
||||||
|
],
|
||||||
|
'flash' => [
|
||||||
|
'success' => fn() => $request->session()->get('success'),
|
||||||
|
'error' => fn() => $request->session()->get('error'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Auth\Events\Lockout;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class LoginRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'nis' => ['required', 'string'],
|
||||||
|
'password' => ['required', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to authenticate the request's credentials.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function authenticate(): void
|
||||||
|
{
|
||||||
|
$this->ensureIsNotRateLimited();
|
||||||
|
|
||||||
|
if (! Auth::attempt($this->only('nis', 'password'), $this->boolean('remember'))) {
|
||||||
|
RateLimiter::hit($this->throttleKey());
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'nis' => trans('auth.failed'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimiter::clear($this->throttleKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the login request is not rate limited.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function ensureIsNotRateLimited(): void
|
||||||
|
{
|
||||||
|
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event(new Lockout($this));
|
||||||
|
|
||||||
|
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'nis' => trans('auth.throttle', [
|
||||||
|
'seconds' => $seconds,
|
||||||
|
'minutes' => ceil($seconds / 60),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the rate limiting throttle key for the request.
|
||||||
|
*/
|
||||||
|
public function throttleKey(): string
|
||||||
|
{
|
||||||
|
return Str::transliterate(Str::lower($this->string('nis')) . '|' . $this->ip());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ProfileUpdateRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,8 +10,17 @@ class DetailPayment extends Model
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $guarded = ['id'];
|
protected $guarded = ['id'];
|
||||||
|
protected $table = 'detail_payments';
|
||||||
|
|
||||||
public function payment()
|
public static function cekTunggakan($santri_id)
|
||||||
|
{
|
||||||
|
|
||||||
|
return self::where('status', 'unpaid')->whereHas('payment', function ($query) use ($santri_id) {
|
||||||
|
$query->where('santri_id', $santri_id);
|
||||||
|
})->orderBy('payment_year', 'asc')->orderBy('payment_month')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function payments()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Payment::class, 'payment_id', 'id');
|
return $this->belongsTo(Payment::class, 'payment_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ class Payment extends Model
|
||||||
|
|
||||||
protected $guarded = ['id'];
|
protected $guarded = ['id'];
|
||||||
|
|
||||||
public function santri()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Santri::class, 'santri_id', 'id');
|
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function wallet()
|
public function wallet()
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
class Santri extends Model
|
|
||||||
{
|
|
||||||
use HasFactory;
|
|
||||||
|
|
||||||
protected $guarded = ['id'];
|
|
||||||
|
|
||||||
public function user()
|
|
||||||
{
|
|
||||||
return $this->hasOne(User::class, 'santri_id', 'id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function payments()
|
|
||||||
{
|
|
||||||
return $this->hasMany(Payment::class, 'santri_id', 'id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function wallet()
|
|
||||||
{
|
|
||||||
return $this->hasOne(Wallet::class, 'santri_id', 'id');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,10 +17,8 @@ class User extends Authenticatable
|
||||||
*
|
*
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $guarded = [
|
||||||
'name',
|
'id'
|
||||||
'email',
|
|
||||||
'password',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,12 +36,14 @@ class User extends Authenticatable
|
||||||
*
|
*
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
|
||||||
'email_verified_at' => 'datetime',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function santri()
|
public function payments()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Santri::class, 'santri_id', 'id');
|
return $this->hasMany(Payment::class, 'user_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function wallet()
|
||||||
|
{
|
||||||
|
return $this->hasOne(Wallet::class, 'user_id', 'id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ public function payments()
|
||||||
return $this->hasMany(Payment::class, 'wallet_id', 'id');
|
return $this->hasMany(Payment::class, 'wallet_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function santri()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Santri::class, 'santri_id', 'id');
|
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function walletTransactions()
|
public function walletTransactions()
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
@ -19,6 +20,11 @@ public function register(): void
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
Inertia::share([
|
||||||
|
'flash' => [
|
||||||
|
'success' => session('success'),
|
||||||
|
'error' => session('error'),
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public const HOME = '/home';
|
public const HOME = '/dashboard';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define your route model bindings, pattern filters, and other route configuration.
|
* Define your route model bindings, pattern filters, and other route configuration.
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\DetailPayment;
|
||||||
|
use App\Models\Payment;
|
||||||
|
use App\Models\PaymentType;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class GenerateMonthlyBill
|
||||||
|
{
|
||||||
|
public function generateAutoBill()
|
||||||
|
{
|
||||||
|
$currentMonth = Carbon::now()->month;
|
||||||
|
$currentYear = Carbon::now()->year;
|
||||||
|
$previousMonth = Carbon::now()->subMonth()->month;
|
||||||
|
$previousYear = Carbon::now()->subMonth()->year;
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
try {
|
||||||
|
$santriPayments = Payment::with('detailPayments')->get();
|
||||||
|
|
||||||
|
foreach ($santriPayments as $prevPay) {
|
||||||
|
$existingPayment = Payment::where('santri_id', $prevPay->santri_id)
|
||||||
|
->whereMonth('created_at', $currentMonth)
|
||||||
|
->whereYear('created_at', $currentYear)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$existingPayment) {
|
||||||
|
$newPayment = Payment::create([
|
||||||
|
'payment_status' => 'pending',
|
||||||
|
'amount_payment' => 0,
|
||||||
|
'santri_id' => $prevPay->santri_id,
|
||||||
|
'wallet_id' => $prevPay->wallet_id,
|
||||||
|
'bank' => null,
|
||||||
|
'no_va' => null,
|
||||||
|
'expired_at' => null,
|
||||||
|
'payment_method' => null,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$newPayment = $existingPayment;
|
||||||
|
}
|
||||||
|
|
||||||
|
$previousPayment = Payment::where('santri_id', $prevPay->santri_id)
|
||||||
|
->whereMonth('created_at', $previousMonth)
|
||||||
|
->whereYear('created_at', $previousYear)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$previousPayment) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$previousDetails = $previousPayment->detailPayments;
|
||||||
|
|
||||||
|
foreach ($previousDetails as $previousDetail) {
|
||||||
|
$existingBill = DetailPayment::where('payment_id', $newPayment->id)
|
||||||
|
->where('type_id', $previousDetail->type_id)
|
||||||
|
->where('payment_month', $currentMonth)
|
||||||
|
->where('payment_year', $currentYear)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if (!$existingBill) {
|
||||||
|
$paymentType = PaymentType::find($previousDetail->type_id);
|
||||||
|
$amount = $paymentType ? $paymentType->amount : 0;
|
||||||
|
|
||||||
|
DetailPayment::create([
|
||||||
|
'payment_id' => $newPayment->id,
|
||||||
|
'type_id' => $previousDetail->type_id,
|
||||||
|
'amount' => $amount,
|
||||||
|
'status' => 'unpaid',
|
||||||
|
'payment_month' => $currentMonth,
|
||||||
|
'payment_year' => $currentYear,
|
||||||
|
'penalty' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
return ['message' => 'Berhasil menambah data'];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return ['error' => 'Gagal menambah data: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\DetailPayment;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class cekDenda
|
||||||
|
{
|
||||||
|
public function applyPenalty()
|
||||||
|
{
|
||||||
|
$previousMonth = Carbon::now()->subMonth()->month;
|
||||||
|
$previousYear = Carbon::now()->subMonth()->year;
|
||||||
|
$currentDay = Carbon::now()->day;
|
||||||
|
$penaltyAmount = 20000;
|
||||||
|
|
||||||
|
if ($currentDay >= 10) {
|
||||||
|
DB::beginTransaction();
|
||||||
|
try {
|
||||||
|
$unpaidPayments = DetailPayment::where('status', 'unpaid')
|
||||||
|
->where('payment_month', $previousMonth)
|
||||||
|
->where('payment_year', $previousYear)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($unpaidPayments as $payment) {
|
||||||
|
if (is_null($payment->penalty)) {
|
||||||
|
$payment->update([
|
||||||
|
'penalty' => $penaltyAmount
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
return ['message' => 'Penalty applied successfully'];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return ['error' => $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['message' => 'Not yet time for penalty application'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,12 +7,15 @@
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1",
|
"php": "^8.1",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^7.2",
|
||||||
|
"inertiajs/inertia-laravel": "^0.6.3",
|
||||||
"laravel/framework": "^10.0",
|
"laravel/framework": "^10.0",
|
||||||
"laravel/sanctum": "^3.2",
|
"laravel/sanctum": "^3.2",
|
||||||
"laravel/tinker": "^2.8"
|
"laravel/tinker": "^2.8",
|
||||||
|
"tightenco/ziggy": "^2.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.9.1",
|
"fakerphp/faker": "^1.9.1",
|
||||||
|
"laravel/breeze": "^1.29",
|
||||||
"laravel/pint": "^1.0",
|
"laravel/pint": "^1.0",
|
||||||
"laravel/sail": "^1.18",
|
"laravel/sail": "^1.18",
|
||||||
"mockery/mockery": "^1.4.4",
|
"mockery/mockery": "^1.4.4",
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "bfe12996eeecb6fdc8713a9fd9d431f8",
|
"content-hash": "44d8185187ad62ec42dc2fe932715ec5",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
|
@ -1054,6 +1054,78 @@
|
||||||
],
|
],
|
||||||
"time": "2023-12-03T19:50:20+00:00"
|
"time": "2023-12-03T19:50:20+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "inertiajs/inertia-laravel",
|
||||||
|
"version": "v0.6.11",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/inertiajs/inertia-laravel.git",
|
||||||
|
"reference": "2a1e19048f95c0e4adb2b2733f9119e49c4fc09f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/2a1e19048f95c0e4adb2b2733f9119e49c4fc09f",
|
||||||
|
"reference": "2a1e19048f95c0e4adb2b2733f9119e49c4fc09f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"laravel/framework": "^6.0|^7.0|^8.74|^9.0|^10.0",
|
||||||
|
"php": "^7.2|~8.0.0|~8.1.0|~8.2.0|~8.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.3.3",
|
||||||
|
"orchestra/testbench": "^4.0|^5.0|^6.4|^7.0|^8.0",
|
||||||
|
"phpunit/phpunit": "^8.0|^9.5.8",
|
||||||
|
"roave/security-advisories": "dev-master"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-pcntl": "Recommended when running the Inertia SSR server via the `inertia:start-ssr` artisan command."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Inertia\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"./helpers.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Inertia\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jonathan Reinink",
|
||||||
|
"email": "jonathan@reinink.ca",
|
||||||
|
"homepage": "https://reinink.ca"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The Laravel adapter for Inertia.js.",
|
||||||
|
"keywords": [
|
||||||
|
"inertia",
|
||||||
|
"laravel"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/inertiajs/inertia-laravel/issues",
|
||||||
|
"source": "https://github.com/inertiajs/inertia-laravel/tree/v0.6.11"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/reinink",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-10-27T10:59:02+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/framework",
|
"name": "laravel/framework",
|
||||||
"version": "v10.48.25",
|
"version": "v10.48.25",
|
||||||
|
@ -5340,6 +5412,76 @@
|
||||||
],
|
],
|
||||||
"time": "2024-11-08T15:28:48+00:00"
|
"time": "2024-11-08T15:28:48+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "tightenco/ziggy",
|
||||||
|
"version": "v2.5.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/tighten/ziggy.git",
|
||||||
|
"reference": "2b574ba281546884b7bdde6eefa451ba7e0b52f7"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/tighten/ziggy/zipball/2b574ba281546884b7bdde6eefa451ba7e0b52f7",
|
||||||
|
"reference": "2b574ba281546884b7bdde6eefa451ba7e0b52f7",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"laravel/framework": ">=9.0",
|
||||||
|
"php": ">=8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laravel/folio": "^1.1",
|
||||||
|
"orchestra/testbench": "^7.0 || ^8.0 || ^9.0",
|
||||||
|
"pestphp/pest": "^2.26",
|
||||||
|
"pestphp/pest-plugin-laravel": "^2.4"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Tighten\\Ziggy\\ZiggyServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tighten\\Ziggy\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Daniel Coulbourne",
|
||||||
|
"email": "daniel@tighten.co"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jake Bathman",
|
||||||
|
"email": "jake@tighten.co"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jacob Baker-Kretzmar",
|
||||||
|
"email": "jacob@tighten.co"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Use your Laravel named routes in JavaScript.",
|
||||||
|
"homepage": "https://github.com/tighten/ziggy",
|
||||||
|
"keywords": [
|
||||||
|
"Ziggy",
|
||||||
|
"javascript",
|
||||||
|
"laravel",
|
||||||
|
"routes"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/tighten/ziggy/issues",
|
||||||
|
"source": "https://github.com/tighten/ziggy/tree/v2.5.0"
|
||||||
|
},
|
||||||
|
"time": "2025-01-23T00:32:18+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "tijsverkoyen/css-to-inline-styles",
|
"name": "tijsverkoyen/css-to-inline-styles",
|
||||||
"version": "v2.3.0",
|
"version": "v2.3.0",
|
||||||
|
@ -5798,6 +5940,68 @@
|
||||||
},
|
},
|
||||||
"time": "2020-07-09T08:09:16+00:00"
|
"time": "2020-07-09T08:09:16+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/breeze",
|
||||||
|
"version": "v1.29.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/breeze.git",
|
||||||
|
"reference": "22c53b84b7fff91b01a318d71a10dfc251e92849"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/breeze/zipball/22c53b84b7fff91b01a318d71a10dfc251e92849",
|
||||||
|
"reference": "22c53b84b7fff91b01a318d71a10dfc251e92849",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/console": "^10.17",
|
||||||
|
"illuminate/filesystem": "^10.17",
|
||||||
|
"illuminate/support": "^10.17",
|
||||||
|
"illuminate/validation": "^10.17",
|
||||||
|
"php": "^8.1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"orchestra/testbench": "^8.0",
|
||||||
|
"phpstan/phpstan": "^1.10"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Breeze\\BreezeServiceProvider"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Breeze\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Minimal Laravel authentication scaffolding with Blade and Tailwind.",
|
||||||
|
"keywords": [
|
||||||
|
"auth",
|
||||||
|
"laravel"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/breeze/issues",
|
||||||
|
"source": "https://github.com/laravel/breeze"
|
||||||
|
},
|
||||||
|
"time": "2024-03-04T14:35:21+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/pint",
|
"name": "laravel/pint",
|
||||||
"version": "v1.20.0",
|
"version": "v1.20.0",
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('santris', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
// $table->string('nis');
|
|
||||||
$table->string('nama');
|
|
||||||
$table->string('alamat');
|
|
||||||
$table->enum('status_santri', ['boyong', 'aktif']);
|
|
||||||
$table->enum('role_santri', ['santri', 'pengurus']);
|
|
||||||
$table->enum('jk', ['laki laki', 'perempuan']);
|
|
||||||
$table->date('tanggal_lahir');
|
|
||||||
$table->string('foto');
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('santris');
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -15,8 +15,14 @@ public function up(): void
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nis');
|
$table->string('nis');
|
||||||
$table->string('password');
|
$table->string('password');
|
||||||
$table->string('role');
|
$table->string('level');
|
||||||
$table->foreignId('santri_id')->constrained('santris')->onDelete('cascade');
|
$table->string('nama');
|
||||||
|
$table->string('alamat');
|
||||||
|
$table->bigInteger('no_telp');
|
||||||
|
$table->enum('status_santri', ['lulus', 'aktif']);
|
||||||
|
$table->enum('jk', ['laki laki', 'perempuan']);
|
||||||
|
$table->date('tanggal_lahir');
|
||||||
|
$table->string('foto')->nullable();
|
||||||
$table->rememberToken();
|
$table->rememberToken();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,7 @@ public function up(): void
|
||||||
Schema::create('payment_types', function (Blueprint $table) {
|
Schema::create('payment_types', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('payment_type');
|
$table->string('payment_type');
|
||||||
|
$table->decimal('nominal');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('wallets', function (Blueprint $table) {
|
Schema::create('wallets', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('santri_id')->constrained('santris')->onDelete('cascade');
|
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||||
$table->float('saldo');
|
$table->decimal('saldo');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,10 @@ public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('wallet_transactions', function (Blueprint $table) {
|
Schema::create('wallet_transactions', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
$table->string('order_id')->unique();
|
||||||
$table->foreignId('wallet_id')->constrained('wallets')->onDelete('cascade');
|
$table->foreignId('wallet_id')->constrained('wallets')->onDelete('cascade');
|
||||||
$table->enum('transaction_type', ['topup', 'payment']);
|
$table->enum('transaction_type', ['topup', 'payment']);
|
||||||
$table->float('amount');
|
$table->decimal('amount');
|
||||||
$table->string('description');
|
$table->string('description');
|
||||||
$table->enum('status', ['pending', 'success', 'failed']);
|
$table->enum('status', ['pending', 'success', 'failed']);
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
|
@ -13,14 +13,16 @@ public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('payments', function (Blueprint $table) {
|
Schema::create('payments', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
$table->string('order_id')->unique()->nullable();
|
||||||
$table->enum('payment_status', ['pending', 'failed', 'success']);
|
$table->enum('payment_status', ['pending', 'failed', 'success']);
|
||||||
$table->float('amount_payment');
|
$table->enum('transaction_type', ['topup', 'payment']);
|
||||||
$table->enum('payment_method', ['online', 'offline']);
|
$table->decimal('amount_payment')->nullable();
|
||||||
$table->String('bank');
|
$table->String('bank')->nullable();
|
||||||
$table->string('no_va');
|
$table->string('no_va')->nullable();
|
||||||
$table->dateTime('expired_at');
|
$table->dateTime('expired_at')->nullable();
|
||||||
$table->foreignId('santri_id')->constrained('santris')->onDelete('cascade');
|
$table->string('snap_token')->nullable();
|
||||||
$table->foreignId('wallet_id')->constrained('wallets')->onDelete('cascade');
|
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||||
|
$table->foreignId('wallet_id')->nullable()->constrained('wallets')->onDelete('cascade');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,11 @@ public function up(): void
|
||||||
Schema::create('detail_payments', function (Blueprint $table) {
|
Schema::create('detail_payments', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('payment_id')->constrained('payments')->onDelete('cascade');
|
$table->foreignId('payment_id')->constrained('payments')->onDelete('cascade');
|
||||||
$table->enum('mounth', ['januari', 'februari', 'maret', 'april', 'mei', 'juni', 'juli', 'agustus', 'september', 'oktober', 'november', 'desember']);
|
$table->enum('status', ['paid', 'unpaid']);
|
||||||
$table->enum('status', ['bayar', 'belum bayar']);
|
$table->decimal('amount')->nullable();
|
||||||
$table->float('amount');
|
$table->decimal('penalty')->nullable();
|
||||||
$table->float('penalty')->nullable();
|
$table->integer('payment_month');
|
||||||
|
$table->integer('payment_year');
|
||||||
$table->foreignId('type_id')->constrained('payment_types')->onDelete('cascade');
|
$table->foreignId('type_id')->constrained('payment_types')->onDelete('cascade');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
|
@ -12,11 +14,31 @@ class DatabaseSeeder extends Seeder
|
||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// \App\Models\User::factory(10)->create();
|
User::insert([
|
||||||
|
[
|
||||||
// \App\Models\User::factory()->create([
|
'nis' => '1234567890',
|
||||||
// 'name' => 'Test User',
|
'password' => Hash::make('password123'),
|
||||||
// 'email' => 'test@example.com',
|
'level' => 1,
|
||||||
// ]);
|
'nama' => 'Admin',
|
||||||
|
'alamat' => 'Jl. MH. Thamrin No. 10, Ajong',
|
||||||
|
'status_santri' => 'aktif',
|
||||||
|
'jk' => 'laki laki',
|
||||||
|
'tanggal_lahir' => '2005-08-15',
|
||||||
|
'foto' => null,
|
||||||
|
'no_telp' => '80989080980'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'nis' => '0987654321',
|
||||||
|
'password' => Hash::make('pitik123'),
|
||||||
|
'level' => 2,
|
||||||
|
'nama' => 'Ahmad Kasim',
|
||||||
|
'alamat' => 'Jl. Pesantren No. 10, Jakarta',
|
||||||
|
'status_santri' => 'aktif',
|
||||||
|
'jk' => 'laki laki',
|
||||||
|
'tanggal_lahir' => '2003-08-15',
|
||||||
|
'foto' => null,
|
||||||
|
'no_telp' => '80989080980'
|
||||||
|
]
|
||||||
|
],);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["resources/js/*"],
|
||||||
|
"ziggy-js": ["./vendor/tightenco/ziggy"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "public"]
|
||||||
|
}
|
20
package.json
|
@ -5,8 +5,28 @@
|
||||||
"build": "vite build"
|
"build": "vite build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@headlessui/react": "^1.4.2",
|
||||||
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
|
"autoprefixer": "^10.4.12",
|
||||||
"axios": "^1.1.2",
|
"axios": "^1.1.2",
|
||||||
|
"daisyui": "^4.12.23",
|
||||||
"laravel-vite-plugin": "^0.7.2",
|
"laravel-vite-plugin": "^0.7.2",
|
||||||
|
"postcss": "^8.4.31",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"tailwindcss": "^3.2.1",
|
||||||
"vite": "^4.0.0"
|
"vite": "^4.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@heroicons/react": "^2.2.0",
|
||||||
|
"@inertiajs/inertia": "^0.11.1",
|
||||||
|
"@inertiajs/react": "^2.0.4",
|
||||||
|
"@reduxjs/toolkit": "^2.6.0",
|
||||||
|
"react-chartjs-2": "^5.3.0",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
|
"react-tailwindcss-datepicker": "^2.0.0",
|
||||||
|
"sweetalert2": "^11.21.0",
|
||||||
|
"theme-change": "^2.5.0",
|
||||||
|
"web-vitals": "^4.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
After Width: | Height: | Size: 1.6 MiB |
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"short_name": "DashWind",
|
||||||
|
"name": "DashWind",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
After Width: | Height: | Size: 364 KiB |
After Width: | Height: | Size: 241 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 758 KiB |
After Width: | Height: | Size: 226 KiB |
After Width: | Height: | Size: 758 KiB |
After Width: | Height: | Size: 758 KiB |
After Width: | Height: | Size: 758 KiB |
After Width: | Height: | Size: 758 KiB |
After Width: | Height: | Size: 758 KiB |
After Width: | Height: | Size: 226 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 758 KiB |
After Width: | Height: | Size: 181 KiB |
After Width: | Height: | Size: 509 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 721 KiB |
After Width: | Height: | Size: 906 KiB |
After Width: | Height: | Size: 906 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 226 KiB |
After Width: | Height: | Size: 226 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 4.4 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="light">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>React Test</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root">Loading...</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,44 @@
|
||||||
|
const moment = require("moment");
|
||||||
|
|
||||||
|
module.exports = Object.freeze({
|
||||||
|
CALENDAR_INITIAL_EVENTS : [
|
||||||
|
{title : "Product call", theme : "GREEN", startTime : moment().add(-12, 'd').startOf('day'), endTime : moment().add(-12, 'd').endOf('day')},
|
||||||
|
{title : "Meeting with tech team", theme : "PINK", startTime : moment().add(-8, 'd').startOf('day'), endTime : moment().add(-8, 'd').endOf('day')},
|
||||||
|
{title : "Meeting with Cristina", theme : "PURPLE", startTime : moment().add(-2, 'd').startOf('day'), endTime : moment().add(-2, 'd').endOf('day')},
|
||||||
|
{title : "Meeting with Alex", theme : "BLUE", startTime : moment().startOf('day'), endTime : moment().endOf('day')},
|
||||||
|
{title : "Product Call", theme : "GREEN", startTime : moment().startOf('day'), endTime : moment().endOf('day')},
|
||||||
|
{title : "Client Meeting", theme : "PURPLE", startTime : moment().startOf('day'), endTime : moment().endOf('day')},
|
||||||
|
{title : "Client Meeting", theme : "ORANGE", startTime : moment().add(3, 'd').startOf('day'), endTime : moment().add(3, 'd').endOf('day')},
|
||||||
|
{title : "Product meeting", theme : "PINK", startTime : moment().add(5, 'd').startOf('day'), endTime : moment().add(5, 'd').endOf('day')},
|
||||||
|
{title : "Sales Meeting", theme : "GREEN", startTime : moment().add(8, 'd').startOf('day'), endTime : moment().add(8, 'd').endOf('day')},
|
||||||
|
{title : "Product Meeting", theme : "ORANGE", startTime : moment().add(8, 'd').startOf('day'), endTime : moment().add(8, 'd').endOf('day')},
|
||||||
|
{title : "Marketing Meeting", theme : "PINK", startTime : moment().add(8, 'd').startOf('day'), endTime : moment().add(8, 'd').endOf('day')},
|
||||||
|
{title : "Client Meeting", theme : "GREEN", startTime : moment().add(8, 'd').startOf('day'), endTime : moment().add(8, 'd').endOf('day')},
|
||||||
|
{title : "Sales meeting", theme : "BLUE", startTime : moment().add(12, 'd').startOf('day'), endTime : moment().add(12, 'd').endOf('day')},
|
||||||
|
{title : "Client meeting", theme : "PURPLE", startTime : moment().add(16, 'd').startOf('day'), endTime : moment().add(16, 'd').endOf('day')},
|
||||||
|
],
|
||||||
|
|
||||||
|
RECENT_TRANSACTIONS : [
|
||||||
|
{name : "Alex", avatar : "https://reqres.in/img/faces/1-image.jpg", email : "alex@dashwind.com", location : "Paris", amount : 100, date : moment().endOf('day')},
|
||||||
|
{name : "Ereena", avatar : "https://reqres.in/img/faces/2-image.jpg", email : "ereena@dashwind.com", location : "London", amount : 190, date : moment().add(-1, 'd').endOf('day')},
|
||||||
|
{name : "John", avatar : "https://reqres.in/img/faces/3-image.jpg", email : "jhon@dashwind.com", location : "Canada", amount : 112, date : moment().add(-1, 'd').endOf('day')},
|
||||||
|
{name : "Matrix", avatar : "https://reqres.in/img/faces/4-image.jpg", email : "matrix@dashwind.com", location : "Peru", amount : 111, date : moment().add(-1, 'd').endOf('day')},
|
||||||
|
{name : "Virat", avatar : "https://reqres.in/img/faces/5-image.jpg", email : "virat@dashwind.com", location : "London", amount : 190, date : moment().add(-2, 'd').endOf('day')},
|
||||||
|
{name : "Miya", avatar : "https://reqres.in/img/faces/6-image.jpg", email : "miya@dashwind.com", location : "Paris", amount : 230, date : moment().add(-2, 'd').endOf('day')},
|
||||||
|
{name : "Virat", avatar : "https://reqres.in/img/faces/3-image.jpg", email : "virat@dashwind.com", location : "Canada", amount : 331, date : moment().add(-2, 'd').endOf('day')},
|
||||||
|
{name : "Matrix", avatar : "https://reqres.in/img/faces/1-image.jpg", email : "matrix@dashwind.com", location : "London", amount : 581, date : moment().add(-2, 'd').endOf('day')},
|
||||||
|
{name : "Ereena", avatar : "https://reqres.in/img/faces/3-image.jpg", email : "ereena@dashwind.com", location : "Tokyo", amount : 151, date : moment().add(-2, 'd').endOf('day')},
|
||||||
|
{name : "John", avatar : "https://reqres.in/img/faces/2-image.jpg", email : "jhon@dashwind.com", location : "Paris", amount : 91, date : moment().add(-2, 'd').endOf('day')},
|
||||||
|
{name : "Virat", avatar : "https://reqres.in/img/faces/3-image.jpg", email : "virat@dashwind.com", location : "Canada", amount : 161, date : moment().add(-3, 'd').endOf('day')},
|
||||||
|
{name : "Matrix", avatar : "https://reqres.in/img/faces/4-image.jpg", email : "matrix@dashwind.com", location : "US", amount : 121, date : moment().add(-3, 'd').endOf('day')},
|
||||||
|
{name : "Ereena", avatar : "https://reqres.in/img/faces/6-image.jpg", email : "jhon@dashwind.com", location : "Tokyo", amount : 713, date : moment().add(-3, 'd').endOf('day')},
|
||||||
|
{name : "John", avatar : "https://reqres.in/img/faces/2-image.jpg", email : "ereena@dashwind.com", location : "London", amount : 217, date : moment().add(-3, 'd').endOf('day')},
|
||||||
|
{name : "Virat", avatar : "https://reqres.in/img/faces/3-image.jpg", email : "virat@dashwind.com", location : "Paris", amount : 117, date : moment().add(-3, 'd').endOf('day')},
|
||||||
|
{name : "Miya", avatar : "https://reqres.in/img/faces/7-image.jpg", email : "jhon@dashwind.com", location : "Canada", amount : 612, date : moment().add(-3, 'd').endOf('day')},
|
||||||
|
{name : "Matrix", avatar : "https://reqres.in/img/faces/3-image.jpg", email : "matrix@dashwind.com", location : "London", amount : 631, date : moment().add(-3, 'd').endOf('day')},
|
||||||
|
{name : "Virat", avatar : "https://reqres.in/img/faces/2-image.jpg", email : "ereena@dashwind.com", location : "Tokyo", amount : 151, date : moment().add(-3, 'd').endOf('day')},
|
||||||
|
{name : "Ereena", avatar : "https://reqres.in/img/faces/3-image.jpg", email : "virat@dashwind.com", location : "Paris", amount : 617, date : moment().add(-3, 'd').endOf('day')},
|
||||||
|
|
||||||
|
|
||||||
|
]
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
export const MODAL_BODY_TYPES = Object.freeze({
|
||||||
|
USER_DETAIL: "USER_DETAIL",
|
||||||
|
LEAD_ADD_NEW: "LEAD_ADD_NEW",
|
||||||
|
CONFIRMATION: "CONFIRMATION",
|
||||||
|
DEFAULT: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RIGHT_DRAWER_TYPES = Object.freeze({
|
||||||
|
NOTIFICATION: "NOTIFICATION",
|
||||||
|
CALENDAR_EVENTS: "CALENDAR_EVENTS",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CONFIRMATION_MODAL_CLOSE_TYPES = Object.freeze({
|
||||||
|
LEAD_DELETE: "LEAD_DELETE",
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
|
@ -0,0 +1,38 @@
|
||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-logo {
|
||||||
|
height: 40vmin;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.App-logo {
|
||||||
|
animation: App-logo-spin infinite 20s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-header {
|
||||||
|
background-color: #282c34;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: calc(10px + 2vmin);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-link {
|
||||||
|
color: #61dafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes App-logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
const checkAuth = () => {
|
||||||
|
/* Getting token value stored in localstorage, if token is not present we will open login page
|
||||||
|
for all internal dashboard routes */
|
||||||
|
const TOKEN = localStorage.getItem("token")
|
||||||
|
const PUBLIC_ROUTES = ["login", "forgot-password", "register", "documentation"]
|
||||||
|
|
||||||
|
const isPublicPage = PUBLIC_ROUTES.some(r => window.location.href.includes(r))
|
||||||
|
|
||||||
|
if (!TOKEN && !isPublicPage) {
|
||||||
|
window.location.href = '/login'
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
axios.defaults.headers.common['Authorization'] = `Bearer ${TOKEN}`
|
||||||
|
|
||||||
|
axios.interceptors.request.use(function (config) {
|
||||||
|
// UPDATE: Add this code to show global loading indicator
|
||||||
|
document.body.classList.add('loading-indicator');
|
||||||
|
return config
|
||||||
|
}, function (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.interceptors.response.use(function (response) {
|
||||||
|
// UPDATE: Add this code to hide global loading indicator
|
||||||
|
document.body.classList.remove('loading-indicator');
|
||||||
|
return response;
|
||||||
|
}, function (error) {
|
||||||
|
document.body.classList.remove('loading-indicator');
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
return TOKEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default checkAuth
|
|
@ -0,0 +1,27 @@
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
const initializeApp = () => {
|
||||||
|
|
||||||
|
// Setting base URL for all API request via axios
|
||||||
|
axios.defaults.baseURL = import.meta.env.VITE_API_BASE_URL;
|
||||||
|
|
||||||
|
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
||||||
|
// dev code
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Prod build code
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Removing console.log from prod
|
||||||
|
console.log = () => { };
|
||||||
|
|
||||||
|
|
||||||
|
// init analytics here
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default initializeApp
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
|
import headerSlice from '../Components/features/common/headerSlice'
|
||||||
|
import modalSlice from '../Components/features/common/modalSlice'
|
||||||
|
import rightDrawerSlice from '../Components/features/common/rightDrawerSlice'
|
||||||
|
|
||||||
|
const combinedReducer = {
|
||||||
|
header: headerSlice,
|
||||||
|
rightDrawer: rightDrawerSlice,
|
||||||
|
modal: modalSlice,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default configureStore({
|
||||||
|
reducer: combinedReducer
|
||||||
|
})
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Inertia } from "@inertiajs/inertia";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
const DeleteButton = ({ isOpen, onClose, item, tableName }) => {
|
||||||
|
if (!isOpen || !item) return null
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
Inertia.post(`/delete${tableName}/${item.id}`, {
|
||||||
|
onSuccess: () => {
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal modal-open">
|
||||||
|
<div className="modal-box">
|
||||||
|
<h2 className="font-bold text-lg text-center mb-5">
|
||||||
|
Konfirmasi Hapus Data
|
||||||
|
</h2>
|
||||||
|
<p className="text-center mb-4">
|
||||||
|
Apakah Anda yakin ingin menghapus <strong>{item.nama}</strong>?
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-center gap-4">
|
||||||
|
<button onClick={handleDelete} className="btn btn-error text-white">
|
||||||
|
Hapus
|
||||||
|
</button>
|
||||||
|
<button onClick={onClose} className="btn btn-secondary text-white">
|
||||||
|
Batal
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label className="modal-backdrop" onClick={onClose}></label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeleteButton
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { useState, createContext, useContext, Fragment } from 'react';
|
||||||
|
import { Link } from '@inertiajs/react';
|
||||||
|
import { Transition } from '@headlessui/react';
|
||||||
|
|
||||||
|
const DropDownContext = createContext();
|
||||||
|
|
||||||
|
const Dropdown = ({ children }) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const toggleOpen = () => {
|
||||||
|
setOpen((previousState) => !previousState);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropDownContext.Provider value={{ open, setOpen, toggleOpen }}>
|
||||||
|
<div className="relative">{children}</div>
|
||||||
|
</DropDownContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Trigger = ({ children }) => {
|
||||||
|
const { open, setOpen, toggleOpen } = useContext(DropDownContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div onClick={toggleOpen}>{children}</div>
|
||||||
|
|
||||||
|
{open && <div className="fixed inset-0 z-40" onClick={() => setOpen(false)}></div>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Content = ({ align = 'right', width = '48', contentClasses = 'py-1 bg-white', children }) => {
|
||||||
|
const { open, setOpen } = useContext(DropDownContext);
|
||||||
|
|
||||||
|
let alignmentClasses = 'origin-top';
|
||||||
|
|
||||||
|
if (align === 'left') {
|
||||||
|
alignmentClasses = 'ltr:origin-top-left rtl:origin-top-right start-0';
|
||||||
|
} else if (align === 'right') {
|
||||||
|
alignmentClasses = 'ltr:origin-top-right rtl:origin-top-left end-0';
|
||||||
|
}
|
||||||
|
|
||||||
|
let widthClasses = '';
|
||||||
|
|
||||||
|
if (width === '48') {
|
||||||
|
widthClasses = 'w-48';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
show={open}
|
||||||
|
enter="transition ease-out duration-200"
|
||||||
|
enterFrom="opacity-0 scale-95"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="opacity-100 scale-100"
|
||||||
|
leaveTo="opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`absolute z-50 mt-2 rounded-md shadow-lg ${alignmentClasses} ${widthClasses}`}
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
<div className={`rounded-md ring-1 ring-black ring-opacity-5 ` + contentClasses}>{children}</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DropdownLink = ({ className = '', children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
{...props}
|
||||||
|
className={
|
||||||
|
'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out ' +
|
||||||
|
className
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Dropdown.Trigger = Trigger;
|
||||||
|
Dropdown.Content = Content;
|
||||||
|
Dropdown.Link = DropdownLink;
|
||||||
|
|
||||||
|
export default Dropdown;
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { themeChange } from 'theme-change'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import BellIcon from '@heroicons/react/24/outline/BellIcon'
|
||||||
|
import Bars3Icon from '@heroicons/react/24/outline/Bars3Icon'
|
||||||
|
import MoonIcon from '@heroicons/react/24/outline/MoonIcon'
|
||||||
|
import SunIcon from '@heroicons/react/24/outline/SunIcon'
|
||||||
|
import { openRightDrawer } from './features/common/rightDrawerSlice'
|
||||||
|
import { RIGHT_DRAWER_TYPES } from '../../../public/utils/globalConstantUtil'
|
||||||
|
import { Link } from '@inertiajs/react'
|
||||||
|
import { useForm, usePage } from '@inertiajs/react'
|
||||||
|
|
||||||
|
function Header() {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const { noOfNotifications, pageTitle } = useSelector(state => state.header)
|
||||||
|
|
||||||
|
const [theme, setTheme] = useState(localStorage.getItem("theme") ||
|
||||||
|
(window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light")
|
||||||
|
);
|
||||||
|
|
||||||
|
const { auth } = usePage().props
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
themeChange(false);
|
||||||
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
|
localStorage.setItem("theme", theme);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
setTheme(prevTheme => prevTheme === "dark" ? "light" : "dark");
|
||||||
|
}
|
||||||
|
|
||||||
|
// const openNotification = () => {
|
||||||
|
// dispatch(openRightDrawer({ header: "Notifications", bodyType: RIGHT_DRAWER_TYPES.NOTIFICATION }))
|
||||||
|
// }
|
||||||
|
|
||||||
|
const { post } = useForm()
|
||||||
|
const logoutUser = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
post('/logout')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="navbar sticky top-0 bg-base-100 z-10 shadow-md">
|
||||||
|
<div className="flex-1">
|
||||||
|
<label htmlFor="left-sidebar-drawer" className="btn btn-primary drawer-button lg:hidden">
|
||||||
|
<Bars3Icon className="h-5 inline-block w-5" />
|
||||||
|
</label>
|
||||||
|
<h1 className="text-2xl font-semibold ml-2">{pageTitle}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-none">
|
||||||
|
<button onClick={toggleTheme} className="btn btn-ghost">
|
||||||
|
{theme === "dark" ? <SunIcon className="w-6 h-6" /> : <MoonIcon className="w-6 h-6" />}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* <button className="btn btn-ghost ml-4 btn-circle" onClick={openNotification}>
|
||||||
|
<div className="indicator">
|
||||||
|
<BellIcon className="h-6 w-6" />
|
||||||
|
{noOfNotifications > 0 && <span className="indicator-item badge badge-secondary badge-sm">{noOfNotifications}</span>}
|
||||||
|
</div>
|
||||||
|
</button> */}
|
||||||
|
|
||||||
|
<div className="dropdown dropdown-end ml-4">
|
||||||
|
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
|
||||||
|
<div className="w-10 rounded-full">
|
||||||
|
<img src={auth.user.foto ? `${auth.user.foto}` : `/fotoSantri/no-pic.png`} alt="profile" />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<ul tabIndex={0} className="menu menu-compact dropdown-content mt-3 p-2 shadow bg-base-100 rounded-box w-52">
|
||||||
|
<li className="ml-3" >Welcome, {auth.user.nama}</li>
|
||||||
|
<div className="divider mt-0 mb-0"></div>
|
||||||
|
<li><Link href={route('profile.edit')}>Profile Settings</Link></li>
|
||||||
|
<li><a href="#" onClick={logoutUser}>Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header;
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
function InputText({labelTitle, labelStyle, type, containerStyle, defaultValue, placeholder, updateFormValue, updateType}){
|
||||||
|
|
||||||
|
const [value, setValue] = useState(defaultValue)
|
||||||
|
|
||||||
|
const updateInputValue = (val) => {
|
||||||
|
setValue(val)
|
||||||
|
updateFormValue({updateType, value : val})
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className={`form-control w-full ${containerStyle}`}>
|
||||||
|
<label className="label">
|
||||||
|
<span className={"label-text text-base-content " + labelStyle}>{labelTitle}</span>
|
||||||
|
</label>
|
||||||
|
<input type={type || "text"} value={value} placeholder={placeholder || ""} onChange={(e) => updateInputValue(e.target.value)}className="input input-bordered w-full " />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default InputText
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
|
||||||
|
function SearchBar({searchText, styleClass, placeholderText, setSearchText}) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const updateSearchInput = (value) => {
|
||||||
|
setSearchText(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={"inline-block " + styleClass}>
|
||||||
|
<div className="input-group relative flex flex-wrap items-stretch w-full ">
|
||||||
|
<input type="search" value={searchText} placeholder={placeholderText || "Search"} onChange={(e) => updateSearchInput(e.target.value)} className="input input-sm input-bordered w-full max-w-xs" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchBar
|
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import capitalize from 'capitalize-the-first-letter'
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon'
|
||||||
|
|
||||||
|
|
||||||
|
function SelectBox(props){
|
||||||
|
|
||||||
|
const {labelTitle, labelDescription, defaultValue, containerStyle, placeholder, labelStyle, options, updateType, updateFormValue} = props
|
||||||
|
|
||||||
|
const [value, setValue] = useState(defaultValue || "")
|
||||||
|
|
||||||
|
|
||||||
|
const updateValue = (newValue) =>{
|
||||||
|
updateFormValue({updateType, value : newValue})
|
||||||
|
setValue(newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`inline-block ${containerStyle}`}>
|
||||||
|
<label className={`label ${labelStyle}`}>
|
||||||
|
<div className="label-text">{labelTitle}
|
||||||
|
{labelDescription && <div className="tooltip tooltip-right" data-tip={labelDescription}><InformationCircleIcon className='w-4 h-4'/></div>}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<select className="select select-bordered w-full" value={value} onChange={(e) => updateValue(e.target.value)}>
|
||||||
|
<option disabled value="PLACEHOLDER">{placeholder}</option>
|
||||||
|
{
|
||||||
|
options.map((o, k) => {
|
||||||
|
return <option value={o.value || o.name} key={k}>{o.name}</option>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectBox
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
function TextAreaInput({labelTitle, labelStyle, type, containerStyle, defaultValue, placeholder, updateFormValue, updateType}){
|
||||||
|
|
||||||
|
const [value, setValue] = useState(defaultValue)
|
||||||
|
|
||||||
|
const updateInputValue = (val) => {
|
||||||
|
setValue(val)
|
||||||
|
updateFormValue({updateType, value : val})
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className={`form-control w-full ${containerStyle}`}>
|
||||||
|
<label className="label">
|
||||||
|
<span className={"label-text text-base-content " + labelStyle}>{labelTitle}</span>
|
||||||
|
</label>
|
||||||
|
<textarea value={value} className="textarea textarea-bordered w-full" placeholder={placeholder || ""} onChange={(e) => updateInputValue(e.target.value)}></textarea>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default TextAreaInput
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
function ToogleInput({labelTitle, labelStyle, type, containerStyle, defaultValue, placeholder, updateFormValue, updateType}){
|
||||||
|
|
||||||
|
const [value, setValue] = useState(defaultValue)
|
||||||
|
|
||||||
|
const updateToogleValue = () => {
|
||||||
|
setValue(!value)
|
||||||
|
updateFormValue({updateType, value : !value})
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className={`form-control w-full ${containerStyle}`}>
|
||||||
|
<label className="label cursor-pointer">
|
||||||
|
<span className={"label-text text-base-content " + labelStyle}>{labelTitle}</span>
|
||||||
|
<input type="checkbox" className="toggle" checked={value} onChange={(e) => updateToogleValue()}/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default ToogleInput
|
|
@ -0,0 +1,33 @@
|
||||||
|
import PageContent from "./PageContent";
|
||||||
|
import LeftSidebar from "./LeftSidebar";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { removeNotificationMessage } from "@/Components/features/common/headerSlice";
|
||||||
|
import { themeChange } from "theme-change";
|
||||||
|
import { usePage } from "@inertiajs/react";
|
||||||
|
|
||||||
|
function Layout({ children }) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { url, } = usePage();
|
||||||
|
const [showSidebar, setShowSidebar] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// console.log("Route berubah (Inertia):", url);
|
||||||
|
setShowSidebar(!url.startsWith("/login"));
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
themeChange(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="drawer lg:drawer-open">
|
||||||
|
<input id="left-sidebar-drawer" type="checkbox" className="drawer-toggle" />
|
||||||
|
{showSidebar && <LeftSidebar />}
|
||||||
|
<PageContent>{children}</PageContent>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { Link, usePage } from '@inertiajs/react';
|
||||||
|
import SidebarSubmenu from './SidebarSubmenu';
|
||||||
|
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
||||||
|
import routes from '../Routes/sidebar';
|
||||||
|
|
||||||
|
function LeftSidebar() {
|
||||||
|
const { url } = usePage();
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
document.getElementById('left-sidebar-drawer').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="drawer-side z-30">
|
||||||
|
<label htmlFor="left-sidebar-drawer" className="drawer-overlay"></label>
|
||||||
|
<ul className="menu pt-2 w-64 bg-base-100 min-h-full text-base-content">
|
||||||
|
<button
|
||||||
|
className="btn btn-ghost bg-base-300 btn-circle z-50 top-0 right-0 mt-4 mr-2 absolute lg:hidden"
|
||||||
|
onClick={close}
|
||||||
|
>
|
||||||
|
<XMarkIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<li className="mb-2 font-semibold">
|
||||||
|
<Link href="/dashboard" className='flex justify-center'><img src="/assets/gogoSantri.png" alt="pp" width={100} /></Link>
|
||||||
|
</li>
|
||||||
|
{routes.map((route, k) => (
|
||||||
|
<li key={k}>
|
||||||
|
{route.submenu ? (
|
||||||
|
<SidebarSubmenu {...route} />
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
href={route.path}
|
||||||
|
className={`relative font-normal ${route.path === url ? 'font-semibold bg-base-200' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{route.path === url && (
|
||||||
|
<span
|
||||||
|
className="absolute inset-y-0 left-0 w-1 rounded-tr-md rounded-br-md bg-primary"
|
||||||
|
aria-hidden="true"
|
||||||
|
></span>
|
||||||
|
)}
|
||||||
|
{route.icon} {route.name}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LeftSidebar;
|
|
@ -0,0 +1,318 @@
|
||||||
|
import { Inertia } from "@inertiajs/inertia";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
const getImageUrl = (foto) => {
|
||||||
|
if (!foto) return null;
|
||||||
|
if (typeof foto === 'string') return `${foto}`;
|
||||||
|
return URL.createObjectURL(foto);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalInput = ({ fields, tableName, options, initialData, onClose, showPayments = false }) => {
|
||||||
|
const [formData, setFormData] = useState({});
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
const [selectedPayments, setSelectedPayments] = useState([]);
|
||||||
|
const [paymentDetails, setPaymentDetails] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// console.log(initialData)
|
||||||
|
setFormData(initialData || {});
|
||||||
|
setSelectedPayments([]);
|
||||||
|
setPaymentDetails({});
|
||||||
|
}, [initialData]);
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
if (e.target.type === "file") {
|
||||||
|
setFormData({ ...formData, [e.target.name]: e.target.files[0] });
|
||||||
|
} else {
|
||||||
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// console.log(initialData)
|
||||||
|
const handlePaymentChange = (e) => {
|
||||||
|
const typeId = e.target.value;
|
||||||
|
console.log(typeId)
|
||||||
|
|
||||||
|
if (!selectedPayments.includes(typeId)) {
|
||||||
|
setSelectedPayments([...selectedPayments, typeId]);
|
||||||
|
|
||||||
|
const nominal = parseInt(options.payment_nominal[typeId]) || 0;
|
||||||
|
const penalty = parseInt(options.payment_penalty[typeId]) || 0;
|
||||||
|
|
||||||
|
setPaymentDetails({
|
||||||
|
...paymentDetails,
|
||||||
|
[typeId]: {
|
||||||
|
range: 1,
|
||||||
|
nominal,
|
||||||
|
penalty,
|
||||||
|
amount: nominal + penalty,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRangeChange = (paymentType, newRange) => {
|
||||||
|
setPaymentDetails((prevDetails) => {
|
||||||
|
const validRange = newRange && !isNaN(newRange) ? Math.max(1, Number(newRange)) : 1;
|
||||||
|
const currentDetails = prevDetails[paymentType] || { nominal: 0, penalty: 0 };
|
||||||
|
|
||||||
|
const newAmount = (currentDetails.nominal + currentDetails.penalty) * validRange;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevDetails,
|
||||||
|
[paymentType]: {
|
||||||
|
...currentDetails,
|
||||||
|
range: validRange,
|
||||||
|
amount: newAmount,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemovePayment = (paymentType) => {
|
||||||
|
setSelectedPayments(selectedPayments.filter((p) => p !== paymentType));
|
||||||
|
const newDetails = { ...paymentDetails };
|
||||||
|
delete newDetails[paymentType];
|
||||||
|
setPaymentDetails(newDetails);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formDataObj = new FormData();
|
||||||
|
Object.keys(formData).forEach((key) => {
|
||||||
|
if (key === 'foto' && !(formData[key] instanceof File)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
formDataObj.append(key, formData[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (showPayments) {
|
||||||
|
const detailsArray = Object.entries(paymentDetails).map(([type_id, detail]) => ({
|
||||||
|
...detail,
|
||||||
|
type_id: parseInt(type_id)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Append items satu per satu
|
||||||
|
detailsArray.forEach((item, index) => {
|
||||||
|
formDataObj.append(`items[${index}][type_id]`, item.type_id);
|
||||||
|
formDataObj.append(`items[${index}][range]`, item.range);
|
||||||
|
// Kalau mau append nominal & penalty juga bisa, tapi biasanya backend gak butuh karena bisa hitung ulang
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const url = initialData
|
||||||
|
? `/update${tableName}/${initialData.id}`
|
||||||
|
: `/add${tableName}`;
|
||||||
|
|
||||||
|
Inertia.post(url, formDataObj, {
|
||||||
|
forceFormData: true,
|
||||||
|
onError: (errors) => setErrors(errors),
|
||||||
|
onSuccess: () => {
|
||||||
|
document.getElementById('modal_input').checked = false;
|
||||||
|
setFormData({});
|
||||||
|
setErrors({});
|
||||||
|
onClose({});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formData.foto instanceof File) {
|
||||||
|
const url = URL.createObjectURL(formData.foto);
|
||||||
|
return () => URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
}, [formData.foto]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="modal_input" className="modal-toggle" />
|
||||||
|
<div className="modal" role="dialog">
|
||||||
|
<div className="modal-box max-w-lg">
|
||||||
|
<h2 className="font-bold text-xl text-center mb-6 border-b pb-2">
|
||||||
|
Form Data
|
||||||
|
</h2>
|
||||||
|
<form onSubmit={handleSubmit} encType="multipart/form-data" className="space-y-4">
|
||||||
|
{Object.entries(fields).map(([field, config]) => {
|
||||||
|
const type = typeof config === "string" ? config : config.type;
|
||||||
|
const readOnly = typeof config === "object" && config.readonly;
|
||||||
|
return (
|
||||||
|
<div key={field} className="form-control">
|
||||||
|
<label className="label font-semibold capitalize">{field.replace("_", " ")}</label>
|
||||||
|
{type === "select" ? (
|
||||||
|
<select
|
||||||
|
name={field}
|
||||||
|
value={formData[field] || ""}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="select select-info focus:outline-none focus:ring-1 ring-info w-full"
|
||||||
|
>
|
||||||
|
<option disabled value="">
|
||||||
|
Pilih {field.replace("_", " ")}
|
||||||
|
</option>
|
||||||
|
{options[field] &&
|
||||||
|
Object.entries(options[field]).map(([key, value]) => (
|
||||||
|
<option key={key} value={key}>
|
||||||
|
{value}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
) : type === "file" ? (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
name={field}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="file-input file-input-info focus:outline-none focus:ring-1 ring-info w-full"
|
||||||
|
/>
|
||||||
|
{field === 'foto' && (
|
||||||
|
<div className="mt-2">
|
||||||
|
{formData.foto && (
|
||||||
|
<img
|
||||||
|
src={getImageUrl(formData.foto)}
|
||||||
|
alt="Preview Foto"
|
||||||
|
className="w-32 h-32 object-cover rounded-lg border"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
) : type === "password" ? (
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name={field}
|
||||||
|
onChange={handleChange}
|
||||||
|
value={formData[field] || ""}
|
||||||
|
className="input input-info focus:outline-none focus:ring-1 ring-info w-full"
|
||||||
|
placeholder={
|
||||||
|
initialData
|
||||||
|
? 'Kosongkan jika tidak ingin mengubah password'
|
||||||
|
: 'Masukkan password broh'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
) : (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
name={field}
|
||||||
|
value={formData[field] || ""}
|
||||||
|
onChange={handleChange}
|
||||||
|
readOnly={readOnly}
|
||||||
|
className="input input-info focus:outline-none focus:ring-1 ring-info w-full"
|
||||||
|
placeholder={`Masukkan ${field.replace("_", " ")}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{errors[field] && (
|
||||||
|
<p className="text-red-500 text-sm mt-1">{errors[field]}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
|
||||||
|
{showPayments && (
|
||||||
|
<div className="space-y-4 mt-4">
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label font-semibold">Payment Type</label>
|
||||||
|
<select
|
||||||
|
onChange={handlePaymentChange}
|
||||||
|
className="select select-info focus:outline-none focus:ring-1 ring-info w-full"
|
||||||
|
>
|
||||||
|
<option disabled value="">
|
||||||
|
Pilih Payment Type
|
||||||
|
</option>
|
||||||
|
{options.payment_type &&
|
||||||
|
Object.entries(options.payment_type).map(([id, name]) => (
|
||||||
|
<option key={id} value={id}>
|
||||||
|
{name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedPayments.map((paymentType) => (
|
||||||
|
<div
|
||||||
|
key={paymentType}
|
||||||
|
className="border rounded-lg p-4 relative bg-gray-50 shadow-sm"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemovePayment(paymentType)}
|
||||||
|
className="absolute top-2 right-2 bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded-full text-xs"
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="form-control mb-2">
|
||||||
|
<label className="label font-semibold">
|
||||||
|
{options.payment_type[paymentType]} - Range Bulan
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value={paymentDetails[paymentType].range}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleRangeChange(paymentType, parseInt(e.target.value))
|
||||||
|
}
|
||||||
|
className="input input-info w-full"
|
||||||
|
name="range"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-control mb-2">
|
||||||
|
<label className="label font-semibold">Nominal</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={paymentDetails[paymentType].nominal}
|
||||||
|
readOnly
|
||||||
|
className="input bg-gray-100 w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-control mb-2">
|
||||||
|
<label className="label font-semibold">Penalty</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={paymentDetails[paymentType].penalty}
|
||||||
|
readOnly
|
||||||
|
className="input bg-gray-100 w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className="form-control mt-4">
|
||||||
|
<label className="label font-semibold">Total Amount</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={Object.values(paymentDetails).reduce(
|
||||||
|
(sum, p) => sum + (p.amount || 0),
|
||||||
|
0
|
||||||
|
)}
|
||||||
|
readOnly
|
||||||
|
className="input bg-gray-100 w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button type="submit" className="btn btn-info text-white font-bold w-full mt-4">
|
||||||
|
{initialData ? "submit" : "Tambah Data"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<label className="modal-backdrop" htmlFor="modal_input">
|
||||||
|
Close
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalInput
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Link } from '@inertiajs/react';
|
||||||
|
|
||||||
|
export default function NavLink({ active = false, className = '', children, ...props }) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
{...props}
|
||||||
|
className={
|
||||||
|
'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 transition duration-150 ease-in-out focus:outline-none ' +
|
||||||
|
(active
|
||||||
|
? 'border-indigo-400 text-gray-900 focus:border-indigo-700 '
|
||||||
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300 ') +
|
||||||
|
className
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import Header from "./Header";
|
||||||
|
import { Suspense, useEffect, useRef, useState } from "react";
|
||||||
|
import SuspenseContent from "./SuspenseContent";
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { usePage } from "@inertiajs/react";
|
||||||
|
import Swal from "sweetalert2"
|
||||||
|
|
||||||
|
function PageContent({ children }) {
|
||||||
|
const mainContentRef = useRef(null);
|
||||||
|
const { pageTitle } = useSelector(state => state.header);
|
||||||
|
const { url } = usePage(); // Ambil URL dari Inertia
|
||||||
|
const [isLoginPage, setIsLoginPage] = useState(false);
|
||||||
|
const { flash } = usePage().props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (flash.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
} else if (flash.error) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [flash]);
|
||||||
|
useEffect(() => {
|
||||||
|
mainContentRef.current?.scroll({
|
||||||
|
top: 0,
|
||||||
|
behavior: "smooth"
|
||||||
|
});
|
||||||
|
}, [pageTitle]);
|
||||||
|
|
||||||
|
// Update state saat route berubah
|
||||||
|
useEffect(() => {
|
||||||
|
setIsLoginPage(url === "/login");
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="drawer-content flex flex-col">
|
||||||
|
{!isLoginPage && <Header />}
|
||||||
|
<main className="flex-1 overflow-y-auto md:pt-4 pt-4 px-6 bg-base-200" ref={mainContentRef}>
|
||||||
|
<Suspense fallback={<SuspenseContent />}>
|
||||||
|
<div className="min-h-screen">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageContent;
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Link } from '@inertiajs/react';
|
||||||
|
|
||||||
|
export default function ResponsiveNavLink({ active = false, className = '', children, ...props }) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
{...props}
|
||||||
|
className={`w-full flex items-start ps-3 pe-4 py-2 border-l-4 ${
|
||||||
|
active
|
||||||
|
? 'border-indigo-400 text-indigo-700 bg-indigo-50 focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700'
|
||||||
|
: 'border-transparent text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300'
|
||||||
|
} text-base font-medium focus:outline-none transition duration-150 ease-in-out ${className}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|