payment phase 2
|
@ -37,10 +37,9 @@ public function indexManualPayment(cekDenda $cekDenda, GenerateMonthlyBill $gene
|
|||
'penalty' => $penalty,
|
||||
'bill' => $bill,
|
||||
'fields' => [
|
||||
'nis' => 'text',
|
||||
'nama' => 'text',
|
||||
'status_santri' => 'text',
|
||||
'role_santri' => 'text',
|
||||
'nis' => ['type' => 'text', 'readonly' => true],
|
||||
'nama' => ['type' => 'text', 'readonly' => true],
|
||||
'status_santri' => ['type' => 'text', 'readonly' => true],
|
||||
],
|
||||
'options' => [
|
||||
'payment_type' => $paymentType,
|
||||
|
@ -54,103 +53,118 @@ public function manualPayment(Request $request, $paymentId)
|
|||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
DB::enableQueryLog();
|
||||
|
||||
$range = $request->input('range');
|
||||
$payment = Payment::find($paymentId);
|
||||
$userId = User::findOrFail($request->id);
|
||||
$range = (int) $request->input('range');
|
||||
$userId = $request->id;
|
||||
$typeId = $request->input('type_id');
|
||||
|
||||
if (!$payment) {
|
||||
Payment::create([
|
||||
'payment_status' => 'pending',
|
||||
'amount_payment' => 0,
|
||||
'bank' => null,
|
||||
'no_va' => null,
|
||||
'expired_at' => null,
|
||||
'user_id' => $userId
|
||||
]);
|
||||
}
|
||||
$existingPayment = Payment::where('user_id', $userId)
|
||||
->where('payment_status', 'pending')
|
||||
->first();
|
||||
|
||||
$unpaidDetails = DetailPayment::where('payment_id', $paymentId)
|
||||
$unpaidDetails = $existingPayment
|
||||
? DetailPayment::where('payment_id', $existingPayment->id)
|
||||
->where('status', 'unpaid')
|
||||
->orderBy('payment_year', 'asc')
|
||||
->orderBy('payment_month', 'asc')
|
||||
->get();
|
||||
->orderBy('payment_year')
|
||||
->orderBy('payment_month')
|
||||
->get()
|
||||
: collect();
|
||||
|
||||
$jumlahUnpaid = $unpaidDetails->count();
|
||||
$totalAmount = 0;
|
||||
if ($existingPayment && $unpaidDetails->count() > 0) {
|
||||
$totalAmount = 0;
|
||||
$toPay = $unpaidDetails->take($range);
|
||||
$jumlahUnpaid = $toPay->count();
|
||||
|
||||
Log::info("Jumlah Unpaid: $jumlahUnpaid | Range: $range");
|
||||
|
||||
if ($jumlahUnpaid > 0) {
|
||||
foreach ($unpaidDetails->take($range) as $detail) {
|
||||
foreach ($toPay as $detail) {
|
||||
$nominal = PaymentType::where('id', $detail->type_id)->value('nominal') ?? 0;
|
||||
$penalty = $detail->penalty ?? 0;
|
||||
$total = $nominal + $penalty;
|
||||
|
||||
Log::info("Update DetailPayment ID {$detail->id} | Total: $total");
|
||||
|
||||
$detail->update([
|
||||
'status' => 'paid',
|
||||
'amount' => $total,
|
||||
'amount' => $nominal,
|
||||
'penalty' => $penalty,
|
||||
]);
|
||||
|
||||
$totalAmount += $total;
|
||||
}
|
||||
}
|
||||
|
||||
$sisa = max(0, $range - $jumlahUnpaid);
|
||||
dd("Sisa pembayaran baru yang perlu dibuat: $sisa");
|
||||
$sisa = $range - $jumlahUnpaid;
|
||||
$bulan = $unpaidDetails->last()?->payment_month ?? now()->month;
|
||||
$tahun = $unpaidDetails->last()?->payment_year ?? now()->year;
|
||||
|
||||
if ($sisa > 0) {
|
||||
$latestUnpaid = $unpaidDetails->last();
|
||||
$bulanTerakhir = $latestUnpaid ? $latestUnpaid->payment_month : now()->month;
|
||||
$tahunTerakhir = $latestUnpaid ? $latestUnpaid->payment_year : now()->year;
|
||||
$typeId = $latestUnpaid ? $latestUnpaid->type_id : PaymentType::first()->id;
|
||||
$nominal = PaymentType::where('id', $typeId)->value('nominal') ?? 0;
|
||||
|
||||
for ($i = 1; $i <= $sisa; $i++) {
|
||||
$bulanTerakhir++;
|
||||
if ($bulanTerakhir > 12) {
|
||||
$bulanTerakhir = 1;
|
||||
$tahunTerakhir++;
|
||||
for ($i = 0; $i < $sisa; $i++) {
|
||||
$bulan++;
|
||||
if ($bulan > 12) {
|
||||
$bulan = 1;
|
||||
$tahun++;
|
||||
}
|
||||
|
||||
$totalAmount += $nominal;
|
||||
|
||||
Log::info("Buat DetailPayment baru untuk bulan: $bulanTerakhir, tahun: $tahunTerakhir");
|
||||
$nominal = PaymentType::where('id', $typeId)->value('nominal') ?? 0;
|
||||
|
||||
DetailPayment::create([
|
||||
'payment_id' => $paymentId,
|
||||
'payment_month' => $bulanTerakhir,
|
||||
'payment_year' => $tahunTerakhir,
|
||||
'payment_id' => $existingPayment->id,
|
||||
'payment_month' => $bulan,
|
||||
'payment_year' => $tahun,
|
||||
'amount' => $nominal,
|
||||
'penalty' => 0,
|
||||
'status' => 'paid',
|
||||
'type_id' => $typeId,
|
||||
]);
|
||||
|
||||
$totalAmount += $nominal;
|
||||
}
|
||||
|
||||
$existingPayment->update([
|
||||
'amount_payment' => DetailPayment::where('payment_id', $existingPayment->id)->sum('amount'),
|
||||
'payment_status' => DetailPayment::where('payment_id', $existingPayment->id)->where('status', 'unpaid')->exists() ? 'pending' : 'success',
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
return redirect()->back()->with('success', 'Pembayaran berhasil menggunakan data existing');
|
||||
}
|
||||
|
||||
$newPayment = Payment::create([
|
||||
'payment_status' => 'success',
|
||||
'amount_payment' => 0,
|
||||
'bank' => null,
|
||||
'no_va' => null,
|
||||
'expired_at' => null,
|
||||
'user_id' => $userId,
|
||||
]);
|
||||
|
||||
$bulan = now()->month;
|
||||
$tahun = now()->year;
|
||||
$nominal = PaymentType::where('id', $typeId)->value('nominal') ?? 0;
|
||||
$totalAmount = 0;
|
||||
|
||||
for ($i = 0; $i < $range; $i++) {
|
||||
DetailPayment::create([
|
||||
'payment_id' => $newPayment->id,
|
||||
'payment_month' => $bulan,
|
||||
'payment_year' => $tahun,
|
||||
'amount' => $nominal,
|
||||
'penalty' => 0,
|
||||
'status' => 'paid',
|
||||
'type_id' => $typeId,
|
||||
]);
|
||||
|
||||
$totalAmount += $nominal;
|
||||
|
||||
$bulan++;
|
||||
if ($bulan > 12) {
|
||||
$bulan = 1;
|
||||
$tahun++;
|
||||
}
|
||||
}
|
||||
|
||||
$totalAmountFinal = DetailPayment::where('payment_id', $paymentId)->sum('amount');
|
||||
|
||||
Log::info("Total pembayaran akhir: $totalAmountFinal");
|
||||
|
||||
$payment->update([
|
||||
'amount_payment' => $totalAmountFinal,
|
||||
'payment_status' => DetailPayment::where('payment_id', $paymentId)
|
||||
->where('status', 'unpaid')
|
||||
->exists() ? 'pending' : 'success'
|
||||
$newPayment->update([
|
||||
'amount_payment' => $totalAmount,
|
||||
]);
|
||||
|
||||
Log::info("Update Payment ID $paymentId | amount_payment: $totalAmountFinal");
|
||||
|
||||
DB::commit();
|
||||
|
||||
// dd($request->all());
|
||||
|
||||
return redirect()->back()->with('success', 'Berhasil Melakukan Pembayaran');
|
||||
dd($newPayment);
|
||||
return redirect()->back()->with('success', 'Pembayaran baru berhasil dibuat');
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
return dd('Error:', $e->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,12 @@ public function index()
|
|||
'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' => ['lulus' => 'Lulus', 'aktif' => 'Aktif'],
|
||||
|
@ -46,9 +46,9 @@ public function store(Request $request)
|
|||
'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'
|
||||
], [
|
||||
|
@ -58,10 +58,10 @@ public function store(Request $request)
|
|||
'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;
|
||||
|
@ -82,13 +82,13 @@ public function store(Request $request)
|
|||
'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);
|
||||
|
||||
// dd($santri);
|
||||
return redirect()->back()->with('success', 'Data berhasil ditambahkan');
|
||||
} catch (\Throwable $th) {
|
||||
// dd($th->getMessage());
|
||||
|
@ -105,7 +105,6 @@ public function update(Request $request, $id)
|
|||
'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'
|
||||
|
@ -115,10 +114,9 @@ public function update(Request $request, $id)
|
|||
'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',
|
||||
'tanggal_lahir.required' => 'wajib mengisi tanggal lahir santri',
|
||||
'tanggal_lahir.date' => 'tanggal lahir harus dalam format tanggal yang benar',
|
||||
]);
|
||||
|
||||
try {
|
||||
|
@ -129,7 +127,6 @@ public function update(Request $request, $id)
|
|||
'nama' => $request->nama,
|
||||
'alamat' => $request->alamat,
|
||||
'status_santri' => $request->status_santri,
|
||||
'role_santri' => $request->role_santri,
|
||||
'jk' => $request->jk,
|
||||
'tanggal_lahir' => $request->tanggal_lahir,
|
||||
];
|
||||
|
|
|
@ -2,64 +2,17 @@
|
|||
|
||||
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')->get();
|
||||
// dd($wallet);
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Wallet $wallet)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(Wallet $wallet)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Wallet $wallet)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Wallet $wallet)
|
||||
{
|
||||
//
|
||||
return Inertia::render('list-admin/payment/WalletUser', compact('wallet'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ public function up(): void
|
|||
$table->string('level');
|
||||
$table->string('nama');
|
||||
$table->string('alamat');
|
||||
$table->bigInteger('no_telp');
|
||||
$table->enum('status_santri', ['lulus', 'aktif']);
|
||||
$table->enum('role_santri', ['santri', 'pengurus']);
|
||||
$table->enum('jk', ['laki laki', 'perempuan']);
|
||||
$table->date('tanggal_lahir');
|
||||
$table->string('foto')->nullable();
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ public function up(): void
|
|||
Schema::create('wallets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->float('saldo');
|
||||
$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,8 +13,9 @@ 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->decimal('amount_payment')->nullable();
|
||||
$table->String('bank')->nullable();
|
||||
$table->string('no_va')->nullable();
|
||||
$table->dateTime('expired_at')->nullable();
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -22,22 +22,22 @@ public function run(): void
|
|||
'nama' => 'Admin',
|
||||
'alamat' => 'Jl. MH. Thamrin No. 10, Ajong',
|
||||
'status_santri' => 'aktif',
|
||||
'role_santri' => 'santri',
|
||||
'jk' => 'laki laki',
|
||||
'tanggal_lahir' => '2005-08-15',
|
||||
'foto' => null
|
||||
'foto' => null,
|
||||
'no_telp' => '80989080980'
|
||||
],
|
||||
[
|
||||
'nis' => '0987654321',
|
||||
'password' => Hash::make('pitik123'),
|
||||
'level' => 1,
|
||||
'level' => 2,
|
||||
'nama' => 'Ahmad Kasim',
|
||||
'alamat' => 'Jl. Pesantren No. 10, Jakarta',
|
||||
'status_santri' => 'aktif',
|
||||
'role_santri' => 'santri',
|
||||
'jk' => 'laki laki',
|
||||
'tanggal_lahir' => '2003-08-15',
|
||||
'foto' => null
|
||||
'foto' => null,
|
||||
'no_telp' => '80989080980'
|
||||
]
|
||||
],);
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 411 B |
Before Width: | Height: | Size: 979 B |
Before Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 75 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 |
|
@ -8,7 +8,7 @@ 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 } from '@inertiajs/react'
|
||||
import { useForm, usePage } from '@inertiajs/react'
|
||||
|
||||
function Header() {
|
||||
const dispatch = useDispatch()
|
||||
|
@ -18,6 +18,8 @@ function Header() {
|
|||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light")
|
||||
);
|
||||
|
||||
const { auth } = usePage().props
|
||||
|
||||
useEffect(() => {
|
||||
themeChange(false);
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
|
@ -62,11 +64,13 @@ function Header() {
|
|||
<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={"/public/fotoSantri/no-pic.png"} alt="profile" />
|
||||
<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><Link href="/app/settings-profile">Profile Settings</Link></li>
|
||||
<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><Link href="/app/settings-billing">Bill History</Link></li>
|
||||
<div className="divider mt-0 mb-0"></div>
|
||||
<li><a href="#" onClick={logoutUser}>Logout</a></li>
|
||||
|
|
|
@ -13,15 +13,15 @@ function LeftSidebar() {
|
|||
return (
|
||||
<div className="drawer-side z-30">
|
||||
<label htmlFor="left-sidebar-drawer" className="drawer-overlay"></label>
|
||||
<ul className="menu pt-2 w-80 bg-base-100 min-h-full text-base-content">
|
||||
<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 text-xl">
|
||||
<Link href="/dashboard">GoGoSantri</Link>
|
||||
<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}>
|
||||
|
|
|
@ -28,8 +28,8 @@ const ModalInput = ({ fields, tableName, options, initialData, onClose, showPaym
|
|||
if (!selectedPayments.includes(value)) {
|
||||
setSelectedPayments([...selectedPayments, value]);
|
||||
|
||||
const nominal = options.payment_nominal?.[value] || 0;
|
||||
const penalty = options.payment_penalty?.[value] || 0;
|
||||
const nominal = parseInt(options.payment_nominal?.[value]) || 0;
|
||||
const penalty = parseInt(options.payment_penalty?.[value]) || 0;
|
||||
|
||||
setPaymentDetails({
|
||||
...paymentDetails,
|
||||
|
@ -69,177 +69,203 @@ const ModalInput = ({ fields, tableName, options, initialData, onClose, showPaym
|
|||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
// console.log("tableName:", tableName);
|
||||
e.preventDefault();
|
||||
|
||||
const formDataObj = new FormData()
|
||||
const formDataObj = new FormData();
|
||||
Object.keys(formData).forEach((key) => {
|
||||
if (key === 'foto' && !(formData[key] instanceof File)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
formDataObj.append(key, formData[key])
|
||||
})
|
||||
formDataObj.append(key, formData[key]);
|
||||
});
|
||||
|
||||
if (initialData) {
|
||||
Inertia.post(`/update${tableName}/${initialData.id}`, formDataObj, {
|
||||
forceFormData: true,
|
||||
onError: (errors) => setErrors(errors),
|
||||
onSuccess: () => {
|
||||
document.getElementById('modal_input').checked = false
|
||||
setFormData({})
|
||||
setErrors({})
|
||||
onClose({})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Inertia.post(`/add${tableName}`, formDataObj, {
|
||||
forceFormData: true,
|
||||
onError: (errors) => setErrors(errors),
|
||||
onSuccess: () => {
|
||||
document.getElementById('modal_input').checked = false
|
||||
setFormData({})
|
||||
setErrors({})
|
||||
}
|
||||
})
|
||||
if (showPayments) {
|
||||
formDataObj.append('payment_details', JSON.stringify(paymentDetails));
|
||||
}
|
||||
}
|
||||
|
||||
const url = initialData
|
||||
? `/update${tableName}/${initialData.id}`
|
||||
: `/add${tableName}`;
|
||||
|
||||
Inertia.post(url, formDataObj, {
|
||||
forceFormData: true,
|
||||
onError: (errors) => setErrors(errors),
|
||||
onSuccess: () => {
|
||||
document.getElementById('modal_input').checked = false;
|
||||
setFormData({});
|
||||
setErrors({});
|
||||
onClose({});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
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" ? (
|
||||
<input
|
||||
type="file"
|
||||
name={field}
|
||||
onChange={handleChange}
|
||||
className="file-input file-input-bordered w-full file-input-secondary"
|
||||
className="file-input file-input-info focus:outline-none focus:ring-1 ring-info w-full"
|
||||
/>
|
||||
) : type === "password" ? (
|
||||
<input
|
||||
type="password"
|
||||
name={field}
|
||||
onChange={handleChange}
|
||||
value={formData[field] || ""}
|
||||
className="input input-info focus:outline-none focus:ring-1 ring-info w-full"
|
||||
placeholder={
|
||||
initialData
|
||||
? 'Kosongkan jika tidak ingin mengubah password'
|
||||
: 'Masukkan password broh'
|
||||
}
|
||||
/>
|
||||
|
||||
) : (
|
||||
fields[field] === "password" ? (
|
||||
<input
|
||||
type="password"
|
||||
name={field}
|
||||
onChange={handleChange}
|
||||
value=""
|
||||
className="grow border-none focus:ring-0"
|
||||
placeholder="Kosongkan jika tidak ingin mengubah password"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type={fields[field]}
|
||||
name={field}
|
||||
value={formData[field] || ""}
|
||||
onChange={handleChange}
|
||||
className="grow border-none focus:ring-0"
|
||||
placeholder={`Masukkan ${field.replace("_", " ")}`}
|
||||
/>
|
||||
)
|
||||
<input
|
||||
type={type}
|
||||
name={field}
|
||||
value={formData[field] || ""}
|
||||
onChange={handleChange}
|
||||
readOnly={readOnly}
|
||||
className="input input-info focus:outline-none focus:ring-1 ring-info w-full"
|
||||
placeholder={`Masukkan ${field.replace("_", " ")}`}
|
||||
/>
|
||||
)}
|
||||
</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(([key, value]) => (
|
||||
<option key={key} value={key}>
|
||||
{value}
|
||||
</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" name="range"
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useForm, usePage } from '@inertiajs/react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||
import { Head } from '@inertiajs/react';
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { auth } = usePage().props;
|
||||
const [preview, setPreview] = useState(null);
|
||||
const id = auth.user.id
|
||||
|
||||
|
||||
const { data, setData, post, processing, errors } = useForm({
|
||||
nama: auth.user.nama || '',
|
||||
alamat: auth.user.alamat || '',
|
||||
no_telp: auth.user.no_telp || '',
|
||||
jk: auth.user.jk || 'laki laki',
|
||||
tanggal_lahir: auth.user.tanggal_lahir || '',
|
||||
password: '',
|
||||
foto: null
|
||||
});
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setPageTitle("Profile Page"));
|
||||
}, [dispatch]);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
post(route('profile.update', id));
|
||||
};
|
||||
|
||||
const handlePhotoChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setPreview(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
setData('foto', file);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-4xl mx-auto bg-base-100 shadow-sm rounded-lg overflow-hidden">
|
||||
<Head title="Profile Page" />
|
||||
|
||||
<div className="px-6 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="relative h-16 w-16 rounded-full overflow-hidden mr-4 border border-gray-200 cursor-pointer group">
|
||||
<label htmlFor="photo-upload" className="block relative h-16 w-16 rounded-full overflow-hidden border-4 border-transparent hover:border-blue-500 cursor-pointer">
|
||||
<img
|
||||
src={preview || auth.user.foto || "/fotoSantri/no-pic.png"}
|
||||
alt="Profile"
|
||||
className="h-full w-full object-cover transition duration-200"
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black opacity-0 hover:opacity-30 transition duration-200 rounded-full">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-8 w-8 text-white"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 4.5c-3.315 0-6 2.685-6 6s2.685 6 6 6 6-2.685 6-6-2.685-6-6-6zM12 10.5a2.25 2.25 0 100-4.5 2.25 2.25 0 000 4.5zm-6 5.25h12c.828 0 1.5.672 1.5 1.5v3c0 .828-.672 1.5-1.5 1.5H6c-.828 0-1.5-.672-1.5-1.5v-3c0-.828.672-1.5 1.5-1.5z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="photo-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handlePhotoChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">{auth.user.nama}</h2>
|
||||
<p className="text-sm">{auth.user.level == 1 ? 'Admin' : ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="px-6 py-4" encType='multipart/form-data'>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input label="Nama Lengkap" name="nama" value={data.nama} onChange={setData} error={errors.nama} />
|
||||
<Input label="Alamat" name="alamat" value={data.alamat} onChange={setData} error={errors.alamat} />
|
||||
<Input label="No Telepon" name="no_telp" value={data.no_telp} onChange={setData} error={errors.no_telp} />
|
||||
<Input label="Tanggal Lahir" name="tanggal_lahir" type="date" value={data.tanggal_lahir} onChange={setData} error={errors.tanggal_lahir} />
|
||||
<Select label="Jenis Kelamin" name="jk" value={data.jk} onChange={setData} options={['laki laki', 'perempuan']} error={errors.jk} />
|
||||
<Input label="Password" name="password" type="password" value={data.password} onChange={setData} error={errors.password} placeholder="Kosongkan jika tidak ingin mengganti password" />
|
||||
</div>
|
||||
<div className="mt-6 text-right">
|
||||
<button
|
||||
type='submit'
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded-md text-sm font-medium"
|
||||
disabled={processing}
|
||||
>
|
||||
Simpan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Input({ label, name, value, onChange, type = 'text', error, placeholder = '' }) {
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">{label}</label>
|
||||
<input
|
||||
type={type}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={(e) => onChange(name, e.target.value)}
|
||||
placeholder={placeholder}
|
||||
className={`w-full px-4 py-2 bg-gray-50 border ${error ? 'border-red-500' : 'border-gray-200'} rounded-md focus:outline-none`}
|
||||
/>
|
||||
{error && <div className="text-red-500 text-sm mt-1">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Select({ label, name, value, onChange, options = [], error }) {
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">{label}</label>
|
||||
<select
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={(e) => onChange(name, e.target.value)}
|
||||
className={`w-full px-4 py-2 bg-gray-50 border ${error ? 'border-red-500' : 'border-gray-200'} rounded-md focus:outline-none`}
|
||||
>
|
||||
{options.map((opt) => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
{error && <div className="text-red-500 text-sm mt-1">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -100,7 +100,7 @@ export default function ManualPayment({ santri, fields, options }) {
|
|||
</div>
|
||||
<div>
|
||||
<div className="font-bold">{item.nama}</div>
|
||||
<div className="text-sm opacity-50">{item.role_santri}</div>
|
||||
<div className="text-sm opacity-50">{item.level == 1 ? 'Admin' : 'User'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -181,7 +181,7 @@ export default function ManualPayment({ santri, fields, options }) {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{selectedSantri ? selectedPayments.map((payment, idx) =>
|
||||
{selectedPayments.map((payment, idx) =>
|
||||
payment.detail_payments.map((detail) => (
|
||||
<tr key={`${idx}-${detail.id}`}>
|
||||
<td>{detail.payment_type?.payment_type}</td>
|
||||
|
@ -195,7 +195,7 @@ export default function ManualPayment({ santri, fields, options }) {
|
|||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : 'tidak ada data pembayaran santri'}
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@ import DeleteButton from '@/Components/DeleteButton';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||
|
||||
export default function PaymentType({ tableName, paymentType, fields }) {
|
||||
export default function PaymentType({ paymentType, fields }) {
|
||||
const [selectedPaymentType, setSelectedPaymentType] = useState(null);
|
||||
const [isDeleteOpen, setDeleteOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
@ -27,15 +27,15 @@ export default function PaymentType({ tableName, paymentType, fields }) {
|
|||
}
|
||||
}, [searchTerm, paymentType]);
|
||||
|
||||
const handleSearch = (e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
};
|
||||
|
||||
const deleteModal = (item) => {
|
||||
setSelectedPaymentType(item);
|
||||
setDeleteOpen(true);
|
||||
};
|
||||
|
||||
const handleSearch = (e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Head title="Daftar Payment Type" />
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Head } from '@inertiajs/react';
|
||||
import ModalInput from '@/Components/ModalInput';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||
import { CurrencyDollarIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
|
||||
export default function ManualPayment({ wallet }) {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [filteredSantri, setFilteredSantri] = useState([]);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setPageTitle("Data Dompet Santri"));
|
||||
}, [dispatch]);
|
||||
|
||||
// console.log(santri.id)
|
||||
useEffect(() => {
|
||||
if (wallet) {
|
||||
setFilteredSantri(
|
||||
wallet.filter(item =>
|
||||
item.nama.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.nis.toString().includes(searchTerm)
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [searchTerm, wallet]);
|
||||
|
||||
const handleSearch = (e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Head title="Dompet Santri" />
|
||||
<div className="card bg-base-100 shadow-xl">
|
||||
<div className="card-body">
|
||||
<div className="flex items-center mb-6">
|
||||
<div className="bg-gradient-to-tr from-blue-400 to-blue-600 p-3 rounded-lg mr-3">
|
||||
<CurrencyDollarIcon className="h-6 w-6 text-white" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold">Data Dompet Santri</h1>
|
||||
<div className="ml-auto">
|
||||
<span className="text-gray-700">Overview</span>
|
||||
<span className="ml-2 text-gray-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="form-control">
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Cari Santri..."
|
||||
className="input input-bordered"
|
||||
value={searchTerm}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nama</th>
|
||||
<th>Nis</th>
|
||||
<th>Status</th>
|
||||
<th>Saldo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredSantri.length > 0 ? (
|
||||
filteredSantri.map((item, i) => (
|
||||
<tr key={i}>
|
||||
<td>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="avatar">
|
||||
<div className="mask mask-squircle w-12 h-12">
|
||||
<img src={`https://ui-avatars.com/api/?name=${item.nama}`} alt="Avatar" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold">{item.nama}</div>
|
||||
<div className="text-sm opacity-50">{item.level === 1 ? 'Admin' : 'Santri'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{item.nis}</td>
|
||||
<td>
|
||||
<div className={`badge ${item.status_santri === 'aktif' ? 'badge-success' : 'badge-warning'} gap-2 text-white`}>
|
||||
{item.status_santri || "Open"}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{item.wallet ? item.wallet.saldo : 'Tidak ada saldo'}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="4" className="text-center">Tidak ada data santri.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="flex justify-center mt-4">
|
||||
{wallet?.links?.map((link, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`px-4 py-2 mx-1 ${link.active ? "bg-blue-500 text-white" : "bg-gray-200"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (link.url) window.location.href = link.url;
|
||||
}}
|
||||
disabled={!link.url}
|
||||
>
|
||||
{link.label.replace('«', '«').replace('»', '»')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -102,7 +102,7 @@ export default function IndexSantri({ santri, fields, options }) {
|
|||
</div>
|
||||
<div>
|
||||
<div className="font-bold">{item.nama}</div>
|
||||
<div className="text-sm opacity-50">{item.role_santri || "Santri"}</div>
|
||||
<div className="text-sm opacity-50">{item.level == 1 ? 'Admin' : 'User'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -6,6 +6,7 @@ const Dashboard = lazy(() => import('@/Pages/protected/Dashboard'))
|
|||
const IndexSantri = lazy(() => import('@/Pages/list-admin/santri/IndexSantri'))
|
||||
const PaymentType = lazy(() => import('@/Pages/list-admin/payment/PaymentType'))
|
||||
const ManualPayment = lazy(() => import('@/Pages/list-admin/payment/ManualPayment'))
|
||||
const WalletUser = lazy(() => import('@pages/list-admin/wallet/WalletUser'))
|
||||
|
||||
console.log(route('dashboard'))
|
||||
|
||||
|
@ -26,6 +27,10 @@ const routes = [
|
|||
path: route('indexManualPayment'),
|
||||
component: ManualPayment,
|
||||
},
|
||||
{
|
||||
path: route('walletUser'),
|
||||
componenet: WalletUser
|
||||
}
|
||||
]
|
||||
|
||||
export default routes
|
||||
|
|
|
@ -47,6 +47,11 @@ const routes = [
|
|||
icon: <ArrowRightIcon className={submenuIconClasses} />,
|
||||
name: 'Data Santri'
|
||||
},
|
||||
{
|
||||
path: '/data-payment-type',
|
||||
icon: <ArrowRightIcon className={submenuIconClasses} />,
|
||||
name: 'Tipe Pembayaran'
|
||||
},
|
||||
]
|
||||
|
||||
},
|
||||
|
@ -56,16 +61,16 @@ const routes = [
|
|||
icon: <CurrencyDollarIcon className={`${iconClasses} inline`} />,
|
||||
name: 'Data Pembayaran',
|
||||
submenu: [
|
||||
{
|
||||
path: '/data-payment-type',
|
||||
icon: <ArrowRightIcon className={submenuIconClasses} />,
|
||||
name: 'Tipe Pembayaran'
|
||||
},
|
||||
{
|
||||
path: '/index-manual-payment',
|
||||
icon: <ArrowRightIcon className={submenuIconClasses} />,
|
||||
name: 'Data Pembayaran'
|
||||
},
|
||||
{
|
||||
path: '/wallet-user',
|
||||
icon: <ArrowRightIcon className={submenuIconClasses} />,
|
||||
name: 'Wallet User'
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
|||
import store from '../js/Auth/store';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
||||
const appName = import.meta.env.VITE_APP_NAME || 'GogoSantri';
|
||||
const Layout = lazy(() => import('@/Components/Layout'));
|
||||
|
||||
createInertiaApp({
|
||||
|
@ -16,7 +16,6 @@ createInertiaApp({
|
|||
resolve: (name) => {
|
||||
return resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/**.jsx'))
|
||||
.then((page) => {
|
||||
// Pastikan semua halaman pakai LayoutWrapper
|
||||
page.default.layout = (page) => <LayoutWrapper>{page}</LayoutWrapper>;
|
||||
return page;
|
||||
});
|
||||
|
@ -39,7 +38,7 @@ createInertiaApp({
|
|||
import { usePage } from '@inertiajs/react';
|
||||
|
||||
const LayoutWrapper = ({ children }) => {
|
||||
const { url } = usePage(); // Sekarang usePage() ada di dalam Inertia App
|
||||
const { url } = usePage();
|
||||
const isLoginPage = url.startsWith('/login') || url.startsWith('/auth/login');
|
||||
|
||||
return isLoginPage ? <>{children}</> : <Layout>{children}</Layout>;
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title inertia>{{ config('app.name', 'Laravel') }}</title>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
<title inertia>{{ config('app.name', 'GogoSantri') }}</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Scripts -->
|
||||
@routes
|
||||
@viteReactRefresh
|
||||
@vite(['resources/js/app.jsx', "resources/js/Pages/{$page['component']}.jsx"])
|
||||
@inertiaHead
|
||||
</head>
|
||||
|
||||
<body class="font-sans antialiased">
|
||||
@inertia
|
||||
</body>
|
||||
|
||||
<!-- Scripts -->
|
||||
@routes
|
||||
@viteReactRefresh
|
||||
@vite(['resources/js/app.jsx', "resources/js/Pages/{$page['component']}.jsx"])
|
||||
@inertiaHead
|
||||
</head>
|
||||
<body class="font-sans antialiased">
|
||||
@inertia
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\WalletController;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\SantriController;
|
||||
|
@ -9,17 +10,6 @@
|
|||
use App\Http\Controllers\HomeController;
|
||||
use Inertia\Inertia;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Web Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register web routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider within a group which
|
||||
| contains the "web" middleware group. Now create something great!
|
||||
|
|
||||
*/
|
||||
|
||||
Route::get('/', function () {
|
||||
return redirect()->route('login');
|
||||
});
|
||||
|
@ -28,17 +18,14 @@
|
|||
return redirect()->route('login');
|
||||
});
|
||||
|
||||
// Route::get('/dashboard', function () {
|
||||
// return Inertia::render('Dashboard');
|
||||
// })->middleware(['auth', 'verified'])->name('dashboard');
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
|
||||
// dashboard
|
||||
Route::get('/dashboard', [HomeController::class, 'index'])->name('dashboard');
|
||||
|
||||
// profile
|
||||
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
Route::post('/update-profile/{id}', [ProfileController::class, 'updateProfile'])->name('profile.update');
|
||||
|
||||
// santri
|
||||
Route::get('/data-santri', [SantriController::class, 'index'])->name('indexSantri');
|
||||
|
@ -56,6 +43,9 @@
|
|||
Route::get('/index-manual-payment', [PaymentController::class, 'indexManualPayment'])->name('indexManualPayment');
|
||||
Route::post('/updatepayments/{paymentId}', [PaymentController::class, 'manualPayment'])->name('manualPayment');
|
||||
|
||||
// wallet
|
||||
Route::get('/wallet-user', [WalletController::class, 'walletUser'])->name('walletUser');
|
||||
|
||||
Route::get('profile-settings', function () {
|
||||
return Inertia::render('ProfileSettings');
|
||||
});
|
||||
|
|