Compare commits
11 Commits
f0890af9e5
...
d73a8e636c
Author | SHA1 | Date |
---|---|---|
|
d73a8e636c | |
|
0cc138f0a2 | |
|
91dff8b258 | |
|
8617fce4aa | |
|
260942462c | |
|
1238ca53fd | |
|
37a865eef7 | |
|
a9a07fd834 | |
|
a111e526bf | |
|
2a7731bbd5 | |
|
d1e06e99aa |
|
@ -1,4 +1,4 @@
|
|||
APP_NAME=Laravel
|
||||
APP_NAME=GoGoSantri
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
|
|
|
@ -34,6 +34,8 @@ public function store(LoginRequest $request): RedirectResponse
|
|||
|
||||
$request->session()->regenerate();
|
||||
|
||||
session()->flash('success', 'Login berhasil!');
|
||||
|
||||
return redirect()->intended(RouteServiceProvider::HOME);
|
||||
}
|
||||
|
||||
|
@ -48,6 +50,8 @@ public function destroy(Request $request): RedirectResponse
|
|||
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
session()->flash('success', 'Logout berhasil!');
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class NewPasswordController extends Controller
|
|||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('Auth/ResetPassword', [
|
||||
'email' => $request->email,
|
||||
'nis' => $request->nis,
|
||||
'token' => $request->route('token'),
|
||||
]);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public function store(Request $request): RedirectResponse
|
|||
{
|
||||
$request->validate([
|
||||
'token' => 'required',
|
||||
'email' => 'required|email',
|
||||
'nis' => 'required|nis',
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
|
@ -44,7 +44,7 @@ public function store(Request $request): RedirectResponse
|
|||
// 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('email', 'password', 'password_confirmation', 'token'),
|
||||
$request->only('nis', 'password', 'password_confirmation', 'token'),
|
||||
function ($user) use ($request) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($request->password),
|
||||
|
@ -63,7 +63,7 @@ function ($user) use ($request) {
|
|||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [trans($status)],
|
||||
'nis' => [trans($status)],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,15 +32,16 @@ public function create(): Response
|
|||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
||||
// '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,
|
||||
'email' => $request->email,
|
||||
// 'name' => $request->name,
|
||||
'nis' => $request->nis,
|
||||
'password' => Hash::make($request->password),
|
||||
'level' => 2
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
|
|
@ -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,65 +4,161 @@
|
|||
|
||||
use App\Models\Payment;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Santri;
|
||||
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
|
||||
{
|
||||
public function indexManualPayment(cekDenda $cekDenda, GenerateMonthlyBill $generateMonthlyBill)
|
||||
{
|
||||
$penalty = $cekDenda->applyPenalty();
|
||||
$bill = $generateMonthlyBill->generateAutoBill();
|
||||
|
||||
$paymentType = PaymentType::pluck('payment_type');
|
||||
$nominal = PaymentType::pluck('nominal');
|
||||
$paymentTypes = PaymentType::get(['id', 'payment_type', 'nominal']);
|
||||
|
||||
$paymentPenalties = DetailPayment::with('paymentType')
|
||||
->get()
|
||||
->pluck('penalty', 'type_id');
|
||||
|
||||
$santri = Santri::with([
|
||||
$santri = User::with([
|
||||
'payments.detailPayments.paymentType',
|
||||
'user'
|
||||
])->get();
|
||||
])->where('level', 2)->paginate(10);
|
||||
|
||||
return Inertia::render('list-admin/payment/ManualPayment', [
|
||||
'santri' => $santri,
|
||||
'penalty' => $penalty,
|
||||
'bill' => $bill,
|
||||
'penalty' => 0,
|
||||
'fields' => [
|
||||
'nis' => 'text',
|
||||
'nama' => 'text',
|
||||
'status_santri' => 'text',
|
||||
'role_santri' => 'text',
|
||||
// 'total_penalty' => 'text',
|
||||
// 'amount_payment' => 'text',
|
||||
// 'nominal' => 'text',
|
||||
// 'payment_type' => 'select',
|
||||
'nis' => ['type' => 'text', 'readonly' => true],
|
||||
'nama' => ['type' => 'text', 'readonly' => true],
|
||||
'status_santri' => ['type' => 'text', 'readonly' => true],
|
||||
],
|
||||
'options' => [
|
||||
'payment_type' => $paymentType,
|
||||
'payment_nominal' => $nominal,
|
||||
'payment_type' => $paymentTypes->pluck('payment_type', 'id'),
|
||||
'payment_nominal' => $paymentTypes->pluck('nominal', 'id'),
|
||||
'payment_penalty' => $paymentPenalties
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function manualPayment(Request $request, $id)
|
||||
public function manualPayment(Request $request, $userId)
|
||||
{
|
||||
$request->validate([
|
||||
''
|
||||
], [
|
||||
'amount.required' => 'wajib mengisi nominal pembayaran',
|
||||
$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 {
|
||||
} catch (Exception $e) {
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function transaction()
|
||||
{
|
||||
$transaction = User::with('payments', 'payments.detailPayments', 'wallet.walletTransactions', 'payments.detailPayments.paymentType')
|
||||
->where('level', 10)
|
||||
->paginate(2);
|
||||
|
||||
// dd($transaction);
|
||||
|
||||
return Inertia::render('list-admin/payment/Transaction', compact('transaction'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@ public function index()
|
|||
return Inertia::render('list-admin/payment/PaymentType', [
|
||||
'paymentType' => $paymentType,
|
||||
'fields' => [
|
||||
'payment_type' => 'text'
|
||||
'payment_type' => 'text',
|
||||
'nominal' => 'number',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@ -25,17 +26,20 @@ public function index()
|
|||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'payment_type' => 'required|string'
|
||||
'payment_type' => 'required|string',
|
||||
'nominal' => 'required|numeric'
|
||||
], [
|
||||
'payment_type.required' => 'wajib mengisi payment type'
|
||||
'payment_type.required' => 'wajib mengisi payment type',
|
||||
'nominal.required' => 'wajib mengisi nominal pembayaran'
|
||||
]);
|
||||
|
||||
try {
|
||||
PaymentType::create([
|
||||
'payment_type' => $request->payment_type
|
||||
'payment_type' => $request->payment_type,
|
||||
'nominal' => $request->nominal
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'berhasil insert data');
|
||||
return redirect()->back()->with('success', 'berhasil insert data tipe pembayaran');
|
||||
} catch (\Throwable $th) {
|
||||
return redirect()->back()->with('error', 'Data gagal di tambahkan' . $th->getMessage());
|
||||
}
|
||||
|
|
|
@ -3,61 +3,74 @@
|
|||
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
|
||||
{
|
||||
/**
|
||||
* Display the user's profile form.
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
return Inertia::render('Profile/Edit', [
|
||||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
|
||||
'status' => session('status'),
|
||||
]);
|
||||
// $user = User::all();
|
||||
return Inertia::render('Profile/Profile');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return Redirect::route('profile.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's account.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
public function updateProfile(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'password' => ['required', 'current_password'],
|
||||
'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',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
$santri = User::findOrFail($id);
|
||||
try {
|
||||
|
||||
Auth::logout();
|
||||
$updateData = [
|
||||
'nama' => $request->nama,
|
||||
'alamat' => $request->alamat,
|
||||
'jk' => $request->jk,
|
||||
'tanggal_lahir' => $request->tanggal_lahir,
|
||||
];
|
||||
|
||||
$user->delete();
|
||||
if ($request->hasFile('foto')) {
|
||||
if ($santri->foto && File::exists(public_path($santri->foto))) {
|
||||
File::delete(public_path($santri->foto));
|
||||
}
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
$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 Redirect::to('/');
|
||||
// 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,32 +2,38 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Santri;
|
||||
use App\Models\User;
|
||||
use App\Models\Wallet;
|
||||
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
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$santri = Santri::all();
|
||||
$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',
|
||||
'role_santri' => 'select',
|
||||
'jk' => 'select',
|
||||
'level' => 'select',
|
||||
'tanggal_lahir' => 'date',
|
||||
'foto' => 'file'
|
||||
'foto' => 'file',
|
||||
],
|
||||
'options' => [
|
||||
'status_santri' => ['boyong' => 'Boyong', 'aktif' => 'Aktif'],
|
||||
'status_santri' => ['lulus' => 'Lulus', 'aktif' => 'Aktif'],
|
||||
'role_santri' => ['santri' => 'Santri', 'pengurus' => 'Pengurus'],
|
||||
'jk' => ['laki laki' => 'Laki-Laki', 'perempuan' => 'Perempuan'],
|
||||
'level' => [1 => 'Admin', 2 => 'User']
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
@ -35,21 +41,28 @@ public function index()
|
|||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'nis' => 'required',
|
||||
'password' => 'required',
|
||||
'level' => 'required',
|
||||
'nama' => 'required',
|
||||
'alamat' => 'required',
|
||||
'status_santri' => 'required',
|
||||
'role_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',
|
||||
'role.required' => 'wajib mengisi role santri',
|
||||
'gender.required' => 'wajib mengisi gender',
|
||||
'ttl.required' => 'wajib mengisi tanggal lahir santri',
|
||||
'ttl.date' => 'tanggal lahir harus dalam format tanggal yang benar',
|
||||
'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;
|
||||
|
@ -63,17 +76,22 @@ public function store(Request $request)
|
|||
}
|
||||
|
||||
try {
|
||||
$santri = Santri::create([
|
||||
$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,
|
||||
'role_santri' => $request->role_santri,
|
||||
'jk' => $request->jk,
|
||||
'tanggal_lahir' => $request->tanggal_lahir,
|
||||
'no_telp' => $request->no_telp,
|
||||
'foto' => $fotoPath
|
||||
]);
|
||||
// dd($santri);
|
||||
|
||||
$santri->wallet()->create(['saldo' => 0]);
|
||||
|
||||
// dd($santri);
|
||||
return redirect()->back()->with('success', 'Data berhasil ditambahkan');
|
||||
} catch (\Throwable $th) {
|
||||
// dd($th->getMessage());
|
||||
|
@ -84,30 +102,34 @@ public function store(Request $request)
|
|||
public function update(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'nis' => 'required',
|
||||
'password' => 'nullable',
|
||||
'level' => 'required',
|
||||
'nama' => 'required',
|
||||
'alamat' => 'required',
|
||||
'status_santri' => 'required',
|
||||
'role_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',
|
||||
'role_santri.required' => 'wajib mengisi role santri',
|
||||
'jk.required' => 'wajib mengisi jenis kelamin',
|
||||
'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 = Santri::findOrFail($id);
|
||||
$santri = User::findOrFail($id);
|
||||
$updateData = [
|
||||
'nis' => $request->nis,
|
||||
'level' => $request->level,
|
||||
'nama' => $request->nama,
|
||||
'alamat' => $request->alamat,
|
||||
'status_santri' => $request->status_santri,
|
||||
'role_santri' => $request->role_santri,
|
||||
'jk' => $request->jk,
|
||||
'tanggal_lahir' => $request->tanggal_lahir,
|
||||
];
|
||||
|
@ -122,18 +144,24 @@ public function update(Request $request, $id)
|
|||
$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(Santri $santri, $id)
|
||||
public function destroy($id)
|
||||
{
|
||||
$santri = Santri::findOrFail($id);
|
||||
$santri = User::findOrFail($id);
|
||||
|
||||
$santri->delete();
|
||||
File::delete('fotoSantri/' . basename($santri->foto));
|
||||
|
|
|
@ -2,64 +2,18 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Wallet;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class WalletController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function walletUser()
|
||||
{
|
||||
//
|
||||
}
|
||||
$wallet = User::with('wallet')
|
||||
->where('level', 2)
|
||||
->paginate(10);
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
//
|
||||
return Inertia::render('list-admin/payment/WalletUser', compact('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)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -34,6 +34,10 @@ public function share(Request $request): array
|
|||
'auth' => [
|
||||
'user' => $request->user(),
|
||||
],
|
||||
'flash' => [
|
||||
'success' => fn() => $request->session()->get('success'),
|
||||
'error' => fn() => $request->session()->get('error'),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ public function authorize(): bool
|
|||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'nis' => ['required', 'string'],
|
||||
'password' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
@ -41,11 +41,11 @@ public function authenticate(): void
|
|||
{
|
||||
$this->ensureIsNotRateLimited();
|
||||
|
||||
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
||||
if (! Auth::attempt($this->only('nis', 'password'), $this->boolean('remember'))) {
|
||||
RateLimiter::hit($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.failed'),
|
||||
'nis' => trans('auth.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ public function ensureIsNotRateLimited(): void
|
|||
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.throttle', [
|
||||
'nis' => trans('auth.throttle', [
|
||||
'seconds' => $seconds,
|
||||
'minutes' => ceil($seconds / 60),
|
||||
]),
|
||||
|
@ -80,6 +80,6 @@ public function ensureIsNotRateLimited(): void
|
|||
*/
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||
return Str::transliterate(Str::lower($this->string('nis')) . '|' . $this->ip());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ class Payment extends Model
|
|||
|
||||
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()
|
||||
|
|
|
@ -1,29 +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'];
|
||||
protected $table = 'santris';
|
||||
|
||||
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>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -38,12 +36,14 @@ class User extends Authenticatable
|
|||
*
|
||||
* @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');
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -19,6 +20,11 @@ public function register(): void
|
|||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
Inertia::share([
|
||||
'flash' => [
|
||||
'success' => session('success'),
|
||||
'error' => session('error'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +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('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')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('santris');
|
||||
}
|
||||
};
|
|
@ -16,7 +16,13 @@ public function up(): void
|
|||
$table->string('nis');
|
||||
$table->string('password');
|
||||
$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->timestamps();
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ public function up(): void
|
|||
Schema::create('payment_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('payment_type');
|
||||
$table->float('nominal');
|
||||
$table->decimal('nominal');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ public function up(): void
|
|||
{
|
||||
Schema::create('wallets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('santri_id')->constrained('santris')->onDelete('cascade');
|
||||
$table->float('saldo');
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->decimal('saldo');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -13,9 +13,10 @@ public function up(): void
|
|||
{
|
||||
Schema::create('wallet_transactions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('order_id')->unique();
|
||||
$table->foreignId('wallet_id')->constrained('wallets')->onDelete('cascade');
|
||||
$table->enum('transaction_type', ['topup', 'payment']);
|
||||
$table->float('amount');
|
||||
$table->decimal('amount');
|
||||
$table->string('description');
|
||||
$table->enum('status', ['pending', 'success', 'failed']);
|
||||
$table->timestamps();
|
||||
|
|
|
@ -13,12 +13,15 @@ public function up(): void
|
|||
{
|
||||
Schema::create('payments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('order_id')->unique()->nullable();
|
||||
$table->enum('payment_status', ['pending', 'failed', 'success']);
|
||||
$table->float('amount_payment')->nullable();
|
||||
$table->enum('transaction_type', ['topup', 'payment']);
|
||||
$table->decimal('amount_payment')->nullable();
|
||||
$table->String('bank')->nullable();
|
||||
$table->string('no_va')->nullable();
|
||||
$table->dateTime('expired_at')->nullable();
|
||||
$table->foreignId('santri_id')->constrained('santris')->onDelete('cascade');
|
||||
$table->string('snap_token')->nullable();
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->foreignId('wallet_id')->nullable()->constrained('wallets')->onDelete('cascade');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
|
|
@ -15,8 +15,8 @@ public function up(): void
|
|||
$table->id();
|
||||
$table->foreignId('payment_id')->constrained('payments')->onDelete('cascade');
|
||||
$table->enum('status', ['paid', 'unpaid']);
|
||||
$table->float('amount')->nullable();
|
||||
$table->float('penalty')->nullable();
|
||||
$table->decimal('amount')->nullable();
|
||||
$table->decimal('penalty')->nullable();
|
||||
$table->integer('payment_month');
|
||||
$table->integer('payment_year');
|
||||
$table->foreignId('type_id')->constrained('payment_types')->onDelete('cascade');
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
|
@ -12,11 +14,31 @@ class DatabaseSeeder extends Seeder
|
|||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// \App\Models\User::factory(10)->create();
|
||||
|
||||
// \App\Models\User::factory()->create([
|
||||
// 'name' => 'Test User',
|
||||
// 'email' => 'test@example.com',
|
||||
// ]);
|
||||
User::insert([
|
||||
[
|
||||
'nis' => '1234567890',
|
||||
'password' => Hash::make('password123'),
|
||||
'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'
|
||||
]
|
||||
],);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,19 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@inertiajs/inertia": "^0.11.1"
|
||||
"@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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@headlessui/react": "^1.4.2",
|
||||
"@inertiajs/react": "^1.3.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"autoprefixer": "^10.4.12",
|
||||
|
@ -18,7 +26,6 @@
|
|||
"laravel-vite-plugin": "^0.7.2",
|
||||
"postcss": "^8.4.31",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwindcss": "^3.2.1",
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
|
@ -63,30 +70,30 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.26.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz",
|
||||
"integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==",
|
||||
"version": "7.26.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz",
|
||||
"integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz",
|
||||
"integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
|
||||
"integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/generator": "^7.26.5",
|
||||
"@babel/generator": "^7.26.9",
|
||||
"@babel/helper-compilation-targets": "^7.26.5",
|
||||
"@babel/helper-module-transforms": "^7.26.0",
|
||||
"@babel/helpers": "^7.26.7",
|
||||
"@babel/parser": "^7.26.7",
|
||||
"@babel/template": "^7.25.9",
|
||||
"@babel/traverse": "^7.26.7",
|
||||
"@babel/types": "^7.26.7",
|
||||
"@babel/helpers": "^7.26.9",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/traverse": "^7.26.9",
|
||||
"@babel/types": "^7.26.9",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
|
@ -102,13 +109,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.26.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
|
||||
"integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz",
|
||||
"integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.26.5",
|
||||
"@babel/types": "^7.26.5",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/types": "^7.26.9",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^3.0.2"
|
||||
|
@ -200,25 +207,25 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz",
|
||||
"integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
|
||||
"integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.25.9",
|
||||
"@babel/types": "^7.26.7"
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/types": "^7.26.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
|
||||
"integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
|
||||
"integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.26.7"
|
||||
"@babel/types": "^7.26.9"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
|
@ -258,30 +265,30 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
||||
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.25.9",
|
||||
"@babel/parser": "^7.25.9",
|
||||
"@babel/types": "^7.25.9"
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/types": "^7.26.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
|
||||
"integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz",
|
||||
"integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/generator": "^7.26.5",
|
||||
"@babel/parser": "^7.26.7",
|
||||
"@babel/template": "^7.25.9",
|
||||
"@babel/types": "^7.26.7",
|
||||
"@babel/generator": "^7.26.9",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/types": "^7.26.9",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
|
@ -290,9 +297,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
|
||||
"integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
|
||||
"integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
|
@ -671,15 +678,21 @@
|
|||
"react-dom": "^16 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroicons/react": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
|
||||
"integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
|
||||
"peerDependencies": {
|
||||
"react": ">= 16 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@inertiajs/core": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-1.3.0.tgz",
|
||||
"integrity": "sha512-TJ8R1eUYY473m9DaKlCPRdHTdznFWTDuy5VvEzXg3t/hohbDQedLj46yn/uAqziJPEUZJrSftZzPI2NMzL9tQA==",
|
||||
"dev": true,
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.0.4.tgz",
|
||||
"integrity": "sha512-gCUqpwBRYOhz0hwBDWca2lkk+Mc+36GvbRoE0rEvYFpzQAMMP0xFhH9h8hr7VWTn+vVOZRuDvakI+4cazwtvCg==",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"deepmerge": "^4.0.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"qs": "^6.9.0"
|
||||
}
|
||||
},
|
||||
|
@ -702,12 +715,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@inertiajs/react": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/react/-/react-1.3.0.tgz",
|
||||
"integrity": "sha512-K+PF23xP6jjMkubs8PbxT1MroSDdH1z3VTEGbO3685Xyf0QNwoNIF95hnyqJxlWaeG4fB0GAag40gh04fefRUA==",
|
||||
"dev": true,
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/react/-/react-2.0.4.tgz",
|
||||
"integrity": "sha512-syPqZNVU5v0DB3VHCm9aVQafJ9kgkxtC5lfc4WOTBxtUjZjbJYDwt5d0yLOhyfU4S7d9CR0dhlkkEt1DsedD3Q==",
|
||||
"dependencies": {
|
||||
"@inertiajs/core": "1.3.0",
|
||||
"@inertiajs/core": "2.0.4",
|
||||
"lodash.isequal": "^4.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -779,6 +791,12 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -824,6 +842,29 @@
|
|||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz",
|
||||
"integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==",
|
||||
"dependencies": {
|
||||
"immer": "^10.0.3",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"reselect": "^5.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/forms": {
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
|
||||
|
@ -837,12 +878,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-virtual": {
|
||||
"version": "3.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.3.tgz",
|
||||
"integrity": "sha512-vCU+OTylXN3hdC8RKg68tPlBPjjxtzon7Ys46MgrSLE+JhSjSTPvoQifV6DQJeJmA8Q3KT6CphJbejupx85vFw==",
|
||||
"version": "3.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.2.tgz",
|
||||
"integrity": "sha512-LceSUgABBKF6HSsHK2ZqHzQ37IKV/jlaWbHm+NyTa3/WNb/JZVcThDuTainf+PixltOOcFCYXwxbLpOX9sCx+g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@tanstack/virtual-core": "3.11.3"
|
||||
"@tanstack/virtual-core": "3.13.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
@ -854,9 +895,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@tanstack/virtual-core": {
|
||||
"version": "3.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.3.tgz",
|
||||
"integrity": "sha512-v2mrNSnMwnPJtcVqNvV0c5roGCBqeogN8jDtgtuHCphdwBasOZ17x8UV8qpHUh+u0MLfX43c0uUHKje0s+Zb0w==",
|
||||
"version": "3.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.2.tgz",
|
||||
"integrity": "sha512-Qzz4EgzMbO5gKrmqUondCjiHcuu4B1ftHb0pjCut661lXZdGoHeze9f/M8iwsK1t5LGR6aNuNGU7mxkowaW6RQ==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
@ -904,6 +945,11 @@
|
|||
"@babel/types": "^7.20.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
|
||||
|
@ -975,8 +1021,7 @@
|
|||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.20",
|
||||
|
@ -1019,7 +1064,6 @@
|
|||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
|
@ -1098,9 +1142,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
|
||||
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
|
@ -1134,9 +1178,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001696",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz",
|
||||
"integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==",
|
||||
"version": "1.0.30001700",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
|
||||
"integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1153,6 +1197,18 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.4.8",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz",
|
||||
"integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
|
@ -1217,7 +1273,6 @@
|
|||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
|
@ -1304,6 +1359,12 @@
|
|||
"url": "https://opencollective.com/daisyui"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
|
@ -1333,7 +1394,6 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
|
@ -1370,9 +1430,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.88",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.88.tgz",
|
||||
"integrity": "sha512-K3C2qf1o+bGzbilTDCTBhTQcMS9KW60yTAaTeeXsfvQuTDDwlokLam/AdqlqcSy9u4UainDgsHV23ksXAOgamw==",
|
||||
"version": "1.5.104",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.104.tgz",
|
||||
"integrity": "sha512-Us9M2L4cO/zMBqVkJtnj353nQhMju9slHm62NprKTmdF3HH8wYOtNvDFq/JB2+ZRoGLzdvYDiATlMHs98XBM1g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
|
@ -1408,6 +1468,20 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
||||
|
@ -1489,9 +1563,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
|
||||
"integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz",
|
||||
"integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
|
@ -1529,12 +1603,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.0",
|
||||
"cross-spawn": "^7.0.6",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1545,13 +1619,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"dev": true,
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1603,16 +1677,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
|
||||
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.0",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
|
@ -1700,6 +1774,20 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
|
@ -1711,6 +1799,15 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
|
@ -1810,8 +1907,7 @@
|
|||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
|
@ -1875,14 +1971,12 @@
|
|||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||
"dev": true
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead."
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
|
@ -1933,7 +2027,6 @@
|
|||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
|
@ -1942,7 +2035,6 @@
|
|||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
|
@ -2042,12 +2134,6 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nprogress": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
|
||||
"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
@ -2067,9 +2153,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
|
||||
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
|
@ -2157,9 +2243,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
|
||||
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2302,8 +2388,7 @@
|
|||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
|
@ -2343,7 +2428,6 @@
|
|||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
|
@ -2351,11 +2435,21 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-chartjs-2": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz",
|
||||
"integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==",
|
||||
"peerDependencies": {
|
||||
"chart.js": "^4.1.1",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
|
@ -2364,6 +2458,28 @@
|
|||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.25 || ^19",
|
||||
"react": "^18.0 || ^19",
|
||||
"redux": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||
|
@ -2373,6 +2489,15 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-tailwindcss-datepicker": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-tailwindcss-datepicker/-/react-tailwindcss-datepicker-2.0.0.tgz",
|
||||
"integrity": "sha512-HADddzZjeOIMxkKkueAyQmiAExLXYcwgPG0Qv1oP9cjCx4/jrhbbOAbazgjJmxfhGxUw7e0Goqs+DY3htKlhAA==",
|
||||
"peerDependencies": {
|
||||
"dayjs": "^1.11.12",
|
||||
"react": "^17.0.2 || ^18.2.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
@ -2394,6 +2519,24 @@
|
|||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||
"peerDependencies": {
|
||||
"redux": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
|
@ -2468,6 +2611,7 @@
|
|||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
|
@ -2721,6 +2865,15 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sweetalert2": {
|
||||
"version": "11.21.0",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.21.0.tgz",
|
||||
"integrity": "sha512-fiEK7SqRY/QD/wC2uqEHlfYGZ7qe2UcyQbJpbpj4YRVqplBgcI+euPZLZL+evLINcvbtXmL1SFUdZHKqBHGAAQ==",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/limonte"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
|
@ -2758,6 +2911,11 @@
|
|||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/theme-change": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/theme-change/-/theme-change-2.5.0.tgz",
|
||||
"integrity": "sha512-B/UdsgdHAGhSKHTAQnxg/etN0RaMDpehuJmZIjLMDVJ6DGIliRHGD6pODi1CXLQAN9GV0GSyB3G6yCuK05PkPQ=="
|
||||
},
|
||||
"node_modules/thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
|
@ -2827,6 +2985,14 @@
|
|||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
@ -2898,6 +3064,11 @@
|
|||
"picomatch": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/web-vitals": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
|
||||
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
13
package.json
|
@ -6,7 +6,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@headlessui/react": "^1.4.2",
|
||||
"@inertiajs/react": "^1.3.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"autoprefixer": "^10.4.12",
|
||||
|
@ -15,11 +14,19 @@
|
|||
"laravel-vite-plugin": "^0.7.2",
|
||||
"postcss": "^8.4.31",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwindcss": "^3.2.1",
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@inertiajs/inertia": "^0.11.1"
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
|
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:
|
Before Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 184 KiB |
Before Width: | Height: | Size: 184 KiB |
Before Width: | Height: | Size: 184 KiB |
Before Width: | Height: | Size: 184 KiB |
Before Width: | Height: | Size: 302 KiB |
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 |
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 181 KiB |
After Width: | Height: | Size: 509 KiB |
Before Width: | Height: | Size: 160 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,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
|
||||
})
|
|
@ -1,7 +0,0 @@
|
|||
export default function ApplicationLogo(props) {
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
export default function Checkbox({ className = '', ...props }) {
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
type="checkbox"
|
||||
className={
|
||||
'rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 ' +
|
||||
className
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
export default function DangerButton({ className = '', disabled, children, ...props }) {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={
|
||||
`inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150 ${
|
||||
disabled && 'opacity-25'
|
||||
} ` + className
|
||||
}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
|
@ -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
|
|
@ -1,7 +0,0 @@
|
|||
export default function InputError({ message, className = '', ...props }) {
|
||||
return message ? (
|
||||
<p {...props} className={'text-sm text-red-600 ' + className}>
|
||||
{message}
|
||||
</p>
|
||||
) : null;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default function InputLabel({ value, className = '', children, ...props }) {
|
||||
return (
|
||||
<label {...props} className={`block font-medium text-sm text-gray-700 ` + className}>
|
||||
{value ? value : children}
|
||||
</label>
|
||||
);
|
||||
}
|
|
@ -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;
|
|
@ -1,6 +1,12 @@
|
|||
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({});
|
||||
|
@ -8,6 +14,7 @@ const ModalInput = ({ fields, tableName, options, initialData, onClose, showPaym
|
|||
const [paymentDetails, setPaymentDetails] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
// console.log(initialData)
|
||||
setFormData(initialData || {});
|
||||
setSelectedPayments([]);
|
||||
setPaymentDetails({});
|
||||
|
@ -21,17 +28,20 @@ const ModalInput = ({ fields, tableName, options, initialData, onClose, showPaym
|
|||
}
|
||||
};
|
||||
|
||||
// console.log(initialData)
|
||||
const handlePaymentChange = (e) => {
|
||||
const value = e.target.value;
|
||||
if (!selectedPayments.includes(value)) {
|
||||
setSelectedPayments([...selectedPayments, value]);
|
||||
const typeId = e.target.value;
|
||||
console.log(typeId)
|
||||
|
||||
const nominal = options.payment_nominal?.[value] || 0;
|
||||
const penalty = options.payment_penalty?.[value] || 0;
|
||||
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,
|
||||
[value]: {
|
||||
[typeId]: {
|
||||
range: 1,
|
||||
nominal,
|
||||
penalty,
|
||||
|
@ -59,7 +69,6 @@ const ModalInput = ({ fields, tableName, options, initialData, onClose, showPaym
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
const handleRemovePayment = (paymentType) => {
|
||||
setSelectedPayments(selectedPayments.filter((p) => p !== paymentType));
|
||||
const newDetails = { ...paymentDetails };
|
||||
|
@ -69,158 +78,241 @@ const ModalInput = ({ fields, tableName, options, initialData, onClose, showPaym
|
|||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const formDataObj = new FormData();
|
||||
|
||||
Object.entries(formData).forEach(([key, value]) => {
|
||||
if (key === 'foto' && !(value instanceof File)) return;
|
||||
formDataObj.append(key, value);
|
||||
const formDataObj = new FormData();
|
||||
Object.keys(formData).forEach((key) => {
|
||||
if (key === 'foto' && !(formData[key] instanceof File)) {
|
||||
return;
|
||||
}
|
||||
formDataObj.append(key, formData[key]);
|
||||
});
|
||||
|
||||
formDataObj.append("payments", JSON.stringify(paymentDetails));
|
||||
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}`;
|
||||
|
||||
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;
|
||||
document.getElementById('modal_input').checked = false;
|
||||
setFormData({});
|
||||
setErrors({});
|
||||
if (onClose) onClose({});
|
||||
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">
|
||||
<h2 className="font-bold text-lg text-center mb-5">
|
||||
{initialData ? "Edit Data" : "Tambah Data"}
|
||||
<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">
|
||||
{Object.keys(fields).map((field) => (
|
||||
<div key={field} className="mb-2">
|
||||
<label className="input input-bordered input-secondary flex items-center gap-2">
|
||||
{field.replace("_", " ")}
|
||||
{fields[field] === "select" ? (
|
||||
<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-bordered w-full select-secondary"
|
||||
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>
|
||||
))}
|
||||
{options[field] &&
|
||||
Object.entries(options[field]).map(([key, value]) => (
|
||||
<option key={key} value={key}>
|
||||
{value}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : fields[field] === "file" ? (
|
||||
) : 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="file"
|
||||
type="password"
|
||||
name={field}
|
||||
onChange={handleChange}
|
||||
className="file-input file-input-bordered w-full file-input-secondary"
|
||||
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={fields[field]}
|
||||
type={type}
|
||||
name={field}
|
||||
value={formData[field] || ""}
|
||||
onChange={handleChange}
|
||||
className="grow border-none focus:ring-0"
|
||||
readOnly={readOnly}
|
||||
className="input input-info focus:outline-none focus:ring-1 ring-info w-full"
|
||||
placeholder={`Masukkan ${field.replace("_", " ")}`}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
{errors[field] && <p className="text-red-500 text-sm">{errors[field]}</p>}
|
||||
</div>
|
||||
))}
|
||||
{errors[field] && (
|
||||
<p className="text-red-500 text-sm mt-1">{errors[field]}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
{showPayments && (
|
||||
<div>
|
||||
<div className="mb-2">
|
||||
<label className="input input-bordered input-secondary flex items-center gap-2">
|
||||
Payment Type
|
||||
<select onChange={handlePaymentChange} className="select select-bordered w-full select-secondary">
|
||||
<option disabled value="">Pilih Payment Type</option>
|
||||
{options.payment_type &&
|
||||
Object.entries(options.payment_type).map(([key, value]) => (
|
||||
<option key={key} value={key}>{value}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<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="mb-2 border p-2 rounded relative">
|
||||
<div
|
||||
key={paymentType}
|
||||
className="border rounded-lg p-4 relative bg-gray-50 shadow-sm"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemovePayment(paymentType)}
|
||||
className="absolute top-0 right-0 bg-red-500 text-white px-2 py-1 rounded-full text-xs"
|
||||
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="mb-2">
|
||||
<label className="input input-bordered input-secondary flex items-center gap-2">
|
||||
<div className="form-control mb-2">
|
||||
<label className="label font-semibold">
|
||||
{options.payment_type[paymentType]} - Range Bulan
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={paymentDetails[paymentType].range}
|
||||
onChange={(e) => handleRangeChange(paymentType, parseInt(e.target.value))}
|
||||
className="grow border-none focus:ring-0"
|
||||
/>
|
||||
</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="mb-2">
|
||||
<label className="input input-bordered input-secondary flex items-center gap-2">
|
||||
Nominal
|
||||
<input type="text" value={paymentDetails[paymentType].nominal} readOnly className="grow border-none focus:ring-0 bg-gray-100" />
|
||||
</label>
|
||||
<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="mb-2">
|
||||
<label className="input input-bordered input-secondary flex items-center gap-2">
|
||||
Penalty
|
||||
<input type="text" value={paymentDetails[paymentType].penalty} readOnly className="grow border-none focus:ring-0 bg-gray-100" />
|
||||
</label>
|
||||
<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="mb-2">
|
||||
<label className="input input-bordered input-secondary flex items-center gap-2">
|
||||
Total Amount
|
||||
<input
|
||||
type="text"
|
||||
value={Object.values(paymentDetails).reduce((sum, p) => sum + (p.amount || 0), 0)}
|
||||
readOnly
|
||||
className="grow border-none focus:ring-0 bg-gray-100"
|
||||
/>
|
||||
|
||||
</label>
|
||||
<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-secondary w-full mt-3">
|
||||
{initialData ? "Simpan Perubahan" : "Tambah Data"}
|
||||
<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>
|
||||
<label className="modal-backdrop" htmlFor="modal_input">
|
||||
Close
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalInput;
|
||||
export default ModalInput
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
export default function PrimaryButton({ className = '', disabled, children, ...props }) {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={
|
||||
`inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150 ${
|
||||
disabled && 'opacity-25'
|
||||
} ` + className
|
||||
}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
export default function SecondaryButton({ type = 'button', className = '', disabled, children, ...props }) {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
type={type}
|
||||
className={
|
||||
`inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150 ${
|
||||
disabled && 'opacity-25'
|
||||
} ` + className
|
||||
}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import ChevronDownIcon from '@heroicons/react/24/outline/ChevronDownIcon';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, usePage } from '@inertiajs/react';
|
||||
|
||||
function SidebarSubmenu({ submenu, name, icon }) {
|
||||
const { url } = usePage();
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (submenu.some(m => m.path === url)) {
|
||||
setIsExpanded(true);
|
||||
}
|
||||
}, [url, submenu]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full block" onClick={() => setIsExpanded(!isExpanded)}>
|
||||
{icon} {name}
|
||||
<ChevronDownIcon
|
||||
className={`w-5 h-5 mt-1 float-right delay-400 duration-500 transition-all ${isExpanded ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className={`w-full ${isExpanded ? '' : 'hidden'}`}>
|
||||
<ul className="menu menu-compact">
|
||||
{submenu.map((m, k) => (
|
||||
<li key={k}>
|
||||
<Link href={m.path}>
|
||||
{m.icon} {m.name}
|
||||
{url === m.path && (
|
||||
<span
|
||||
className="absolute mt-1 mb-1 inset-y-0 left-0 w-1 rounded-tr-md rounded-br-md bg-primary"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarSubmenu;
|
|
@ -0,0 +1,9 @@
|
|||
function SuspenseContent() {
|
||||
return (
|
||||
<div className="w-full h-screen text-gray-300 dark:text-gray-200 bg-base-100">
|
||||
Loading...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SuspenseContent
|
|
@ -0,0 +1,40 @@
|
|||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import axios from 'axios'
|
||||
import { CONFIRMATION_MODAL_CLOSE_TYPES } from '../../../../../../public/utils/globalConstantUtil'
|
||||
import { deleteLead } from '../../leads/leadSlice'
|
||||
import { showNotification } from '../headerSlice'
|
||||
|
||||
function ConfirmationModalBody({ extraObject, closeModal }) {
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { message, type, _id, index } = extraObject
|
||||
|
||||
|
||||
const proceedWithYes = async () => {
|
||||
if (type === CONFIRMATION_MODAL_CLOSE_TYPES.LEAD_DELETE) {
|
||||
// positive response, call api or dispatch redux function
|
||||
dispatch(deleteLead({ index }))
|
||||
dispatch(showNotification({ message: "Lead Deleted!", status: 1 }))
|
||||
}
|
||||
closeModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className=' text-xl mt-8 text-center'>
|
||||
{message}
|
||||
</p>
|
||||
|
||||
<div className="modal-action mt-12">
|
||||
|
||||
<button className="btn btn-outline " onClick={() => closeModal()}>Cancel</button>
|
||||
|
||||
<button className="btn btn-primary w-36" onClick={() => proceedWithYes()}>Yes</button>
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfirmationModalBody
|
|
@ -0,0 +1,15 @@
|
|||
function NotificationBodyRightDrawer(){
|
||||
return(
|
||||
<>
|
||||
{
|
||||
[...Array(15)].map((_, i) => {
|
||||
return <div key={i} className={"grid mt-3 card bg-base-200 rounded-box p-3" + (i < 5 ? " bg-blue-100" : "")}>
|
||||
{i % 2 === 0 ? `Your sales has increased by 30% yesterday` : `Total likes for instagram post - New launch this week, has crossed 100k `}
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotificationBodyRightDrawer
|
|
@ -0,0 +1,30 @@
|
|||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const headerSlice = createSlice({
|
||||
name: 'header',
|
||||
initialState: {
|
||||
pageTitle: "Home",
|
||||
noOfNotifications: 15,
|
||||
newNotificationMessage: "",
|
||||
newNotificationStatus: 1,
|
||||
},
|
||||
reducers: {
|
||||
setPageTitle: (state, action) => {
|
||||
state.pageTitle = action.payload
|
||||
},
|
||||
|
||||
|
||||
removeNotificationMessage: (state, action) => {
|
||||
state.newNotificationMessage = ""
|
||||
},
|
||||
|
||||
showNotification: (state, action) => {
|
||||
state.newNotificationMessage = action.payload.message
|
||||
state.newNotificationStatus = action.payload.status
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
export const { setPageTitle, removeNotificationMessage, showNotification } = headerSlice.actions
|
||||
|
||||
export default headerSlice.reducer
|
|
@ -0,0 +1,35 @@
|
|||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const modalSlice = createSlice({
|
||||
name: 'modal',
|
||||
initialState: {
|
||||
title: "", // current title state management
|
||||
isOpen : false, // modal state management for opening closing
|
||||
bodyType : "", // modal content management
|
||||
size : "", // modal content management
|
||||
extraObject : {},
|
||||
},
|
||||
reducers: {
|
||||
|
||||
openModal: (state, action) => {
|
||||
const {title, bodyType, extraObject, size} = action.payload
|
||||
state.isOpen = true
|
||||
state.bodyType = bodyType
|
||||
state.title = title
|
||||
state.size = size || 'md'
|
||||
state.extraObject = extraObject
|
||||
},
|
||||
|
||||
closeModal: (state, action) => {
|
||||
state.isOpen = false
|
||||
state.bodyType = ""
|
||||
state.title = ""
|
||||
state.extraObject = {}
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
export const { openModal, closeModal } = modalSlice.actions
|
||||
|
||||
export default modalSlice.reducer
|
|
@ -0,0 +1,33 @@
|
|||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const rightDrawerSlice = createSlice({
|
||||
name: 'rightDrawer',
|
||||
initialState: {
|
||||
header: "", // current title state management
|
||||
isOpen : false, // right drawer state management for opening closing
|
||||
bodyType : "", // right drawer content management
|
||||
extraObject : {},
|
||||
},
|
||||
reducers: {
|
||||
|
||||
openRightDrawer: (state, action) => {
|
||||
const {header, bodyType, extraObject} = action.payload
|
||||
state.isOpen = true
|
||||
state.bodyType = bodyType
|
||||
state.header = header
|
||||
state.extraObject = extraObject
|
||||
},
|
||||
|
||||
closeRightDrawer: (state, action) => {
|
||||
state.isOpen = false
|
||||
state.bodyType = ""
|
||||
state.header = ""
|
||||
state.extraObject = {}
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
export const { openRightDrawer, closeRightDrawer } = rightDrawerSlice.actions
|
||||
|
||||
export default rightDrawerSlice.reducer
|