Merge pull request #17 from alealien666/manualPay
migrasi react router dom to inertia
|
@ -1,4 +1,4 @@
|
|||
APP_NAME=Laravel
|
||||
APP_NAME=GoGoSantri
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Dashboard');
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
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;
|
||||
|
@ -12,6 +12,7 @@
|
|||
use App\Services\GenerateMonthlyBill;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PaymentController extends Controller
|
||||
{
|
||||
|
@ -27,13 +28,12 @@ public function indexManualPayment(cekDenda $cekDenda, GenerateMonthlyBill $gene
|
|||
->get()
|
||||
->pluck('penalty', 'type_id');
|
||||
|
||||
$santri = Santri::with([
|
||||
$santri = User::with([
|
||||
'payments.detailPayments.paymentType',
|
||||
'user'
|
||||
])->get();
|
||||
])->paginate(2);
|
||||
|
||||
return Inertia::render('list-admin/payment/ManualPayment', [
|
||||
'santri' => $santri,
|
||||
'santri' => $santri->items(),
|
||||
'penalty' => $penalty,
|
||||
'bill' => $bill,
|
||||
'fields' => [
|
||||
|
@ -41,10 +41,6 @@ public function indexManualPayment(cekDenda $cekDenda, GenerateMonthlyBill $gene
|
|||
'nama' => 'text',
|
||||
'status_santri' => 'text',
|
||||
'role_santri' => 'text',
|
||||
// 'total_penalty' => 'text',
|
||||
// 'amount_payment' => 'text',
|
||||
// 'nominal' => 'text',
|
||||
// 'payment_type' => 'select',
|
||||
],
|
||||
'options' => [
|
||||
'payment_type' => $paymentType,
|
||||
|
@ -56,18 +52,24 @@ public function indexManualPayment(cekDenda $cekDenda, GenerateMonthlyBill $gene
|
|||
|
||||
public function manualPayment(Request $request, $paymentId)
|
||||
{
|
||||
// $request->validate([
|
||||
// 'range' => 'required|integer|min:1',
|
||||
// ], [
|
||||
// 'range.required' => 'Jumlah bulan pembayaran harus diisi.',
|
||||
// 'range.integer' => 'Jumlah bulan pembayaran harus berupa angka.',
|
||||
// 'range.min' => 'Minimal pembayaran adalah 1 bulan.',
|
||||
// ]);
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$range = (int) $request->input('range');
|
||||
DB::enableQueryLog();
|
||||
|
||||
$range = $request->input('range');
|
||||
$payment = Payment::find($paymentId);
|
||||
$userId = User::findOrFail($request->id);
|
||||
|
||||
if (!$payment) {
|
||||
Payment::create([
|
||||
'payment_status' => 'pending',
|
||||
'amount_payment' => 0,
|
||||
'bank' => null,
|
||||
'no_va' => null,
|
||||
'expired_at' => null,
|
||||
'user_id' => $userId
|
||||
]);
|
||||
}
|
||||
|
||||
$unpaidDetails = DetailPayment::where('payment_id', $paymentId)
|
||||
->where('status', 'unpaid')
|
||||
|
@ -78,31 +80,35 @@ public function manualPayment(Request $request, $paymentId)
|
|||
$jumlahUnpaid = $unpaidDetails->count();
|
||||
$totalAmount = 0;
|
||||
|
||||
if ($jumlahUnpaid >= $range) {
|
||||
Log::info("Jumlah Unpaid: $jumlahUnpaid | Range: $range");
|
||||
|
||||
if ($jumlahUnpaid > 0) {
|
||||
foreach ($unpaidDetails->take($range) as $detail) {
|
||||
$total = $detail->amount + $detail->penalty;
|
||||
$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'
|
||||
]);
|
||||
$totalAmount += $total;
|
||||
}
|
||||
} else {
|
||||
foreach ($unpaidDetails as $detail) {
|
||||
$total = $detail->amount + $detail->penalty;
|
||||
$detail->update([
|
||||
'status' => 'paid'
|
||||
'status' => 'paid',
|
||||
'amount' => $total,
|
||||
'penalty' => $penalty,
|
||||
]);
|
||||
|
||||
$totalAmount += $total;
|
||||
}
|
||||
}
|
||||
|
||||
$sisa = $range - $jumlahUnpaid;
|
||||
$sisa = max(0, $range - $jumlahUnpaid);
|
||||
dd("Sisa pembayaran baru yang perlu dibuat: $sisa");
|
||||
|
||||
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');
|
||||
$nominal = PaymentType::where('id', $typeId)->value('nominal') ?? 0;
|
||||
|
||||
for ($i = 1; $i <= $sisa; $i++) {
|
||||
$bulanTerakhir++;
|
||||
|
@ -111,33 +117,43 @@ public function manualPayment(Request $request, $paymentId)
|
|||
$tahunTerakhir++;
|
||||
}
|
||||
|
||||
$penalty = 0;
|
||||
$totalAmount += $nominal + $penalty;
|
||||
$totalAmount += $nominal;
|
||||
|
||||
Log::info("Buat DetailPayment baru untuk bulan: $bulanTerakhir, tahun: $tahunTerakhir");
|
||||
|
||||
DetailPayment::create([
|
||||
'payment_id' => $paymentId,
|
||||
'payment_month' => $bulanTerakhir,
|
||||
'payment_year' => $tahunTerakhir,
|
||||
'amount' => $nominal,
|
||||
'penalty' => $penalty,
|
||||
'penalty' => 0,
|
||||
'status' => 'paid',
|
||||
'type_id' => $typeId
|
||||
'type_id' => $typeId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$totalAmountFinal = DetailPayment::where('payment_id', $paymentId)->sum('amount');
|
||||
|
||||
Log::info("Total pembayaran akhir: $totalAmountFinal");
|
||||
|
||||
$payment->update([
|
||||
'amount_payment' => $totalAmount
|
||||
'amount_payment' => $totalAmountFinal,
|
||||
'payment_status' => DetailPayment::where('payment_id', $paymentId)
|
||||
->where('status', 'unpaid')
|
||||
->exists() ? 'pending' : 'success'
|
||||
]);
|
||||
|
||||
Log::info("Update Payment ID $paymentId | amount_payment: $totalAmountFinal");
|
||||
|
||||
DB::commit();
|
||||
|
||||
// return redirect()->back()->with('success', 'Berhasil Melakukan Pembayaran');
|
||||
// return $request->all();
|
||||
return $payment;
|
||||
// dd($request->all());
|
||||
|
||||
return redirect()->back()->with('success', 'Berhasil Melakukan Pembayaran');
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
// return redirect()->back()->with('error', $e->getMessage());
|
||||
return $e->getMessage();
|
||||
return dd('Error:', $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,14 +26,17 @@ 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');
|
||||
|
|
|
@ -2,32 +2,37 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Santri;
|
||||
use App\Models\User;
|
||||
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::all();
|
||||
return Inertia::render('list-admin/santri/IndexSantri', [
|
||||
'santri' => $santri,
|
||||
'fields' => [
|
||||
'nama' => 'text',
|
||||
'nis' => 'text',
|
||||
'password' => 'password',
|
||||
'alamat' => 'text',
|
||||
'status_santri' => 'select',
|
||||
'role_santri' => 'select',
|
||||
'jk' => 'select',
|
||||
'level' => 'select',
|
||||
'tanggal_lahir' => 'date',
|
||||
'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,14 +40,21 @@ 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',
|
||||
|
||||
'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',
|
||||
|
@ -63,7 +75,10 @@ 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,
|
||||
|
@ -84,6 +99,9 @@ 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',
|
||||
|
@ -92,18 +110,22 @@ public function update(Request $request, $id)
|
|||
'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',
|
||||
'tanggal_lahir.required' => 'wajib mengisi tanggal lahir santri',
|
||||
'tanggal_lahir.date' => 'tanggal lahir harus dalam format tanggal yang benar',
|
||||
'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',
|
||||
]);
|
||||
|
||||
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,
|
||||
|
@ -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));
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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->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();
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ public function up(): void
|
|||
{
|
||||
Schema::create('wallets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('santri_id')->constrained('santris')->onDelete('cascade');
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->float('saldo');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ public function up(): void
|
|||
$table->String('bank')->nullable();
|
||||
$table->string('no_va')->nullable();
|
||||
$table->dateTime('expired_at')->nullable();
|
||||
$table->foreignId('santri_id')->constrained('santris')->onDelete('cascade');
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->foreignId('wallet_id')->nullable()->constrained('wallets')->onDelete('cascade');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
'role_santri' => 'santri',
|
||||
'jk' => 'laki laki',
|
||||
'tanggal_lahir' => '2005-08-15',
|
||||
'foto' => null
|
||||
],
|
||||
[
|
||||
'nis' => '0987654321',
|
||||
'password' => Hash::make('pitik123'),
|
||||
'level' => 1,
|
||||
'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
|
||||
]
|
||||
],);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,16 @@
|
|||
"dependencies": {
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@inertiajs/inertia": "^0.11.1",
|
||||
"@inertiajs/react": "^2.0.4",
|
||||
"@reduxjs/toolkit": "^2.6.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.2.0",
|
||||
"react-tailwindcss-datepicker": "^2.0.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",
|
||||
|
@ -686,14 +687,12 @@
|
|||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
|
@ -716,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": {
|
||||
|
@ -793,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",
|
||||
|
@ -941,11 +945,6 @@
|
|||
"@babel/types": "^7.20.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
|
||||
},
|
||||
"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",
|
||||
|
@ -1022,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",
|
||||
|
@ -1066,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",
|
||||
|
@ -1200,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",
|
||||
|
@ -1264,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"
|
||||
},
|
||||
|
@ -1287,14 +1295,6 @@
|
|||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
|
@ -1359,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",
|
||||
|
@ -1388,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"
|
||||
}
|
||||
|
@ -1467,7 +1472,6 @@
|
|||
"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==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
|
@ -1618,7 +1622,6 @@
|
|||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
|
@ -1775,7 +1778,6 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
|
@ -1969,8 +1971,7 @@
|
|||
"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",
|
||||
|
@ -2026,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"
|
||||
}
|
||||
|
@ -2035,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"
|
||||
},
|
||||
|
@ -2135,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",
|
||||
|
@ -2395,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",
|
||||
|
@ -2443,10 +2435,20 @@
|
|||
"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,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
|
@ -2486,42 +2488,13 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.2.0.tgz",
|
||||
"integrity": "sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"turbo-stream": "2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.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": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.2.0.tgz",
|
||||
"integrity": "sha512-cU7lTxETGtQRQbafJubvZKHEn5izNABxZhBY0Jlzdv0gqQhCPQt2J8aN5ZPjS6mQOXn5NnirWNh+FpE8TTYN0Q==",
|
||||
"dependencies": {
|
||||
"react-router": "7.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
"dayjs": "^1.11.12",
|
||||
"react": "^17.0.2 || ^18.2.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
|
@ -2636,6 +2609,7 @@
|
|||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
|
@ -2649,11 +2623,6 @@
|
|||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
@ -2975,11 +2944,6 @@
|
|||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/turbo-stream": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
||||
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
|
||||
|
|
|
@ -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,16 +14,17 @@
|
|||
"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": {
|
||||
"@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-router-dom": "^7.2.0",
|
||||
"react-tailwindcss-datepicker": "^2.0.0",
|
||||
"theme-change": "^2.5.0",
|
||||
"web-vitals": "^4.2.4"
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 160 KiB |
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: 4.4 KiB |
|
@ -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,146 +0,0 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import ChevronLeftIcon from "@heroicons/react/24/solid/ChevronLeftIcon";
|
||||
import ChevronRightIcon from "@heroicons/react/24/solid/ChevronRightIcon";
|
||||
import moment from "moment";
|
||||
import { CALENDAR_EVENT_STYLE } from "./util";
|
||||
|
||||
const THEME_BG = CALENDAR_EVENT_STYLE
|
||||
|
||||
function CalendarView({calendarEvents, addNewEvent, openDayDetail}){
|
||||
|
||||
const today = moment().startOf('day')
|
||||
const weekdays = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
||||
const colStartClasses = [
|
||||
"",
|
||||
"col-start-2",
|
||||
"col-start-3",
|
||||
"col-start-4",
|
||||
"col-start-5",
|
||||
"col-start-6",
|
||||
"col-start-7",
|
||||
];
|
||||
|
||||
const [firstDayOfMonth, setFirstDayOfMonth] = useState(moment().startOf('month'))
|
||||
const [events, setEvents] = useState([])
|
||||
const [currMonth, setCurrMonth] = useState(() => moment(today).format("MMM-yyyy"));
|
||||
|
||||
useEffect(() => {
|
||||
setEvents(calendarEvents)
|
||||
}, [calendarEvents])
|
||||
|
||||
|
||||
const allDaysInMonth = ()=> {
|
||||
let start = moment(firstDayOfMonth).startOf('week')
|
||||
let end = moment(moment(firstDayOfMonth).endOf('month')).endOf('week')
|
||||
var days = [];
|
||||
var day = start;
|
||||
while (day <= end) {
|
||||
days.push(day.toDate());
|
||||
day = day.clone().add(1, 'd');
|
||||
}
|
||||
return days
|
||||
}
|
||||
|
||||
const getEventsForCurrentDate = (date) => {
|
||||
let filteredEvents = events.filter((e) => {return moment(date).isSame(moment(e.startTime), 'day') } )
|
||||
if(filteredEvents.length > 2){
|
||||
let originalLength = filteredEvents.length
|
||||
filteredEvents = filteredEvents.slice(0, 2)
|
||||
filteredEvents.push({title : `${originalLength - 2} more`, theme : "MORE"})
|
||||
}
|
||||
return filteredEvents
|
||||
}
|
||||
|
||||
const openAllEventsDetail = (date, theme) => {
|
||||
if(theme != "MORE")return 1
|
||||
let filteredEvents = events.filter((e) => {return moment(date).isSame(moment(e.startTime), 'day') } ).map((e) => {return {title : e.title, theme : e.theme}})
|
||||
openDayDetail({filteredEvents, title : moment(date).format("D MMM YYYY")})
|
||||
}
|
||||
|
||||
const isToday = (date) => {
|
||||
return moment(date).isSame(moment(), 'day');
|
||||
}
|
||||
|
||||
const isDifferentMonth = (date) => {
|
||||
return moment(date).month() != moment(firstDayOfMonth).month()
|
||||
}
|
||||
|
||||
const getPrevMonth = (event) => {
|
||||
const firstDayOfPrevMonth = moment(firstDayOfMonth).add(-1, 'M').startOf('month');
|
||||
setFirstDayOfMonth(firstDayOfPrevMonth)
|
||||
setCurrMonth(moment(firstDayOfPrevMonth).format("MMM-yyyy"));
|
||||
};
|
||||
|
||||
const getCurrentMonth = (event) => {
|
||||
const firstDayOfCurrMonth = moment().startOf('month');
|
||||
setFirstDayOfMonth(firstDayOfCurrMonth)
|
||||
setCurrMonth(moment(firstDayOfCurrMonth).format("MMM-yyyy"));
|
||||
};
|
||||
|
||||
const getNextMonth = (event) => {
|
||||
const firstDayOfNextMonth = moment(firstDayOfMonth).add(1, 'M').startOf('month');
|
||||
setFirstDayOfMonth(firstDayOfNextMonth)
|
||||
setCurrMonth(moment(firstDayOfNextMonth).format("MMM-yyyy"));
|
||||
};
|
||||
|
||||
return(
|
||||
<>
|
||||
<div className="w-full bg-base-100 p-4 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex justify-normal gap-2 sm:gap-4">
|
||||
<p className="font-semibold text-xl w-48">
|
||||
{moment(firstDayOfMonth).format("MMMM yyyy").toString()}<span className="text-xs ml-2 ">Beta</span>
|
||||
</p>
|
||||
|
||||
<button className="btn btn-square btn-sm btn-ghost" onClick={getPrevMonth}><ChevronLeftIcon
|
||||
className="w-5 h-5"
|
||||
|
||||
/></button>
|
||||
<button className="btn btn-sm btn-ghost normal-case" onClick={getCurrentMonth}>
|
||||
|
||||
Current Month</button>
|
||||
<button className="btn btn-square btn-sm btn-ghost" onClick={getNextMonth}><ChevronRightIcon
|
||||
className="w-5 h-5"
|
||||
|
||||
/></button>
|
||||
</div>
|
||||
<div>
|
||||
<button className="btn btn-sm btn-ghost btn-outline normal-case" onClick={addNewEvent}>Add New Event</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="my-4 divider" />
|
||||
<div className="grid grid-cols-7 gap-6 sm:gap-12 place-items-center">
|
||||
{weekdays.map((day, key) => {
|
||||
return (
|
||||
<div className="text-xs capitalize" key={key}>
|
||||
{day}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="grid grid-cols-7 mt-1 place-items-center">
|
||||
{allDaysInMonth().map((day, idx) => {
|
||||
return (
|
||||
<div key={idx} className={colStartClasses[moment(day).day().toString()] + " border border-solid w-full h-28 "}>
|
||||
<p className={`inline-block flex items-center justify-center h-8 w-8 rounded-full mx-1 mt-1 text-sm cursor-pointer hover:bg-base-300 ${isToday(day) && " bg-blue-100 dark:bg-blue-400 dark:hover:bg-base-300 dark:text-white"} ${isDifferentMonth(day) && " text-slate-400 dark:text-slate-600"}`} onClick={() => addNewEvent(day)}> { moment(day).format("D") }</p>
|
||||
{
|
||||
getEventsForCurrentDate(day).map((e, k) => {
|
||||
return <p key={k} onClick={() => openAllEventsDetail(day, e.theme)} className={`text-xs px-2 mt-1 truncate ${THEME_BG[e.theme] || ""}`}>{e.title}</p>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default CalendarView
|
|
@ -1,10 +0,0 @@
|
|||
const CALENDAR_EVENT_STYLE = Object.freeze({
|
||||
BLUE: "bg-blue-200 dark:bg-blue-600 dark:text-blue-100",
|
||||
GREEN: "bg-green-200 dark:bg-green-600 dark:text-green-100",
|
||||
PURPLE: "bg-purple-200 dark:bg-purple-600 dark:text-purple-100",
|
||||
ORANGE: "bg-orange-200 dark:bg-orange-600 dark:text-orange-100",
|
||||
PINK: "bg-pink-200 dark:bg-pink-600 dark:text-pink-100",
|
||||
MORE: "hover:underline cursor-pointer font-medium"
|
||||
});
|
||||
|
||||
export { CALENDAR_EVENT_STYLE };
|
|
@ -1,30 +0,0 @@
|
|||
import Subtitle from "../Typography/Subtitle"
|
||||
|
||||
|
||||
function TitleCard({title, children, topMargin, TopSideButtons}){
|
||||
return(
|
||||
<div className={"card w-full p-6 bg-base-100 shadow-xl " + (topMargin || "mt-6")}>
|
||||
|
||||
{/* Title for Card */}
|
||||
<Subtitle styleClass={TopSideButtons ? "inline-block" : ""}>
|
||||
{title}
|
||||
|
||||
{/* Top side button, show only if present */}
|
||||
{
|
||||
TopSideButtons && <div className="inline-block float-right">{TopSideButtons}</div>
|
||||
}
|
||||
</Subtitle>
|
||||
|
||||
<div className="divider mt-2"></div>
|
||||
|
||||
{/** Card Body */}
|
||||
<div className='h-full w-full pb-6 bg-base-100'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default TitleCard
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -7,7 +7,8 @@ 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 'react-router-dom'
|
||||
import { Link } from '@inertiajs/react'
|
||||
import { useForm } from '@inertiajs/react'
|
||||
|
||||
function Header() {
|
||||
const dispatch = useDispatch()
|
||||
|
@ -31,9 +32,10 @@ function Header() {
|
|||
dispatch(openRightDrawer({ header: "Notifications", bodyType: RIGHT_DRAWER_TYPES.NOTIFICATION }))
|
||||
}
|
||||
|
||||
function logoutUser() {
|
||||
localStorage.clear();
|
||||
window.location.href = '/'
|
||||
const { post } = useForm()
|
||||
const logoutUser = (e) => {
|
||||
e.preventDefault()
|
||||
post('/logout')
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -60,14 +62,14 @@ 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="https://placeimg.com/80/80/people" alt="profile" />
|
||||
<img src={"/public/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 to={'/app/settings-profile'}>Profile Settings</Link></li>
|
||||
<li><Link to={'/app/settings-billing'}>Bill History</Link></li>
|
||||
<li><Link href="/app/settings-profile">Profile Settings</Link></li>
|
||||
<li><Link href="/app/settings-billing">Bill History</Link></li>
|
||||
<div className="divider mt-0 mb-0"></div>
|
||||
<li><a onClick={logoutUser}>Logout</a></li>
|
||||
<li><a href="#" onClick={logoutUser}>Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -1,41 +1,33 @@
|
|||
import PageContent from "./PageContent"
|
||||
import LeftSidebar from "./LeftSidebar"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import RightSidebar from './RightSidebar'
|
||||
import { useEffect } from "react"
|
||||
import { removeNotificationMessage } from "@/Components/features/common/headerSlice"
|
||||
import ModalLayout from "./ModalLayout"
|
||||
import { themeChange } from 'theme-change';
|
||||
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 { newNotificationMessage, newNotificationStatus } = useSelector(state => state.header)
|
||||
const dispatch = useDispatch();
|
||||
const { url } = usePage(); // Gunakan usePage() dari Inertia
|
||||
const [showSidebar, setShowSidebar] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
themeChange(false)
|
||||
}, [])
|
||||
console.log("Route berubah (Inertia):", url);
|
||||
setShowSidebar(!url.startsWith("/login")); // Sidebar disembunyikan di halaman login
|
||||
}, [url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (newNotificationMessage !== "") {
|
||||
if (newNotificationStatus === 1) NotificationManager.success(newNotificationMessage, 'Success')
|
||||
if (newNotificationStatus === 0) NotificationManager.error(newNotificationMessage, 'Error')
|
||||
dispatch(removeNotificationMessage())
|
||||
}
|
||||
}, [newNotificationMessage])
|
||||
themeChange(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="drawer lg:drawer-open">
|
||||
<input id="left-sidebar-drawer" type="checkbox" className="drawer-toggle" />
|
||||
<PageContent>{children}</PageContent>
|
||||
<LeftSidebar />
|
||||
</div>
|
||||
|
||||
<RightSidebar />
|
||||
<ModalLayout />
|
||||
|
||||
</>
|
||||
)
|
||||
<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
|
||||
export default Layout;
|
||||
|
||||
|
|
|
@ -1,53 +1,52 @@
|
|||
import routes from '../Routes/sidebar'
|
||||
import { NavLink, Routes, Link, useLocation } from 'react-router-dom'
|
||||
import { Link, usePage } from '@inertiajs/react';
|
||||
import SidebarSubmenu from './SidebarSubmenu';
|
||||
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon'
|
||||
import { useDispatch } from 'react-redux';
|
||||
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
||||
import routes from '../Routes/sidebar';
|
||||
|
||||
function LeftSidebar() {
|
||||
const location = useLocation();
|
||||
const { url } = usePage();
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const close = (e) => {
|
||||
document.getElementById('left-sidebar-drawer').click()
|
||||
}
|
||||
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-80 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 inline-block w-5" />
|
||||
<ul className="menu pt-2 w-80 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 to={'/app/welcome'}>DashWind</Link> </li>
|
||||
{
|
||||
routes.map((route, k) => {
|
||||
return (
|
||||
<li className="" key={k}>
|
||||
{
|
||||
route.submenu ?
|
||||
<SidebarSubmenu {...route} /> :
|
||||
(<NavLink
|
||||
end
|
||||
to={route.path}
|
||||
className={({ isActive }) => `${isActive ? 'font-semibold bg-base-200 ' : 'font-normal'}`} >
|
||||
{route.icon} {route.name}
|
||||
{
|
||||
location.pathname === route.path ? (<span className="absolute inset-y-0 left-0 w-1 rounded-tr-md rounded-br-md bg-primary "
|
||||
aria-hidden="true"></span>) : null
|
||||
}
|
||||
</NavLink>)
|
||||
}
|
||||
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
<Link href="/dashboard">GoGoSantri</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
|
||||
export default LeftSidebar;
|
||||
|
|
|
@ -22,6 +22,7 @@ const ModalInput = ({ fields, tableName, options, initialData, onClose, showPaym
|
|||
}
|
||||
};
|
||||
|
||||
// console.log(initialData)
|
||||
const handlePaymentChange = (e) => {
|
||||
const value = e.target.value;
|
||||
if (!selectedPayments.includes(value)) {
|
||||
|
@ -68,36 +69,40 @@ const ModalInput = ({ fields, tableName, options, initialData, onClose, showPaym
|
|||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const formDataObj = new FormData();
|
||||
e.preventDefault()
|
||||
// console.log("tableName:", tableName);
|
||||
|
||||
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));
|
||||
|
||||
const url = initialData ? `/update${tableName}/${initialData.id}` : `/add${tableName}`;
|
||||
if (!initialData?.id) {
|
||||
// console.error("Error: initialData.id tidak ditemukan");
|
||||
return;
|
||||
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({})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// console.log("URL yang dikirim:", url);
|
||||
// console.log("Data yang dikirim:", formDataObj);
|
||||
|
||||
Inertia.post(url, formDataObj, {
|
||||
forceFormData: true,
|
||||
onError: (errors) => setErrors(errors),
|
||||
onSuccess: () => {
|
||||
document.getElementById("modal_input").checked = false;
|
||||
setFormData({});
|
||||
setErrors({});
|
||||
if (onClose) onClose({});
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -134,14 +139,25 @@ const ModalInput = ({ fields, tableName, options, initialData, onClose, showPaym
|
|||
className="file-input file-input-bordered w-full file-input-secondary"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type={fields[field]}
|
||||
name={field}
|
||||
value={formData[field] || ""}
|
||||
onChange={handleChange}
|
||||
className="grow border-none focus:ring-0"
|
||||
placeholder={`Masukkan ${field.replace("_", " ")}`}
|
||||
/>
|
||||
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("_", " ")}`}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</label>
|
||||
{errors[field] && <p className="text-red-500 text-sm">{errors[field]}</p>}
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
import { useEffect } from 'react'
|
||||
import { MODAL_BODY_TYPES } from '../../../public/utils/globalConstantUtil'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { closeModal } from '@/Components/features/common/modalSlice'
|
||||
import AddLeadModalBody from '@/Components/features/leads/components/AddLeadModalBody'
|
||||
import ConfirmationModalBody from '@/Components/features/common/components/ConfirmationModalBody'
|
||||
|
||||
|
||||
function ModalLayout() {
|
||||
|
||||
|
||||
const { isOpen, bodyType, size, extraObject, title } = useSelector(state => state.modal)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const close = (e) => {
|
||||
dispatch(closeModal(e))
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* The button to open modal */}
|
||||
|
||||
{/* Put this part before </body> tag */}
|
||||
<div className={`modal ${isOpen ? "modal-open" : ""}`}>
|
||||
<div className={`modal-box ${size === 'lg' ? 'max-w-5xl' : ''}`}>
|
||||
<button className="btn btn-sm btn-circle absolute right-2 top-2" onClick={() => close()}>✕</button>
|
||||
<h3 className="font-semibold text-2xl pb-6 text-center">{title}</h3>
|
||||
|
||||
|
||||
{/* Loading modal body according to different modal type */}
|
||||
{
|
||||
{
|
||||
[MODAL_BODY_TYPES.LEAD_ADD_NEW]: <AddLeadModalBody closeModal={close} extraObject={extraObject} />,
|
||||
[MODAL_BODY_TYPES.CONFIRMATION]: <ConfirmationModalBody extraObject={extraObject} closeModal={close} />,
|
||||
[MODAL_BODY_TYPES.DEFAULT]: <div></div>
|
||||
}[bodyType]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalLayout
|
|
@ -1,55 +1,40 @@
|
|||
import Header from "./Header"
|
||||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
|
||||
import routes from '@/Routes'
|
||||
import { Suspense, lazy } from 'react'
|
||||
import SuspenseContent from "./SuspenseContent"
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
// const Page404 = lazy(() => import('@/Pages/protected/404'))
|
||||
|
||||
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";
|
||||
|
||||
function PageContent({ children }) {
|
||||
const mainContentRef = useRef(null);
|
||||
const { pageTitle } = useSelector(state => state.header)
|
||||
const { pageTitle } = useSelector(state => state.header);
|
||||
const { url } = usePage(); // Ambil URL dari Inertia
|
||||
const [isLoginPage, setIsLoginPage] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
mainContentRef.current.scroll({
|
||||
mainContentRef.current?.scroll({
|
||||
top: 0,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}, [pageTitle])
|
||||
}, [pageTitle]);
|
||||
|
||||
// Update state saat route berubah
|
||||
useEffect(() => {
|
||||
setIsLoginPage(url === "/login");
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<div className="drawer-content flex flex-col ">
|
||||
<Header />
|
||||
<main className="flex-1 overflow-y-auto md:pt-4 pt-4 px-6 bg-base-200" ref={mainContentRef}>
|
||||
<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 />}>
|
||||
<Routes>
|
||||
{
|
||||
routes.map((route, key) => {
|
||||
return (
|
||||
<Route
|
||||
key={key}
|
||||
exact={true}
|
||||
path={`${route.path}`}
|
||||
element={<route.component />}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
{/* Redirecting unknown url to 404 page */}
|
||||
{/* <Route path="*" element={<Page404 />} /> */}
|
||||
</Routes>
|
||||
<div className="min-h-screen">
|
||||
{children}
|
||||
</div>
|
||||
</Suspense>
|
||||
<div className="min-h-screen">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default PageContent;
|
||||
|
||||
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,60 +0,0 @@
|
|||
import XMarkIcon from '@heroicons/react/24/solid/XMarkIcon'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import NotificationBodyRightDrawer from '@/Components/features/common/components/NotificationBodyRightDrawer'
|
||||
import { closeRightDrawer } from '@/Components/features/common/rightDrawerSlice'
|
||||
import { RIGHT_DRAWER_TYPES } from '../.../../../../public/utils/globalConstantUtil'
|
||||
import CalendarEventsBodyRightDrawer from '@/Components/features/calendar/CalendarEventsBodyRightDrawer'
|
||||
|
||||
|
||||
function RightSidebar() {
|
||||
|
||||
const { isOpen, bodyType, extraObject, header } = useSelector(state => state.rightDrawer)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const close = (e) => {
|
||||
dispatch(closeRightDrawer(e))
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className={" fixed overflow-hidden z-20 bg-gray-900 bg-opacity-25 inset-0 transform ease-in-out " + (isOpen ? " transition-opacity opacity-100 duration-500 translate-x-0 " : " transition-all delay-500 opacity-0 translate-x-full ")}>
|
||||
|
||||
<section className={"w-80 md:w-96 right-0 absolute bg-base-100 h-full shadow-xl delay-400 duration-500 ease-in-out transition-all transform " + (isOpen ? " translate-x-0 " : " translate-x-full ")}>
|
||||
|
||||
<div className="relative pb-5 flex flex-col h-full">
|
||||
|
||||
{/* Header */}
|
||||
<div className="navbar flex pl-4 pr-4 shadow-md ">
|
||||
<button className="float-left btn btn-circle btn-outline btn-sm" onClick={() => close()}>
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<span className="ml-2 font-bold text-xl">{header}</span>
|
||||
</div>
|
||||
|
||||
|
||||
{/* ------------------ Content Start ------------------ */}
|
||||
<div className="overflow-y-scroll pl-4 pr-4">
|
||||
<div className="flex flex-col w-full">
|
||||
{/* Loading drawer body according to different drawer type */}
|
||||
{
|
||||
{
|
||||
[RIGHT_DRAWER_TYPES.NOTIFICATION]: <NotificationBodyRightDrawer {...extraObject} closeRightDrawer={close} />,
|
||||
[RIGHT_DRAWER_TYPES.CALENDAR_EVENTS]: <CalendarEventsBodyRightDrawer {...extraObject} closeRightDrawer={close} />,
|
||||
[RIGHT_DRAWER_TYPES.DEFAULT]: <div></div>
|
||||
}[bodyType]
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{/* ------------------ Content End ------------------ */}
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<section className=" w-screen h-full cursor-pointer " onClick={() => close()} ></section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RightSidebar
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -1,49 +1,45 @@
|
|||
import ChevronDownIcon from '@heroicons/react/24/outline/ChevronDownIcon'
|
||||
import {useEffect, useState} from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
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);
|
||||
|
||||
function SidebarSubmenu({submenu, name, icon}){
|
||||
const location = useLocation()
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
|
||||
/** Open Submenu list if path found in routes, this is for directly loading submenu routes first time */
|
||||
useEffect(() => {
|
||||
if(submenu.filter(m => {return m.path === location.pathname})[0])setIsExpanded(true)
|
||||
}, [])
|
||||
if (submenu.some(m => m.path === url)) {
|
||||
setIsExpanded(true);
|
||||
}
|
||||
}, [url, submenu]);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
|
||||
{/** Route header */}
|
||||
<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 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>
|
||||
|
||||
{/** Submenu list */}
|
||||
<div className={` w-full `+ (isExpanded ? "" : "hidden")}>
|
||||
<ul className={`menu menu-compact`}>
|
||||
{
|
||||
submenu.map((m, k) => {
|
||||
return(
|
||||
<li key={k}>
|
||||
<Link to={m.path}>
|
||||
{m.icon} {m.name}
|
||||
{
|
||||
location.pathname == 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>) : null
|
||||
}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
<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
|
||||
export default SidebarSubmenu;
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
function ErrorText({styleClass, children}){
|
||||
return(
|
||||
<p className={`text-center text-error ${styleClass}`}>{children}</p>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorText
|
|
@ -1,7 +0,0 @@
|
|||
function HelperText({className, children}){
|
||||
return(
|
||||
<div className={`text-slate-400 ${className}`}>{children}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HelperText
|
|
@ -1,7 +0,0 @@
|
|||
function Subtitle({styleClass, children}){
|
||||
return(
|
||||
<div className={`text-xl font-semibold ${styleClass}`}>{children}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Subtitle
|
|
@ -1,7 +0,0 @@
|
|||
function Title({className, children}){
|
||||
return(
|
||||
<p className={`text-2xl font-bold ${className}`}>{children}</p>
|
||||
)
|
||||
}
|
||||
|
||||
export default Title
|
|
@ -1,19 +0,0 @@
|
|||
import { CALENDAR_EVENT_STYLE } from "@/Components/CalendarView/util"
|
||||
|
||||
const THEME_BG = CALENDAR_EVENT_STYLE
|
||||
|
||||
function CalendarEventsBodyRightDrawer({ filteredEvents }) {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
filteredEvents.map((e, k) => {
|
||||
return <div key={k} className={`grid mt-3 card rounded-box p-3 ${THEME_BG[e.theme] || ""}`}>
|
||||
{e.title}
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CalendarEventsBodyRightDrawer
|
|
@ -1,45 +0,0 @@
|
|||
import { useState } from 'react'
|
||||
import CalendarView from '../../components/CalendarView'
|
||||
import moment from 'moment'
|
||||
import { CALENDAR_INITIAL_EVENTS } from '../../utils/dummyData'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { openRightDrawer } from '../common/rightDrawerSlice'
|
||||
import { RIGHT_DRAWER_TYPES } from '../../utils/globalConstantUtil'
|
||||
import { showNotification } from '../common/headerSlice'
|
||||
|
||||
|
||||
|
||||
const INITIAL_EVENTS = CALENDAR_INITIAL_EVENTS
|
||||
|
||||
function Calendar(){
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [events, setEvents] = useState(INITIAL_EVENTS)
|
||||
|
||||
// Add your own Add Event handler, like opening modal or random event addition
|
||||
// Format - {title :"", theme: "", startTime : "", endTime : ""}, typescript version comming soon :)
|
||||
const addNewEvent = (date) => {
|
||||
let randomEvent = INITIAL_EVENTS[Math.floor(Math.random() * 10)]
|
||||
let newEventObj = {title : randomEvent.title, theme : randomEvent.theme, startTime : moment(date).startOf('day'), endTime : moment(date).endOf('day')}
|
||||
setEvents([...events, newEventObj])
|
||||
dispatch(showNotification({message : "New Event Added!", status : 1}))
|
||||
}
|
||||
|
||||
// Open all events of current day in sidebar
|
||||
const openDayDetail = ({filteredEvents, title}) => {
|
||||
dispatch(openRightDrawer({header : title, bodyType : RIGHT_DRAWER_TYPES.CALENDAR_EVENTS, extraObject : {filteredEvents}}))
|
||||
}
|
||||
|
||||
return(
|
||||
<>
|
||||
<CalendarView
|
||||
calendarEvents={events}
|
||||
addNewEvent={addNewEvent}
|
||||
openDayDetail={openDayDetail}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Calendar
|
|
@ -1,53 +0,0 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import TitleCard from '../../../components/Cards/TitleCard';
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
function BarChart(){
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Store 1',
|
||||
data: labels.map(() => { return Math.random() * 1000 + 500 }),
|
||||
backgroundColor: 'rgba(255, 99, 132, 1)',
|
||||
},
|
||||
{
|
||||
label: 'Store 2',
|
||||
data: labels.map(() => { return Math.random() * 1000 + 500 }),
|
||||
backgroundColor: 'rgba(53, 162, 235, 1)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return(
|
||||
<TitleCard title={"No of Orders"} topMargin="mt-2">
|
||||
<Bar options={options} data={data} />
|
||||
</TitleCard>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default BarChart
|
|
@ -1,66 +0,0 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
Filler,
|
||||
ArcElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Doughnut } from 'react-chartjs-2';
|
||||
import TitleCard from '../../../components/Cards/TitleCard';
|
||||
import Subtitle from '../../../components/Typography/Subtitle';
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend,
|
||||
Tooltip,
|
||||
Filler,
|
||||
Legend);
|
||||
|
||||
function DoughnutChart(){
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const labels = ['Electronics', 'Home Applicances', 'Beauty', 'Furniture', 'Watches', 'Apparel'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: '# of Orders',
|
||||
data: [122, 219, 30, 51, 82, 13],
|
||||
backgroundColor: [
|
||||
'rgba(255, 99, 132, 0.8)',
|
||||
'rgba(54, 162, 235, 0.8)',
|
||||
'rgba(255, 206, 86, 0.8)',
|
||||
'rgba(75, 192, 192, 0.8)',
|
||||
'rgba(153, 102, 255, 0.8)',
|
||||
'rgba(255, 159, 64, 0.8)',
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(255, 99, 132, 1)',
|
||||
'rgba(54, 162, 235, 1)',
|
||||
'rgba(255, 206, 86, 1)',
|
||||
'rgba(75, 192, 192, 1)',
|
||||
'rgba(153, 102, 255, 1)',
|
||||
'rgba(255, 159, 64, 1)',
|
||||
],
|
||||
borderWidth: 1,
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
return(
|
||||
<TitleCard title={"Orders by Category"}>
|
||||
<Doughnut options={options} data={data} />
|
||||
</TitleCard>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default DoughnutChart
|
|
@ -1,66 +0,0 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
Filler,
|
||||
ArcElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Pie } from 'react-chartjs-2';
|
||||
import TitleCard from '../../../components/Cards/TitleCard';
|
||||
import Subtitle from '../../../components/Typography/Subtitle';
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend,
|
||||
Tooltip,
|
||||
Filler,
|
||||
Legend);
|
||||
|
||||
function PieChart(){
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const labels = ['India', 'Middle East', 'Europe', 'US', 'Latin America', 'Asia(non-india)'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: '# of Orders',
|
||||
data: [122, 219, 30, 51, 82, 13],
|
||||
backgroundColor: [
|
||||
'rgba(255, 99, 255, 0.8)',
|
||||
'rgba(54, 162, 235, 0.8)',
|
||||
'rgba(255, 206, 255, 0.8)',
|
||||
'rgba(75, 192, 255, 0.8)',
|
||||
'rgba(153, 102, 255, 0.8)',
|
||||
'rgba(255, 159, 255, 0.8)',
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(255, 99, 255, 1)',
|
||||
'rgba(54, 162, 235, 1)',
|
||||
'rgba(255, 206, 255, 1)',
|
||||
'rgba(75, 192, 255, 1)',
|
||||
'rgba(153, 102, 255, 1)',
|
||||
'rgba(255, 159, 255, 1)',
|
||||
],
|
||||
borderWidth: 1,
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
return(
|
||||
<TitleCard title={"Orders by country"}>
|
||||
<Pie options={options} data={data} />
|
||||
</TitleCard>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default PieChart
|
|
@ -1,55 +0,0 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
Filler,
|
||||
ArcElement,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Scatter } from 'react-chartjs-2';
|
||||
import TitleCard from '../../../components/Cards/TitleCard';
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend,
|
||||
Tooltip,
|
||||
Filler,
|
||||
Legend);
|
||||
|
||||
function ScatterChart(){
|
||||
|
||||
const options = {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const data = {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Orders > 1k',
|
||||
data: Array.from({ length: 100 }, () => ({
|
||||
x: Math.random() * 11,
|
||||
y: Math.random() * 31,
|
||||
})),
|
||||
backgroundColor: 'rgba(255, 99, 132, 1)',
|
||||
},
|
||||
{
|
||||
label: 'Orders > 2K',
|
||||
data: Array.from({ length: 100 }, () => ({
|
||||
x: Math.random() * 12,
|
||||
y: Math.random() * 12,
|
||||
})),
|
||||
backgroundColor: 'rgba(0, 0, 255, 1)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return(
|
||||
<TitleCard title={"No of Orders by month (in k)"}>
|
||||
<Scatter options={options} data={data} />
|
||||
</TitleCard>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default ScatterChart
|
|
@ -1,61 +0,0 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import TitleCard from '../../../components/Cards/TitleCard';
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
function StackBarChart(){
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Store 1',
|
||||
data: labels.map(() => { return Math.random() * 1000 + 500 }),
|
||||
backgroundColor: 'rgba(255, 99, 132, 1)',
|
||||
},
|
||||
{
|
||||
label: 'Store 2',
|
||||
data: labels.map(() => { return Math.random() * 1000 + 500 }),
|
||||
backgroundColor: 'rgba(53, 162, 235, 1)',
|
||||
},
|
||||
{
|
||||
label: 'Store 3',
|
||||
data: labels.map(() => { return Math.random() * 1000 + 500 }),
|
||||
backgroundColor: 'rgba(235, 162, 235, 1)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return(
|
||||
<TitleCard title={"Sales"} topMargin="mt-2">
|
||||
<Bar options={options} data={data} />
|
||||
</TitleCard>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default StackBarChart
|
|
@ -1,58 +0,0 @@
|
|||
import LineChart from './components/LineChart'
|
||||
import BarChart from './components/BarChart'
|
||||
import DoughnutChart from './components/DoughnutChart'
|
||||
import PieChart from './components/PieChart'
|
||||
import ScatterChart from './components/ScatterChart'
|
||||
import StackBarChart from './components/StackBarChart'
|
||||
import Datepicker from "react-tailwindcss-datepicker";
|
||||
import { useState } from 'react'
|
||||
|
||||
|
||||
|
||||
|
||||
function Charts(){
|
||||
|
||||
const [dateValue, setDateValue] = useState({
|
||||
startDate: new Date(),
|
||||
endDate: new Date()
|
||||
});
|
||||
|
||||
const handleDatePickerValueChange = (newValue) => {
|
||||
console.log("newValue:", newValue);
|
||||
setDateValue(newValue);
|
||||
}
|
||||
|
||||
return(
|
||||
<>
|
||||
<Datepicker
|
||||
containerClassName="w-72"
|
||||
value={dateValue}
|
||||
theme={"light"}
|
||||
inputClassName="input input-bordered w-72"
|
||||
popoverDirection={"down"}
|
||||
toggleClassName="invisible"
|
||||
onChange={handleDatePickerValueChange}
|
||||
showShortcuts={true}
|
||||
primaryColor={"white"}
|
||||
/>
|
||||
{/** ---------------------- Different charts ------------------------- */}
|
||||
<div className="grid lg:grid-cols-2 mt-0 grid-cols-1 gap-6">
|
||||
<StackBarChart />
|
||||
<BarChart />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="grid lg:grid-cols-2 mt-4 grid-cols-1 gap-6">
|
||||
<DoughnutChart />
|
||||
<PieChart />
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 mt-4 grid-cols-1 gap-6">
|
||||
<ScatterChart />
|
||||
<LineChart />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Charts
|
|
@ -10,7 +10,7 @@ export const headerSlice = createSlice({
|
|||
},
|
||||
reducers: {
|
||||
setPageTitle: (state, action) => {
|
||||
state.pageTitle = action.payload.title
|
||||
state.pageTitle = action.payload
|
||||
},
|
||||
|
||||
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import TitleCard from '../../../components/Cards/TitleCard';
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
function BarChart(){
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Store 1',
|
||||
data: labels.map(() => { return Math.random() * 1000 + 500 }),
|
||||
backgroundColor: 'rgba(255, 99, 132, 1)',
|
||||
},
|
||||
{
|
||||
label: 'Store 2',
|
||||
data: labels.map(() => { return Math.random() * 1000 + 500 }),
|
||||
backgroundColor: 'rgba(53, 162, 235, 1)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return(
|
||||
<TitleCard title={"Revenue"}>
|
||||
<Bar options={options} data={data} />
|
||||
</TitleCard>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default BarChart
|
|
@ -0,0 +1,53 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import TitleCard from '@/Components/Cards/TitleCard';
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
function BarChart() {
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Store 1',
|
||||
data: labels.map(() => { return Math.random() * 1000 + 500 }),
|
||||
backgroundColor: 'rgba(255, 99, 132, 1)',
|
||||
},
|
||||
{
|
||||
label: 'Store 2',
|
||||
data: labels.map(() => { return Math.random() * 1000 + 500 }),
|
||||
backgroundColor: 'rgba(53, 162, 235, 1)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<TitleCard title={"Revenue"}>
|
||||
<Bar options={options} data={data} />
|
||||
</TitleCard>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default BarChart
|
|
@ -1,75 +0,0 @@
|
|||
import SelectBox from "../../../components/Input/SelectBox"
|
||||
import ArrowDownTrayIcon from '@heroicons/react/24/outline/ArrowDownTrayIcon'
|
||||
import ShareIcon from '@heroicons/react/24/outline/ShareIcon'
|
||||
import EnvelopeIcon from '@heroicons/react/24/outline/EnvelopeIcon'
|
||||
import EllipsisVerticalIcon from '@heroicons/react/24/outline/EllipsisVerticalIcon'
|
||||
import ArrowPathIcon from '@heroicons/react/24/outline/ArrowPathIcon'
|
||||
import { useState } from "react"
|
||||
import Datepicker from "react-tailwindcss-datepicker";
|
||||
|
||||
|
||||
|
||||
const periodOptions = [
|
||||
{name : "Today", value : "TODAY"},
|
||||
{name : "Yesterday", value : "YESTERDAY"},
|
||||
{name : "This Week", value : "THIS_WEEK"},
|
||||
{name : "Last Week", value : "LAST_WEEK"},
|
||||
{name : "This Month", value : "THIS_MONTH"},
|
||||
{name : "Last Month", value : "LAST_MONTH"},
|
||||
]
|
||||
|
||||
function DashboardTopBar({updateDashboardPeriod}){
|
||||
|
||||
const [dateValue, setDateValue] = useState({
|
||||
startDate: new Date(),
|
||||
endDate: new Date()
|
||||
});
|
||||
|
||||
const handleDatePickerValueChange = (newValue) => {
|
||||
console.log("newValue:", newValue);
|
||||
setDateValue(newValue);
|
||||
updateDashboardPeriod(newValue)
|
||||
}
|
||||
|
||||
|
||||
return(
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="">
|
||||
<Datepicker
|
||||
containerClassName="w-72 "
|
||||
value={dateValue}
|
||||
theme={"light"}
|
||||
inputClassName="input input-bordered w-72"
|
||||
popoverDirection={"down"}
|
||||
toggleClassName="invisible"
|
||||
onChange={handleDatePickerValueChange}
|
||||
showShortcuts={true}
|
||||
primaryColor={"white"}
|
||||
/>
|
||||
{/* <SelectBox
|
||||
options={periodOptions}
|
||||
labelTitle="Period"
|
||||
placeholder="Select date range"
|
||||
containerStyle="w-72"
|
||||
labelStyle="hidden"
|
||||
defaultValue="TODAY"
|
||||
updateFormValue={updateSelectBoxValue}
|
||||
/> */}
|
||||
</div>
|
||||
<div className="text-right ">
|
||||
<button className="btn btn-ghost btn-sm normal-case"><ArrowPathIcon className="w-4 mr-2"/>Refresh Data</button>
|
||||
<button className="btn btn-ghost btn-sm normal-case ml-2"><ShareIcon className="w-4 mr-2"/>Share</button>
|
||||
|
||||
<div className="dropdown dropdown-bottom dropdown-end ml-2">
|
||||
<label tabIndex={0} className="btn btn-ghost btn-sm normal-case btn-square "><EllipsisVerticalIcon className="w-5"/></label>
|
||||
<ul tabIndex={0} className="dropdown-content menu menu-compact p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li><a><EnvelopeIcon className="w-4"/>Email Digests</a></li>
|
||||
<li><a><ArrowDownTrayIcon className="w-4"/>Download</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DashboardTopBar
|
|
@ -0,0 +1,75 @@
|
|||
import SelectBox from "@/Components/Input/SelectBox"
|
||||
import ArrowDownTrayIcon from '@heroicons/react/24/outline/ArrowDownTrayIcon'
|
||||
import ShareIcon from '@heroicons/react/24/outline/ShareIcon'
|
||||
import EnvelopeIcon from '@heroicons/react/24/outline/EnvelopeIcon'
|
||||
import EllipsisVerticalIcon from '@heroicons/react/24/outline/EllipsisVerticalIcon'
|
||||
import ArrowPathIcon from '@heroicons/react/24/outline/ArrowPathIcon'
|
||||
import { useState } from "react"
|
||||
import Datepicker from "react-tailwindcss-datepicker";
|
||||
|
||||
|
||||
|
||||
const periodOptions = [
|
||||
{ name: "Today", value: "TODAY" },
|
||||
{ name: "Yesterday", value: "YESTERDAY" },
|
||||
{ name: "This Week", value: "THIS_WEEK" },
|
||||
{ name: "Last Week", value: "LAST_WEEK" },
|
||||
{ name: "This Month", value: "THIS_MONTH" },
|
||||
{ name: "Last Month", value: "LAST_MONTH" },
|
||||
]
|
||||
|
||||
function DashboardTopBar({ updateDashboardPeriod }) {
|
||||
|
||||
const [dateValue, setDateValue] = useState({
|
||||
startDate: new Date(),
|
||||
endDate: new Date()
|
||||
});
|
||||
|
||||
const handleDatePickerValueChange = (newValue) => {
|
||||
console.log("newValue:", newValue);
|
||||
setDateValue(newValue);
|
||||
updateDashboardPeriod(newValue)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="">
|
||||
<Datepicker
|
||||
containerClassName="w-72 "
|
||||
value={dateValue}
|
||||
theme={"light"}
|
||||
inputClassName="input input-bordered w-72"
|
||||
popoverDirection={"down"}
|
||||
toggleClassName="invisible"
|
||||
onChange={handleDatePickerValueChange}
|
||||
showShortcuts={true}
|
||||
primaryColor={"white"}
|
||||
/>
|
||||
{/* <SelectBox
|
||||
options={periodOptions}
|
||||
labelTitle="Period"
|
||||
placeholder="Select date range"
|
||||
containerStyle="w-72"
|
||||
labelStyle="hidden"
|
||||
defaultValue="TODAY"
|
||||
updateFormValue={updateSelectBoxValue}
|
||||
/> */}
|
||||
</div>
|
||||
<div className="text-right ">
|
||||
<button className="btn btn-ghost btn-sm normal-case"><ArrowPathIcon className="w-4 mr-2" />Refresh Data</button>
|
||||
<button className="btn btn-ghost btn-sm normal-case ml-2"><ShareIcon className="w-4 mr-2" />Share</button>
|
||||
|
||||
<div className="dropdown dropdown-bottom dropdown-end ml-2">
|
||||
<label tabIndex={0} className="btn btn-ghost btn-sm normal-case btn-square "><EllipsisVerticalIcon className="w-5" /></label>
|
||||
<ul tabIndex={0} className="dropdown-content menu menu-compact p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li><a><EnvelopeIcon className="w-4" />Email Digests</a></li>
|
||||
<li><a><ArrowDownTrayIcon className="w-4" />Download</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DashboardTopBar
|
|
@ -1,66 +0,0 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
Filler,
|
||||
ArcElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Doughnut } from 'react-chartjs-2';
|
||||
import TitleCard from '../../../components/Cards/TitleCard';
|
||||
import Subtitle from '../../../components/Typography/Subtitle';
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend,
|
||||
Tooltip,
|
||||
Filler,
|
||||
Legend);
|
||||
|
||||
function DoughnutChart(){
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const labels = ['Electronics', 'Home Applicances', 'Beauty', 'Furniture', 'Watches', 'Apparel'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: '# of Orders',
|
||||
data: [122, 219, 30, 51, 82, 13],
|
||||
backgroundColor: [
|
||||
'rgba(255, 99, 132, 0.8)',
|
||||
'rgba(54, 162, 235, 0.8)',
|
||||
'rgba(255, 206, 86, 0.8)',
|
||||
'rgba(75, 192, 192, 0.8)',
|
||||
'rgba(153, 102, 255, 0.8)',
|
||||
'rgba(255, 159, 64, 0.8)',
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(255, 99, 132, 1)',
|
||||
'rgba(54, 162, 235, 1)',
|
||||
'rgba(255, 206, 86, 1)',
|
||||
'rgba(75, 192, 192, 1)',
|
||||
'rgba(153, 102, 255, 1)',
|
||||
'rgba(255, 159, 64, 1)',
|
||||
],
|
||||
borderWidth: 1,
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
return(
|
||||
<TitleCard title={"Orders by Category"}>
|
||||
<Doughnut options={options} data={data} />
|
||||
</TitleCard>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default DoughnutChart
|
|
@ -0,0 +1,66 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
Filler,
|
||||
ArcElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Doughnut } from 'react-chartjs-2';
|
||||
import TitleCard from '@/Components/Cards/TitleCard';
|
||||
import Subtitle from '@/Components/Typography/Subtitle';
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend,
|
||||
Tooltip,
|
||||
Filler,
|
||||
Legend);
|
||||
|
||||
function DoughnutChart() {
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const labels = ['Electronics', 'Home Applicances', 'Beauty', 'Furniture', 'Watches', 'Apparel'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: '# of Orders',
|
||||
data: [122, 219, 30, 51, 82, 13],
|
||||
backgroundColor: [
|
||||
'rgba(255, 99, 132, 0.8)',
|
||||
'rgba(54, 162, 235, 0.8)',
|
||||
'rgba(255, 206, 86, 0.8)',
|
||||
'rgba(75, 192, 192, 0.8)',
|
||||
'rgba(153, 102, 255, 0.8)',
|
||||
'rgba(255, 159, 64, 0.8)',
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(255, 99, 132, 1)',
|
||||
'rgba(54, 162, 235, 1)',
|
||||
'rgba(255, 206, 86, 1)',
|
||||
'rgba(75, 192, 192, 1)',
|
||||
'rgba(153, 102, 255, 1)',
|
||||
'rgba(255, 159, 64, 1)',
|
||||
],
|
||||
borderWidth: 1,
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<TitleCard title={"Orders by Category"}>
|
||||
<Doughnut options={options} data={data} />
|
||||
</TitleCard>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default DoughnutChart
|
|
@ -1,62 +0,0 @@
|
|||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Filler,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import TitleCard from '../../../components/Cards/TitleCard';
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Filler,
|
||||
Legend
|
||||
);
|
||||
|
||||
function LineChart(){
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
fill: true,
|
||||
label: 'MAU',
|
||||
data: labels.map(() => { return Math.random() * 100 + 500 }),
|
||||
borderColor: 'rgb(53, 162, 235)',
|
||||
backgroundColor: 'rgba(53, 162, 235, 0.5)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
return(
|
||||
<TitleCard title={"Montly Active Users (in K)"}>
|
||||
<Line data={data} options={options}/>
|
||||
</TitleCard>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default LineChart
|
|
@ -10,7 +10,7 @@ import {
|
|||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import TitleCard from '../../../components/Cards/TitleCard';
|
||||
import TitleCard from '@/Components/Cards/TitleCard'
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
|
@ -23,7 +23,7 @@ ChartJS.register(
|
|||
Legend
|
||||
);
|
||||
|
||||
function LineChart(){
|
||||
function LineChart() {
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
|
@ -34,28 +34,28 @@ function LineChart(){
|
|||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
fill: true,
|
||||
label: 'MAU',
|
||||
data: labels.map(() => { return Math.random() * 100 + 500 }),
|
||||
borderColor: 'rgb(53, 162, 235)',
|
||||
backgroundColor: 'rgba(53, 162, 235, 0.5)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
fill: true,
|
||||
label: 'MAU',
|
||||
data: labels.map(() => { return Math.random() * 100 + 500 }),
|
||||
borderColor: 'rgb(53, 162, 235)',
|
||||
backgroundColor: 'rgba(53, 162, 235, 0.5)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return(
|
||||
<TitleCard title={"Montly Active Users (in k)"} >
|
||||
<Line data={data} options={options}/>
|
||||
</TitleCard>
|
||||
)
|
||||
|
||||
return (
|
||||
<TitleCard title={"Montly Active Users (in K)"}>
|
||||
<Line data={data} options={options} />
|
||||
</TitleCard>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
import TitleCard from "../../../components/Cards/TitleCard"
|
||||
|
||||
const userSourceData = [
|
||||
{source : "Facebook Ads", count : "26,345", conversionPercent : 10.2},
|
||||
{source : "Google Ads", count : "21,341", conversionPercent : 11.7},
|
||||
{source : "Instagram Ads", count : "34,379", conversionPercent : 12.4},
|
||||
{source : "Affiliates", count : "12,359", conversionPercent : 20.9},
|
||||
{source : "Organic", count : "10,345", conversionPercent : 10.3},
|
||||
]
|
||||
|
||||
function UserChannels(){
|
||||
return(
|
||||
<TitleCard title={"User Signup Source"}>
|
||||
{/** Table Data */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th className="normal-case">Source</th>
|
||||
<th className="normal-case">No of Users</th>
|
||||
<th className="normal-case">Conversion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
userSourceData.map((u, k) => {
|
||||
return(
|
||||
<tr key={k}>
|
||||
<th>{k+1}</th>
|
||||
<td>{u.source}</td>
|
||||
<td>{u.count}</td>
|
||||
<td>{`${u.conversionPercent}%`}</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</TitleCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserChannels
|
|
@ -0,0 +1,45 @@
|
|||
import TitleCard from "@/Components/Cards/TitleCard"
|
||||
|
||||
const userSourceData = [
|
||||
{ source: "Facebook Ads", count: "26,345", conversionPercent: 10.2 },
|
||||
{ source: "Google Ads", count: "21,341", conversionPercent: 11.7 },
|
||||
{ source: "Instagram Ads", count: "34,379", conversionPercent: 12.4 },
|
||||
{ source: "Affiliates", count: "12,359", conversionPercent: 20.9 },
|
||||
{ source: "Organic", count: "10,345", conversionPercent: 10.3 },
|
||||
]
|
||||
|
||||
function UserChannels() {
|
||||
return (
|
||||
<TitleCard title={"User Signup Source"}>
|
||||
{/** Table Data */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th className="normal-case">Source</th>
|
||||
<th className="normal-case">No of Users</th>
|
||||
<th className="normal-case">Conversion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
userSourceData.map((u, k) => {
|
||||
return (
|
||||
<tr key={k}>
|
||||
<th>{k + 1}</th>
|
||||
<td>{u.source}</td>
|
||||
<td>{u.count}</td>
|
||||
<td>{`${u.conversionPercent}%`}</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</TitleCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserChannels
|
|
@ -1,50 +1,50 @@
|
|||
import DashboardStats from './components/DashboardStats'
|
||||
import AmountStats from './components/AmountStats'
|
||||
import PageStats from './components/PageStats'
|
||||
import PageStats from '@/Components/features/dashboard/components/PageStats'
|
||||
|
||||
import UserGroupIcon from '@heroicons/react/24/outline/UserGroupIcon'
|
||||
import UsersIcon from '@heroicons/react/24/outline/UsersIcon'
|
||||
import CircleStackIcon from '@heroicons/react/24/outline/CircleStackIcon'
|
||||
import CreditCardIcon from '@heroicons/react/24/outline/CreditCardIcon'
|
||||
import UserGroupIcon from '@heroicons/react/24/outline/UserGroupIcon'
|
||||
import UsersIcon from '@heroicons/react/24/outline/UsersIcon'
|
||||
import CircleStackIcon from '@heroicons/react/24/outline/CircleStackIcon'
|
||||
import CreditCardIcon from '@heroicons/react/24/outline/CreditCardIcon'
|
||||
import UserChannels from './components/UserChannels'
|
||||
import LineChart from './components/LineChart'
|
||||
import BarChart from './components/BarChart'
|
||||
import DashboardTopBar from './components/DashboardTopBar'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import {showNotification} from '../common/headerSlice'
|
||||
import { showNotification } from '../common/headerSlice'
|
||||
import DoughnutChart from './components/DoughnutChart'
|
||||
import { useState } from 'react'
|
||||
|
||||
const statsData = [
|
||||
{title : "New Users", value : "34.7k", icon : <UserGroupIcon className='w-8 h-8'/>, description : "↗︎ 2300 (22%)"},
|
||||
{title : "Total Sales", value : "$34,545", icon : <CreditCardIcon className='w-8 h-8'/>, description : "Current month"},
|
||||
{title : "Pending Leads", value : "450", icon : <CircleStackIcon className='w-8 h-8'/>, description : "50 in hot leads"},
|
||||
{title : "Active Users", value : "5.6k", icon : <UsersIcon className='w-8 h-8'/>, description : "↙ 300 (18%)"},
|
||||
{ title: "New Users", value: "34.7k", icon: <UserGroupIcon className='w-8 h-8' />, description: "↗︎ 2300 (22%)" },
|
||||
{ title: "Total Sales", value: "$34,545", icon: <CreditCardIcon className='w-8 h-8' />, description: "Current month" },
|
||||
{ title: "Pending Leads", value: "450", icon: <CircleStackIcon className='w-8 h-8' />, description: "50 in hot leads" },
|
||||
{ title: "Active Users", value: "5.6k", icon: <UsersIcon className='w-8 h-8' />, description: "↙ 300 (18%)" },
|
||||
]
|
||||
|
||||
|
||||
|
||||
function Dashboard(){
|
||||
function Dashboard() {
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
|
||||
|
||||
const updateDashboardPeriod = (newRange) => {
|
||||
// Dashboard range changed, write code to refresh your values
|
||||
dispatch(showNotification({message : `Period updated to ${newRange.startDate} to ${newRange.endDate}`, status : 1}))
|
||||
dispatch(showNotification({ message: `Period updated to ${newRange.startDate} to ${newRange.endDate}`, status: 1 }))
|
||||
}
|
||||
|
||||
return(
|
||||
return (
|
||||
<>
|
||||
{/** ---------------------- Select Period Content ------------------------- */}
|
||||
<DashboardTopBar updateDashboardPeriod={updateDashboardPeriod}/>
|
||||
|
||||
{/** ---------------------- Different stats content 1 ------------------------- */}
|
||||
{/** ---------------------- Select Period Content ------------------------- */}
|
||||
<DashboardTopBar updateDashboardPeriod={updateDashboardPeriod} />
|
||||
|
||||
{/** ---------------------- Different stats content 1 ------------------------- */}
|
||||
<div className="grid lg:grid-cols-4 mt-2 md:grid-cols-2 grid-cols-1 gap-6">
|
||||
{
|
||||
statsData.map((d, k) => {
|
||||
return (
|
||||
<DashboardStats key={k} {...d} colorIndex={k}/>
|
||||
<DashboardStats key={k} {...d} colorIndex={k} />
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -52,21 +52,21 @@ function Dashboard(){
|
|||
|
||||
|
||||
|
||||
{/** ---------------------- Different charts ------------------------- */}
|
||||
{/** ---------------------- Different charts ------------------------- */}
|
||||
<div className="grid lg:grid-cols-2 mt-4 grid-cols-1 gap-6">
|
||||
<LineChart />
|
||||
<BarChart />
|
||||
</div>
|
||||
|
||||
{/** ---------------------- Different stats content 2 ------------------------- */}
|
||||
|
||||
|
||||
{/** ---------------------- Different stats content 2 ------------------------- */}
|
||||
|
||||
<div className="grid lg:grid-cols-2 mt-10 grid-cols-1 gap-6">
|
||||
<AmountStats />
|
||||
<PageStats />
|
||||
</div>
|
||||
|
||||
{/** ---------------------- User source channels table ------------------------- */}
|
||||
|
||||
{/** ---------------------- User source channels table ------------------------- */}
|
||||
|
||||
<div className="grid lg:grid-cols-2 mt-4 grid-cols-1 gap-6">
|
||||
<UserChannels />
|
||||
<DoughnutChart />
|
|
@ -1,39 +0,0 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import { useDispatch } from "react-redux"
|
||||
import TitleCard from "../../components/Cards/TitleCard"
|
||||
import { setPageTitle, showNotification } from "../common/headerSlice"
|
||||
import DocComponentsNav from "./components/DocComponentsNav"
|
||||
import ReadMe from "./components/GettingStartedContent"
|
||||
import DocComponentsContent from "./components/DocComponentsContent"
|
||||
import FeaturesNav from "./components/FeaturesNav"
|
||||
import FeaturesContent from "./components/FeaturesContent"
|
||||
|
||||
|
||||
|
||||
function DocComponents(){
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setPageTitle({ title : "Documentation"}))
|
||||
}, [])
|
||||
|
||||
|
||||
return(
|
||||
<>
|
||||
<div className="bg-base-100 flex overflow-hidden rounded-lg" style={{height : "82vh"}}>
|
||||
<div className="flex-none p-4">
|
||||
<DocComponentsNav activeIndex={1}/>
|
||||
</div>
|
||||
|
||||
<div className="grow pt-16 overflow-y-scroll">
|
||||
<DocComponentsContent />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocComponents
|
|
@ -1,39 +0,0 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import { useDispatch } from "react-redux"
|
||||
import TitleCard from "../../components/Cards/TitleCard"
|
||||
import { setPageTitle, showNotification } from "../common/headerSlice"
|
||||
import GettingStartedNav from "./components/GettingStartedNav"
|
||||
import ReadMe from "./components/GettingStartedContent"
|
||||
import GettingStartedContent from "./components/GettingStartedContent"
|
||||
import FeaturesNav from "./components/FeaturesNav"
|
||||
import FeaturesContent from "./components/FeaturesContent"
|
||||
|
||||
|
||||
|
||||
function Features(){
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setPageTitle({ title : "Documentation"}))
|
||||
}, [])
|
||||
|
||||
|
||||
return(
|
||||
<>
|
||||
<div className="bg-base-100 flex overflow-hidden rounded-lg" style={{height : "82vh"}}>
|
||||
<div className="flex-none p-4">
|
||||
<FeaturesNav activeIndex={1}/>
|
||||
</div>
|
||||
|
||||
<div className="grow pt-16 overflow-y-scroll">
|
||||
<FeaturesContent />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Features
|
|
@ -1,37 +0,0 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import { useDispatch } from "react-redux"
|
||||
import TitleCard from "../../components/Cards/TitleCard"
|
||||
import { setPageTitle, showNotification } from "../common/headerSlice"
|
||||
import GettingStartedNav from "./components/GettingStartedNav"
|
||||
import ReadMe from "./components/GettingStartedContent"
|
||||
import GettingStartedContent from "./components/GettingStartedContent"
|
||||
|
||||
|
||||
|
||||
function GettingStarted(){
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setPageTitle({ title : "Documentation"}))
|
||||
}, [])
|
||||
|
||||
|
||||
return(
|
||||
<>
|
||||
<div className="bg-base-100 flex overflow-hidden rounded-lg" style={{height : "82vh"}}>
|
||||
<div className="flex-none p-4">
|
||||
<GettingStartedNav activeIndex={1}/>
|
||||
</div>
|
||||
|
||||
<div className="grow pt-16 overflow-y-scroll">
|
||||
<GettingStartedContent />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default GettingStarted
|
|
@ -1,99 +0,0 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import InputText from '../../../components/Input/InputText'
|
||||
import Title from '../../../components/Typography/Title'
|
||||
import Subtitle from '../../../components/Typography/Subtitle'
|
||||
import ErrorText from '../../../components/Typography/ErrorText'
|
||||
import HelperText from '../../../components/Typography/HelperText'
|
||||
|
||||
import { setPageTitle, showNotification } from '../../common/headerSlice'
|
||||
import TitleCard from '../../../components/Cards/TitleCard'
|
||||
|
||||
function DocComponentsContent(){
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const updateFormValue = () => {
|
||||
// Dummy function for input text component
|
||||
}
|
||||
|
||||
return(
|
||||
<>
|
||||
<article className="prose">
|
||||
<h1 className="" >Components</h1>
|
||||
|
||||
We have added some global components that are used commonly inside the project.
|
||||
|
||||
{/* Typography*/}
|
||||
<h2 id="component1">Typography</h2>
|
||||
<div>
|
||||
These components are present under <span className="badge mt-0 mb-0 badge-ghost">/components/Typography</span> folder. It accepts styleClass as props which can be used to pass additional className for style. It has following components which you can import and use it -
|
||||
<div className="mockup-code mt-4">
|
||||
<pre className='my-0 py-0'><code>{'import Title from "../components/Typography/Title"\n <Title>Your Title here</Title>'}</code></pre>
|
||||
</div>
|
||||
<ul>
|
||||
<li><span className='font-bold'>Title</span> - Use this component to show title
|
||||
<Title>Title Example</Title>
|
||||
</li>
|
||||
<li><span className='font-bold'>Subtitle</span> - Component that shows text smaller than title
|
||||
<Subtitle styleClass="mt-4 mb-6">Subtitle Example</Subtitle>
|
||||
</li>
|
||||
<li><span className='font-bold'>ErrorText</span> - Used for showing error messages
|
||||
<ErrorText styleClass="mt-2">Error Text Example</ErrorText>
|
||||
</li>
|
||||
<li><span className='font-bold'>HelperText</span> - Used for showing secondary message
|
||||
<HelperText styleClass="">Helper Text Example</HelperText></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Form Input*/}
|
||||
<h2 id="component2">Form Input</h2>
|
||||
<p>
|
||||
Many times we have to use form input like text, select one or toogle and in every file we have to handle its state management, here we have added global form component that can be used in any file and state variables can be managed by passing props to it. It is present in <span className="badge mt-0 mb-0 badge-ghost">/components/Input</span> folder.
|
||||
</p>
|
||||
Ex-
|
||||
<div className="mockup-code mt-4">
|
||||
<pre className='my-0 py-0'><code>{'const INITIAL_LEAD_OBJ = {\n first_name : "", \n last_name : "", \n email : "" \n } \n const [leadObj, setLeadObj] = useState(INITIAL_LEAD_OBJ) \n const updateFormValue = ({updateType, value}) => {\n setErrorMessage("") \n setLeadObj({...leadObj, [updateType] : value})\n }\n\n<InputText type="text" defaultValue={leadObj.first_name} \n updateType="first_name" containerStyle="mt-4" \n labelTitle="First Name" updateFormValue={updateFormValue}/>'}</code></pre>
|
||||
</div>
|
||||
<InputText type="text" defaultValue={"input value"} updateType="first_name" containerStyle="mt-3" labelTitle="Label Title" updateFormValue={updateFormValue}/>
|
||||
|
||||
|
||||
<p> This example is from add new lead modal, here we are importing component for creating text input and passing some props to handle its content and state variable. Description of props are as follows - </p>
|
||||
<ul>
|
||||
<li><span className='font-bold'>type</span> - Input type value like number, date, time etc.. </li>
|
||||
<li><span className='font-bold'>updateType</span> - This is used to update state variable in parent component</li>
|
||||
<li><span className='font-bold'>containerStyle</span> - Style class for container of input, which include label as well</li>
|
||||
<li><span className='font-bold'>labelTitle</span> - Title of the label</li>
|
||||
<li><span className='font-bold'>updateFormValue</span> - Function of parent component to update state variable</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Cards */}
|
||||
<h2 id="component3">Cards</h2>
|
||||
<p>
|
||||
<a href="https://daisyui.com/components/card/" target="_blank">Daisy UI</a> already have many cards layout, on top of that we have added one card component that accept title props and shows children inside its body. Also there is a divider between title and body of card. On more provision has been added to add buttons on top left side of card using TopSideButtons props (check leads page).
|
||||
|
||||
</p>
|
||||
Ex -
|
||||
<div className="mockup-code mt-4">
|
||||
<pre className='my-0 py-0'><code>{'<TitleCard title={"Card Title"}> <h1>Card Body</h1></TitleCard>'}</code></pre>
|
||||
</div>
|
||||
<div className='p-8 bg-base-300 rounded-lg mt-4'>
|
||||
<TitleCard title={"Card Title"}> <h1>Card Body</h1></TitleCard>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div className='h-24'></div>
|
||||
|
||||
|
||||
</article>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocComponentsContent
|
|
@ -1,35 +0,0 @@
|
|||
import { useState } from "react"
|
||||
|
||||
function DocComponentsNav({activeIndex}){
|
||||
|
||||
const SECTION_NAVS = [
|
||||
{name : "Typography", isActive : activeIndex === 1 ? true : false},
|
||||
{name : "Form Input", isActive : false},
|
||||
{name : "Cards", isActive : false},
|
||||
]
|
||||
const [navs, setNavs] = useState(SECTION_NAVS)
|
||||
|
||||
const scrollToSection = (currentIndex) => {
|
||||
setNavs(navs.map((n, k) => {
|
||||
if(k === currentIndex)return {...n, isActive : true}
|
||||
else return {...n, isActive : false}
|
||||
}))
|
||||
document.getElementById('component'+(currentIndex+1)).scrollIntoView({behavior: 'smooth' })
|
||||
}
|
||||
|
||||
return(
|
||||
<ul className="menu w-56 mt-10 text-sm">
|
||||
<li className="menu-title"><span className="">Components</span></li>
|
||||
|
||||
{
|
||||
navs.map((n, k) => {
|
||||
return(
|
||||
<li key={k} onClick={() => scrollToSection(k)} className={n.isActive ? "bordered" : ""}><a>{n.name}</a></li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocComponentsNav
|
|
@ -1,147 +0,0 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import Subtitle from '../../../components/Typography/Subtitle'
|
||||
import { setPageTitle, showNotification } from '../../common/headerSlice'
|
||||
|
||||
function FeaturesContent(){
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return(
|
||||
<>
|
||||
<article className="prose">
|
||||
<h1 className="">Features</h1>
|
||||
|
||||
|
||||
|
||||
{/* Authentication*/}
|
||||
<h2 id="feature1">Authentication</h2>
|
||||
<p>
|
||||
JWT based Authentication logic is present in <span className="badge mt-0 mb-0 badge-ghost">/app/auth.js</span>. In the file you can see we are adding bearer token in header for every request. Every routes under <span className="badge mt-0 mb-0 badge-ghost">/routes/</span> folder will need authentication. For public routes like login, register you will have to add routes in <span className="badge mt-0 mb-0 badge-ghost">App.js</span> file and also include the path in PUBLIC_ROUTES variable under <span className="badge mt-0 mb-0 badge-ghost">/app/auth.js</span> file so that auto redirect to login page is not triggered.
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Left Sidebar*/}
|
||||
<h2 id="feature2">Left Sidebar</h2>
|
||||
<p>
|
||||
This is main internal navigation (for pages that will come after login only), all sidebar menu items with their icons are present in <span className="badge mt-0 mb-0 badge-ghost">/routes/sidebar.js</span> file, while path and page components mapping are respectively present in <span className="badge mt-0 mb-0 badge-ghost">/routes/index.js</span> file.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
{/* Add New Page*/}
|
||||
<h2 id="feature3">Add New Page</h2>
|
||||
<p>All <span className='font-semibold'>public routes</span> are present in <span className="badge mt-0 mb-0 badge-ghost">App.js</span> file. Steps to add new public page -
|
||||
</p>
|
||||
|
||||
<ul className='mt-0'>
|
||||
<li>Create Page inside <span className="badge mt-0 mb-0 badge-ghost">/pages</span> folder</li>
|
||||
<li>Go to <span className="badge mt-0 mb-0 badge-ghost">App.js</span> and import the component and add its path</li>
|
||||
<li>Add your new route path in <span className="badge mt-0 mb-0 badge-ghost">/app/auth.js</span> file under PUBLIC_ROUTES variable, this will allow the page to open without login.</li>
|
||||
</ul>
|
||||
|
||||
<p className='mt-4'>All <span className='font-semibold'>protected routes</span> are present in <span className="badge mt-0 mb-0 badge-ghost">/routes/sidebar.js</span> file</p>
|
||||
|
||||
<ul className='mt-0'>
|
||||
<li>Create your page inside <span className="badge mt-0 mb-0 badge-ghost">/pages/protected</span> folder</li>
|
||||
<li>Add your new routes in <span className="badge mt-0 mb-0 badge-ghost">/routes/sidebar.js</span>, this will show your new page in sidebar</li>
|
||||
<li>Import your new routes component and map its path in <span className="badge mt-0 mb-0 badge-ghost">/routes/index.js</span></li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
{/* Right Sidebar*/}
|
||||
<h2 id="feature4">Right Sidebar</h2>
|
||||
<div>
|
||||
This is used for showing long list contents like notifications, settings etc.. We are using redux to show and hide and it is single component and can be called from any file with dispatch method.
|
||||
To add new content follow following steps:
|
||||
<ul>
|
||||
<li>Create new component file containing main body of your content</li>
|
||||
<li>Create new variable in <span className="badge mt-0 mb-0 badge-ghost">/utils/globalConstantUtils.js</span> file under RIGHT_DRAWER_TYPES variable</li>
|
||||
<li>Now include the file mapped with the new variable in <span className="badge mt-0 mb-0 badge-ghost">/containers/RightSidebar.js</span> file using switch. <br />
|
||||
For ex- If you new component name is <span className="badge mt-0 mb-0 badge-ghost">TestRightSideBar.js</span> and variable name is TEST_RIGHT_SIDEBAR, then add following code inside switch code block
|
||||
<br />
|
||||
<div className="mockup-code mt-4">
|
||||
<pre className='my-0 py-0'><code>{`[RIGHT_DRAWER_TYPES.TEST_RIGHT_SIDEBAR] : \n<TestRightSideBar {...extraObject} closeRightDrawer={close}/>`}</code></pre>
|
||||
</div>
|
||||
<span className='text-sm mt-1 italic'>Here extraObject have variables that is passed from parent component while calling openRightDrawer method</span>
|
||||
</li>
|
||||
<li>Now the last step, call dispatch method as follows
|
||||
<div className="mockup-code mt-1">
|
||||
<pre className='my-0 py-0'><code>{'import { useDispatch } from "react-redux"\n const dispatch = useDispatch()\n dispatch(openRightDrawer({header : "Test Right Drawer", \n bodyType : RIGHT_DRAWER_TYPES.TEST_RIGHT_SIDEBAR}))'}</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Themes*/}
|
||||
<h2 id="feature5">Themes</h2>
|
||||
<p>
|
||||
By default we have added light and dark theme and Daisy UI comes with a number of themes, which you can use with no extra effort, you just have to include it in <span className="badge mt-0 mb-0 badge-ghost">tailwind.config.js</span> file, you can add themes like cupcake, corporate, reto etc... Also we can configure themes colors in config file, for more documentation on themes checkout <a href="https://daisyui.com/docs/themes/" target="_blank">Daisy UI documentation.</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Modal*/}
|
||||
<h2 id="feature6">Modal</h2>
|
||||
<div>
|
||||
With global modal functionality you dont have to create seperate modal for each page. We are using redux to show and hide and it is a single component and can be called from any file with dispatch method.
|
||||
Code for showing modal is present in modalSlice and layout container component. To show modal just call openModal() function of modalSlice using dispatch.
|
||||
<br />
|
||||
To add new modal in any page follow following steps:
|
||||
<ul>
|
||||
<li>Create new component file containing main body of your modal content</li>
|
||||
<li>Create new variable in <span className="badge mt-0 mb-0 badge-ghost">/utils/globalConstantUtils.js</span> file under MODAL_BODY_TYPES variable</li>
|
||||
<li>Now include the file mapped with the new variable in <span className="badge mt-0 mb-0 badge-ghost">/containers/ModalLayout.js</span> file using switch. <br />
|
||||
For ex- If you new component name is <span className="badge mt-0 mb-0 badge-ghost">TestModal.js</span> and variable name is TEST_MODAL, then add following code inside switch code block
|
||||
<br />
|
||||
<div className="mockup-code mt-4">
|
||||
<pre className='my-0 py-0'><code>{`[RIGHT_DRAWER_TYPES.TEST_MODAL] : \n<TestModal closeModal={close} extraObject={extraObject}/>`}</code></pre>
|
||||
</div>
|
||||
<span className='text-sm mt-1 italic'>Here extraObject have variables that is passed from parent component while calling openModal method</span>
|
||||
</li>
|
||||
<li>Now the last step, call dispatch method as follows
|
||||
<div className="mockup-code mt-1">
|
||||
<pre className='my-0 py-0'><code>{'import { useDispatch } from "react-redux"\n const dispatch = useDispatch()\n dispatch(openModal({title : "Test Modal Title", \n bodyType : MODAL_BODY_TYPES.TEST_MODAL}))'}</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Notification*/}
|
||||
<h2 id="feature7">Notification</h2>
|
||||
<p>Many times we have to show notification to user be it on successfull form submission or any api success. And requirement can come to show notification from any page, so global notification handling is needed.</p>
|
||||
|
||||
<p className='mt-4'>Code for showing notification is present in headerSlice and layout container component. To show notification just call <span className='badge badge-ghost'>showNotification()</span> function of headerSlice using dispatch. To show success message notification pass status as 1 and for showing error message pass status as 0.</p>
|
||||
|
||||
<div className="mockup-code mb-4">
|
||||
<pre className='my-0 py-0'><code>{'import { useDispatch } from "react-redux"\n const dispatch = useDispatch()\n dispatch(showNotification({message : "Message here", status : 1}))'}</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Click on this button to check</p>
|
||||
|
||||
<button className='btn btn-success' onClick={() => dispatch(showNotification({message : "Your message has been sent!", status : 1}))}>Success</button>
|
||||
|
||||
<button className='btn btn-error ml-4' onClick={() => dispatch(showNotification({message : "Something went wrong!", status : 0}))}>Error</button>
|
||||
|
||||
|
||||
<div className='h-24'></div>
|
||||
|
||||
|
||||
</article>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturesContent
|
|
@ -1,39 +0,0 @@
|
|||
import { useState } from "react"
|
||||
|
||||
function FeaturesNav({activeIndex}){
|
||||
|
||||
const SECTION_NAVS = [
|
||||
{name : "Authentication", isActive : activeIndex === 1 ? true : false},
|
||||
{name : "Sidebar", isActive : false},
|
||||
{name : "Add New Page", isActive : false},
|
||||
{name : "Right sidebar", isActive : false},
|
||||
{name : "Themes", isActive : false},
|
||||
{name : "Modal", isActive : false},
|
||||
{name : "Notification", isActive : false},
|
||||
]
|
||||
const [navs, setNavs] = useState(SECTION_NAVS)
|
||||
|
||||
const scrollToSection = (currentIndex) => {
|
||||
setNavs(navs.map((n, k) => {
|
||||
if(k === currentIndex)return {...n, isActive : true}
|
||||
else return {...n, isActive : false}
|
||||
}))
|
||||
document.getElementById('feature'+(currentIndex+1)).scrollIntoView({behavior: 'smooth' })
|
||||
}
|
||||
|
||||
return(
|
||||
<ul className="menu w-56 mt-10 text-sm">
|
||||
<li className="menu-title"><span className="">Features</span></li>
|
||||
|
||||
{
|
||||
navs.map((n, k) => {
|
||||
return(
|
||||
<li key={k} onClick={() => scrollToSection(k)} className={n.isActive ? "bordered" : ""}><a>{n.name}</a></li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturesNav
|
|
@ -1,173 +0,0 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import Subtitle from '../../../components/Typography/Subtitle'
|
||||
import { setPageTitle } from '../../common/headerSlice'
|
||||
|
||||
function GettingStartedContent(){
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
|
||||
|
||||
return(
|
||||
<>
|
||||
<article className="prose">
|
||||
<h1 className="">Getting Started</h1>
|
||||
|
||||
|
||||
{/* Introduction */}
|
||||
<h2 className="" id="getstarted1">Introduction</h2>
|
||||
<p>A free dashboard template using <span className='font-bold'>Daisy UI</span> and react js. With the help of Dasisy UI, it comes with <span className='font-bold'>fully customizable and themable CSS</span> and power of Tailwind CSS utility classes. We have also added <span className='font-bold'>redux toolkit</span> and configured it for API calls and state management.</p>
|
||||
<p>User authentication has been implemented using JWT token method (ofcourse you need backend API for generating and verifying token). This template can be used to start your next SaaS project or build new internal tools in your company.</p>
|
||||
<h4> Core libraries used - </h4>
|
||||
<ul>
|
||||
<li><a href="https://reactjs.org/" target="_blank">React JS v18.2.0</a></li>
|
||||
<li><a href="https://reactrouter.com/en/main" target="_blank">React Router v6.4.3</a></li>
|
||||
<li><a href="https://tailwindcss.com/" target="_blank">Tailwind CSS v3.3.6</a></li>
|
||||
<li><a href="https://daisyui.com/" target="_blank">Daisy UI v4.4.19</a></li>
|
||||
<li><a href="https://heroicons.com/" target="_blank">HeroIcons v2.0.13</a></li>
|
||||
<li><a href="https://redux-toolkit.js.org/" target="_blank">Redux toolkit v1.9.0</a></li>
|
||||
<li><a href="https://react-chartjs-2.js.org/" target="_blank">React ChartJS 2 v5.0.1</a></li>
|
||||
</ul>
|
||||
<h4>Major features - </h4>
|
||||
<p className=''>Almost all major UI components are available in Daisy UI library. Apart from this logic has been added for following - </p>
|
||||
<ul>
|
||||
<li> <span className='font-bold'>Light/dark</span> mode toggle</li>
|
||||
<li> Token based user authentication</li>
|
||||
<li> <span className='font-bold'>Submenu support</span> in sidebar</li>
|
||||
<li> Store management using <span className='font-bold'>redux toolkit</span></li>
|
||||
<li> <span className='font-bold'>Daisy UI</span> components</li>
|
||||
<li> <span className='font-bold'>Right and left sidebar</span>, Universal loader, notifications and other components</li>
|
||||
<li> React <span className='font-bold'>chart js 2</span> examples</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{/* How to Use */}
|
||||
<h2 id="getstarted2">How to use?</h2>
|
||||
<p>
|
||||
Just clone the repo from github and then run following command (Make sure you have node js installed )<br/>
|
||||
<a href="https://github.com/srobbin01/daisyui-admin-dashboard-template" className='text-sm text-blue-500' target="_blank">Repo Link</a>
|
||||
<br />
|
||||
<code> npm install </code><br />
|
||||
<code>npm start</code>
|
||||
</p>
|
||||
|
||||
|
||||
{/* Tailwind CSS*/}
|
||||
<h2 id="getstarted3">Tailwind CSS</h2>
|
||||
<p>
|
||||
Tailwind CSS is a utility-first CSS framework with predefined classes that you can use to build and design the UI directly in the JSX. We have also included Daisy UI Component, that is based on tailwind CSS.
|
||||
</p>
|
||||
|
||||
{/* Daisy UI */}
|
||||
<h2 id="getstarted4">Daisy UI</h2>
|
||||
|
||||
<p><a href="https://daisyui.com/" target="_blank" className='text-xl btn-link'>Daisy UI</a>, a popular free and opensource tailwind component library has been used for this template. It has a rich collection of components, layouts and is fully customizable and themeable.</p>
|
||||
|
||||
<p>Apart from this it also helps in making HTML code more cleaner as we don't have to include all utility classes of tailwind to make the UI. Check components <a href="https://daisyui.com/components/button/" target="_blank" className='btn-link'>documentation here</a>. For Ex- </p>
|
||||
|
||||
<div className='text-center'>
|
||||
<h2 className='text-xl font-bold mb-0.5'>Creating a button</h2>
|
||||
</div>
|
||||
<div className="">
|
||||
|
||||
<div className='text-center'>
|
||||
<p className='text-center font-semibold'> using only utility classes of tailwind</p>
|
||||
<div className="mockup-code text-justify mb-4">
|
||||
<pre className='my-0 py-0'><code>{'<a className="inline-block px-4 py-3 \n text-sm font-semibold text-center \n text-white uppercase transition duration-200 \n ease-in-out bg-indigo-600 \n rounded-md cursor-pointer \n hover:bg-indigo-700">Button</a>'}</code></pre>
|
||||
</div>
|
||||
<button className="inline-block px-4 py-3 text-sm font-semibold text-center text-white uppercase transition duration-200 ease-in-out bg-indigo-600 rounded-md cursor-pointer hover:bg-indigo-700">Button</button>
|
||||
</div>
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
<div className='grid w-full flex-grow'>
|
||||
<p className='text-center font-semibold'>using daisyUI component classes</p>
|
||||
<div className="mockup-code mb-4">
|
||||
<pre className='my-0 py-0'><code>{'<a className="btn btn-primary">\nButton</a>'}</code></pre>
|
||||
</div>
|
||||
<button className="btn btn-primary">Button</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Chart JS */}
|
||||
<h2 id="getstarted5">Chart JS</h2>
|
||||
<p>
|
||||
Chart JS library has rich components of different charts available. It is based on <a href="https://www.chartjs.org/" target="_blank" alt=""> Chart.js</a> library, the most popular charting library. We have added this library and added couple of examples in seperate page.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
{/* Redux Toolkit */}
|
||||
<h2 id="getstarted6">Redux Toolkit</h2>
|
||||
<p>
|
||||
The Redux Toolkit package helps in writing redux logic easily. It was originally created to help address three common concerns about Redux:
|
||||
<li>Configuring a Redux store is too complicated</li>
|
||||
<li>I have to add a lot of packages to get Redux to do anything useful</li>
|
||||
<li>Redux requires too much boilerplate code"</li>
|
||||
This library has been configured and used for showing notifications, modals and loading data from API in leads page.
|
||||
</p>
|
||||
|
||||
|
||||
{/* Hero Icons */}
|
||||
<h2 id="getstarted7">Hero Icons</h2>
|
||||
<p><a href="https://heroicons.com/" target="_blank" className='text-xl btn-link'>HeroIcons</a> library has been used for all the icons in this templates. It has a rich collection of SVG icons, and is made by the makers of Tailwind CSS.</p>
|
||||
|
||||
<p className='mt-4'>Each icon can be imported individually as a React component, check <a href="https://github.com/tailwindlabs/heroicons" target="_blank" className='btn-link'>documentation</a></p>
|
||||
|
||||
<pre><code>{"import BeakerIcon from '@heroicons/react/24/solid/BeakerIcon'"}</code></pre>
|
||||
<p>Use as follows in your component</p>
|
||||
<pre><code>{"<BeakerIcon className='h-6 w-6'/>"}</code></pre>
|
||||
|
||||
<div className="divider "></div>
|
||||
|
||||
<div className="alert mt-4 alert-warning shadow-lg">
|
||||
<div><span>Note: Importing all icons in single line will increase your build time</span></div>
|
||||
</div>
|
||||
|
||||
<p>Don't import like this (will load all icons and increase build time)</p>
|
||||
<pre><code>{"import {BeakerIcon, BellIcon } from '@heroicons/react/24/solid'"}</code></pre>
|
||||
|
||||
<p>Instead import as follows</p>
|
||||
<pre><code>{"import BeakerIcon from '@heroicons/react/24/solid/BeakerIcon'"}<br />
|
||||
{"import BellIcon from '@heroicons/react/24/solid/BellIcon'"}</code></pre>
|
||||
|
||||
<div className="badge badge-secondary">This is better way for importing icons</div>
|
||||
|
||||
|
||||
|
||||
{/* Project Structure */}
|
||||
<h2 id="getstarted8">Project Structure</h2>
|
||||
<h4>Folders - </h4>
|
||||
<ul className='mt-0'>
|
||||
<li>app - store management, auth and libraries settings are present</li>
|
||||
<li>components - this include all common components to be used in project</li>
|
||||
<li>containers - components related to layout like sidebar, page layout, header etc..</li>
|
||||
<li>features - main folder where all page logic resides, there will be folder for each page and additional folder inside that to group different functionalities like components, modals etc... Redux slice file will also present inside page specific folder.</li>
|
||||
<li>pages - this contain one single file related to one page, if you want to divide page into different components file, use features folder and create seperate folder related to that page</li>
|
||||
<li>routes - all settings related to routes</li>
|
||||
</ul>
|
||||
|
||||
<h4>Files - </h4>
|
||||
<ul className='mt-0'>
|
||||
<li>App.js - Main file containing different routes and components </li>
|
||||
<li>index.css - Additional global css if required</li>
|
||||
<li>index.js - Entry point of project</li>
|
||||
<li>package.json - All dependencies and npm scripts</li>
|
||||
<li>tailwind.config.js - Tailwind CSS configuration file, add theme customization and new themes in this file</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<div className='h-24'></div>
|
||||
|
||||
</article>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default GettingStartedContent
|
|
@ -1,40 +0,0 @@
|
|||
import { useState } from "react"
|
||||
|
||||
function GettingStartedNav({activeIndex}){
|
||||
|
||||
const SECTION_NAVS = [
|
||||
{name : "Introduction", isActive : activeIndex === 1 ? true : false},
|
||||
{name : "How to Use", isActive : false},
|
||||
{name : "Tailwind CSS", isActive : false},
|
||||
{name : "Daisy UI", isActive : false},
|
||||
{name : "Chart JS", isActive : false},
|
||||
{name : "Redux Toolkit", isActive : false},
|
||||
{name : "Hero Icons", isActive : false},
|
||||
{name : "Project Structure", isActive : false},
|
||||
]
|
||||
const [navs, setNavs] = useState(SECTION_NAVS)
|
||||
|
||||
const scrollToSection = (currentIndex) => {
|
||||
setNavs(navs.map((n, k) => {
|
||||
if(k === currentIndex)return {...n, isActive : true}
|
||||
else return {...n, isActive : false}
|
||||
}))
|
||||
document.getElementById('getstarted'+(currentIndex+1)).scrollIntoView({behavior: 'smooth' })
|
||||
}
|
||||
|
||||
return(
|
||||
<ul className="menu w-56 mt-10 text-sm">
|
||||
<li className="menu-title"><span className="">Getting Started</span></li>
|
||||
|
||||
{
|
||||
navs.map((n, k) => {
|
||||
return(
|
||||
<li key={k} onClick={() => scrollToSection(k)} className={n.isActive ? "bordered" : ""}><a>{n.name}</a></li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default GettingStartedNav
|
|
@ -1,60 +0,0 @@
|
|||
import { useState } from "react"
|
||||
import { useDispatch } from "react-redux"
|
||||
import TitleCard from "../../components/Cards/TitleCard"
|
||||
import { showNotification } from "../common/headerSlice"
|
||||
|
||||
|
||||
const INITIAL_INTEGRATION_LIST = [
|
||||
{name : "Slack", icon : "https://cdn-icons-png.flaticon.com/512/2111/2111615.png", isActive : true, description : "Slack is an instant messaging program designed by Slack Technologies and owned by Salesforce."},
|
||||
{name : "Facebook", icon : "https://cdn-icons-png.flaticon.com/512/124/124010.png", isActive : false, description : "Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook."},
|
||||
{name : "Linkedin", icon : "https://cdn-icons-png.flaticon.com/512/174/174857.png", isActive : true, description : "LinkedIn is a business and employment-focused social media platform that works through websites and mobile apps."},
|
||||
{name : "Google Ads", icon : "https://cdn-icons-png.flaticon.com/512/2301/2301145.png", isActive : false, description : "Google Ads is an online advertising platform developed by Google, where advertisers bid to display brief advertisements, service offerings"},
|
||||
{name : "Gmail", icon : "https://cdn-icons-png.flaticon.com/512/5968/5968534.png", isActive : false, description : "Gmail is a free email service provided by Google. As of 2019, it had 1.5 billion active users worldwide."},
|
||||
{name : "Salesforce", icon : "https://cdn-icons-png.flaticon.com/512/5968/5968880.png", isActive : false, description : "It provides customer relationship management software and applications focused on sales, customer service, marketing automation."},
|
||||
{name : "Hubspot", icon : "https://cdn-icons-png.flaticon.com/512/5968/5968872.png", isActive : false, description : "American developer and marketer of software products for inbound marketing, sales, and customer service."},
|
||||
]
|
||||
|
||||
function Integration(){
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [integrationList, setIntegrationList] = useState(INITIAL_INTEGRATION_LIST)
|
||||
|
||||
|
||||
const updateIntegrationStatus = (index) => {
|
||||
let integration = integrationList[index]
|
||||
setIntegrationList(integrationList.map((i, k) => {
|
||||
if(k===index)return {...i, isActive : !i.isActive}
|
||||
return i
|
||||
}))
|
||||
dispatch(showNotification({message : `${integration.name} ${integration.isActive ? "disabled" : "enabled"}` , status : 1}))
|
||||
}
|
||||
|
||||
|
||||
return(
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{
|
||||
integrationList.map((i, k) => {
|
||||
return(
|
||||
<TitleCard key={k} title={i.name} topMargin={"mt-2"}>
|
||||
|
||||
<p className="flex">
|
||||
<img alt="icon" src={i.icon} className="w-12 h-12 inline-block mr-4" />
|
||||
{i.description}
|
||||
</p>
|
||||
<div className="mt-6 text-right">
|
||||
<input type="checkbox" className="toggle toggle-success toggle-lg" checked={i.isActive} onChange={() => updateIntegrationStatus(k)}/>
|
||||
</div>
|
||||
|
||||
</TitleCard>
|
||||
)
|
||||
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Integration
|
|
@ -1,17 +1,17 @@
|
|||
import { useState } from "react"
|
||||
import { useDispatch } from "react-redux"
|
||||
import InputText from '@/Components/Input/InputText'
|
||||
import ErrorText from '@/Components/Typography/ErrorText'
|
||||
import { showNotification } from '@/Components/features/common/headerSlice'
|
||||
import InputText from '../../../components/Input/InputText'
|
||||
import ErrorText from '../../../components/Typography/ErrorText'
|
||||
import { showNotification } from "../../common/headerSlice"
|
||||
import { addNewLead } from "../leadSlice"
|
||||
|
||||
const INITIAL_LEAD_OBJ = {
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
email: ""
|
||||
first_name : "",
|
||||
last_name : "",
|
||||
email : ""
|
||||
}
|
||||
|
||||
function AddLeadModalBody({ closeModal }) {
|
||||
function AddLeadModalBody({closeModal}){
|
||||
const dispatch = useDispatch()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState("")
|
||||
|
@ -19,9 +19,9 @@ function AddLeadModalBody({ closeModal }) {
|
|||
|
||||
|
||||
const saveNewLead = () => {
|
||||
if (leadObj.first_name.trim() === "") return setErrorMessage("First Name is required!")
|
||||
else if (leadObj.email.trim() === "") return setErrorMessage("Email id is required!")
|
||||
else {
|
||||
if(leadObj.first_name.trim() === "")return setErrorMessage("First Name is required!")
|
||||
else if(leadObj.email.trim() === "")return setErrorMessage("Email id is required!")
|
||||
else{
|
||||
let newLeadObj = {
|
||||
"id": 7,
|
||||
"email": leadObj.email,
|
||||
|
@ -29,31 +29,31 @@ function AddLeadModalBody({ closeModal }) {
|
|||
"last_name": leadObj.last_name,
|
||||
"avatar": "https://reqres.in/img/faces/1-image.jpg"
|
||||
}
|
||||
dispatch(addNewLead({ newLeadObj }))
|
||||
dispatch(showNotification({ message: "New Lead Added!", status: 1 }))
|
||||
dispatch(addNewLead({newLeadObj}))
|
||||
dispatch(showNotification({message : "New Lead Added!", status : 1}))
|
||||
closeModal()
|
||||
}
|
||||
}
|
||||
|
||||
const updateFormValue = ({ updateType, value }) => {
|
||||
const updateFormValue = ({updateType, value}) => {
|
||||
setErrorMessage("")
|
||||
setLeadObj({ ...leadObj, [updateType]: value })
|
||||
setLeadObj({...leadObj, [updateType] : value})
|
||||
}
|
||||
|
||||
return (
|
||||
return(
|
||||
<>
|
||||
|
||||
<InputText type="text" defaultValue={leadObj.first_name} updateType="first_name" containerStyle="mt-4" labelTitle="First Name" updateFormValue={updateFormValue} />
|
||||
<InputText type="text" defaultValue={leadObj.first_name} updateType="first_name" containerStyle="mt-4" labelTitle="First Name" updateFormValue={updateFormValue}/>
|
||||
|
||||
<InputText type="text" defaultValue={leadObj.last_name} updateType="last_name" containerStyle="mt-4" labelTitle="Last Name" updateFormValue={updateFormValue} />
|
||||
<InputText type="text" defaultValue={leadObj.last_name} updateType="last_name" containerStyle="mt-4" labelTitle="Last Name" updateFormValue={updateFormValue}/>
|
||||
|
||||
<InputText type="email" defaultValue={leadObj.email} updateType="email" containerStyle="mt-4" labelTitle="Email Id" updateFormValue={updateFormValue} />
|
||||
<InputText type="email" defaultValue={leadObj.email} updateType="email" containerStyle="mt-4" labelTitle="Email Id" updateFormValue={updateFormValue}/>
|
||||
|
||||
|
||||
<ErrorText styleClass="mt-16">{errorMessage}</ErrorText>
|
||||
<div className="modal-action">
|
||||
<button className="btn btn-ghost" onClick={() => closeModal()}>Cancel</button>
|
||||
<button className="btn btn-primary px-6" onClick={() => saveNewLead()}>Save</button>
|
||||
<button className="btn btn-ghost" onClick={() => closeModal()}>Cancel</button>
|
||||
<button className="btn btn-primary px-6" onClick={() => saveNewLead()}>Save</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -4,43 +4,43 @@ import axios from 'axios'
|
|||
|
||||
|
||||
export const getLeadsContent = createAsyncThunk('/leads/content', async () => {
|
||||
const response = await axios.get('/api/users?page=2', {})
|
||||
return response.data;
|
||||
const response = await axios.get('/api/users?page=2', {})
|
||||
return response.data;
|
||||
})
|
||||
|
||||
export const leadsSlice = createSlice({
|
||||
name: 'leads',
|
||||
initialState: {
|
||||
isLoading: false,
|
||||
leads : []
|
||||
leads: []
|
||||
},
|
||||
reducers: {
|
||||
|
||||
|
||||
addNewLead: (state, action) => {
|
||||
let {newLeadObj} = action.payload
|
||||
let { newLeadObj } = action.payload
|
||||
state.leads = [...state.leads, newLeadObj]
|
||||
},
|
||||
|
||||
deleteLead: (state, action) => {
|
||||
let {index} = action.payload
|
||||
let { index } = action.payload
|
||||
state.leads.splice(index, 1)
|
||||
}
|
||||
},
|
||||
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(getLeadsContent.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
})
|
||||
.addCase(getLeadsContent.fulfilled, (state, action) => {
|
||||
state.leads = action.payload.data;
|
||||
state.isLoading = false;
|
||||
})
|
||||
.addCase(getLeadsContent.rejected, (state) => {
|
||||
state.isLoading = false;
|
||||
});
|
||||
}
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(getLeadsContent.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
})
|
||||
.addCase(getLeadsContent.fulfilled, (state, action) => {
|
||||
state.leads = action.payload.data;
|
||||
state.isLoading = false;
|
||||
})
|
||||
.addCase(getLeadsContent.rejected, (state) => {
|
||||
state.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
import {useState, useRef} from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
import LandingIntro from './LandingIntro'
|
||||
import ErrorText from '../../components/Typography/ErrorText'
|
||||
import InputText from '../../components/Input/InputText'
|
||||
import CheckCircleIcon from '@heroicons/react/24/solid/CheckCircleIcon'
|
||||
|
||||
function ForgotPassword(){
|
||||
|
||||
const INITIAL_USER_OBJ = {
|
||||
emailId : ""
|
||||
}
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState("")
|
||||
const [linkSent, setLinkSent] = useState(false)
|
||||
const [userObj, setUserObj] = useState(INITIAL_USER_OBJ)
|
||||
|
||||
const submitForm = (e) =>{
|
||||
e.preventDefault()
|
||||
setErrorMessage("")
|
||||
|
||||
if(userObj.emailId.trim() === "")return setErrorMessage("Email Id is required! (use any value)")
|
||||
else{
|
||||
setLoading(true)
|
||||
// Call API to send password reset link
|
||||
setLoading(false)
|
||||
setLinkSent(true)
|
||||
}
|
||||
}
|
||||
|
||||
const updateFormValue = ({updateType, value}) => {
|
||||
setErrorMessage("")
|
||||
setUserObj({...userObj, [updateType] : value})
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="min-h-screen bg-base-200 flex items-center">
|
||||
<div className="card mx-auto w-full max-w-5xl shadow-xl">
|
||||
<div className="grid md:grid-cols-2 grid-cols-1 bg-base-100 rounded-xl">
|
||||
<div className=''>
|
||||
<LandingIntro />
|
||||
</div>
|
||||
<div className='py-24 px-10'>
|
||||
<h2 className='text-2xl font-semibold mb-2 text-center'>Forgot Password</h2>
|
||||
|
||||
{
|
||||
linkSent &&
|
||||
<>
|
||||
<div className='text-center mt-8'><CheckCircleIcon className='inline-block w-32 text-success'/></div>
|
||||
<p className='my-4 text-xl font-bold text-center'>Link Sent</p>
|
||||
<p className='mt-4 mb-8 font-semibold text-center'>Check your email to reset password</p>
|
||||
<div className='text-center mt-4'><Link to="/login"><button className="btn btn-block btn-primary ">Login</button></Link></div>
|
||||
|
||||
</>
|
||||
}
|
||||
|
||||
{
|
||||
!linkSent &&
|
||||
<>
|
||||
<p className='my-8 font-semibold text-center'>We will send password reset link on your email Id</p>
|
||||
<form onSubmit={(e) => submitForm(e)}>
|
||||
|
||||
<div className="mb-4">
|
||||
|
||||
<InputText type="emailId" defaultValue={userObj.emailId} updateType="emailId" containerStyle="mt-4" labelTitle="Email Id" updateFormValue={updateFormValue}/>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<ErrorText styleClass="mt-12">{errorMessage}</ErrorText>
|
||||
<button type="submit" className={"btn mt-2 w-full btn-primary" + (loading ? " loading" : "")}>Send Reset Link</button>
|
||||
|
||||
<div className='text-center mt-4'>Don't have an account yet? <Link to="/register"><button className=" inline-block hover:text-primary hover:underline hover:cursor-pointer transition duration-200">Register</button></Link></div>
|
||||
</form>
|
||||
</>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ForgotPassword
|
|
@ -1,27 +0,0 @@
|
|||
import TemplatePointers from "./components/TemplatePointers"
|
||||
|
||||
|
||||
|
||||
function LandingIntro(){
|
||||
|
||||
return(
|
||||
<div className="hero min-h-full rounded-l-xl bg-base-200">
|
||||
<div className="hero-content py-12">
|
||||
<div className="max-w-md">
|
||||
|
||||
<h1 className='text-3xl text-center font-bold '><img src="/logo192.png" className="w-12 inline-block mr-2 mask mask-circle" alt="dashwind-logo" />DashWind</h1>
|
||||
|
||||
<div className="text-center mt-12"><img src="./intro.png" alt="Dashwind Admin Template" className="w-48 inline-block"></img></div>
|
||||
|
||||
{/* Importing pointers component */}
|
||||
<TemplatePointers />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default LandingIntro
|
|
@ -1,72 +0,0 @@
|
|||
import {useState, useRef} from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
import LandingIntro from './LandingIntro'
|
||||
import ErrorText from '../../components/Typography/ErrorText'
|
||||
import InputText from '../../components/Input/InputText'
|
||||
|
||||
function Login(){
|
||||
|
||||
const INITIAL_LOGIN_OBJ = {
|
||||
password : "",
|
||||
emailId : ""
|
||||
}
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState("")
|
||||
const [loginObj, setLoginObj] = useState(INITIAL_LOGIN_OBJ)
|
||||
|
||||
const submitForm = (e) =>{
|
||||
e.preventDefault()
|
||||
setErrorMessage("")
|
||||
|
||||
if(loginObj.emailId.trim() === "")return setErrorMessage("Email Id is required! (use any value)")
|
||||
if(loginObj.password.trim() === "")return setErrorMessage("Password is required! (use any value)")
|
||||
else{
|
||||
setLoading(true)
|
||||
// Call API to check user credentials and save token in localstorage
|
||||
localStorage.setItem("token", "DumyTokenHere")
|
||||
setLoading(false)
|
||||
window.location.href = '/app/welcome'
|
||||
}
|
||||
}
|
||||
|
||||
const updateFormValue = ({updateType, value}) => {
|
||||
setErrorMessage("")
|
||||
setLoginObj({...loginObj, [updateType] : value})
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="min-h-screen bg-base-200 flex items-center">
|
||||
<div className="card mx-auto w-full max-w-5xl shadow-xl">
|
||||
<div className="grid md:grid-cols-2 grid-cols-1 bg-base-100 rounded-xl">
|
||||
<div className=''>
|
||||
<LandingIntro />
|
||||
</div>
|
||||
<div className='py-24 px-10'>
|
||||
<h2 className='text-2xl font-semibold mb-2 text-center'>Login</h2>
|
||||
<form onSubmit={(e) => submitForm(e)}>
|
||||
|
||||
<div className="mb-4">
|
||||
|
||||
<InputText type="emailId" defaultValue={loginObj.emailId} updateType="emailId" containerStyle="mt-4" labelTitle="Email Id" updateFormValue={updateFormValue}/>
|
||||
|
||||
<InputText defaultValue={loginObj.password} type="password" updateType="password" containerStyle="mt-4" labelTitle="Password" updateFormValue={updateFormValue}/>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='text-right text-primary'><Link to="/forgot-password"><span className="text-sm inline-block hover:text-primary hover:underline hover:cursor-pointer transition duration-200">Forgot Password?</span></Link>
|
||||
</div>
|
||||
|
||||
<ErrorText styleClass="mt-8">{errorMessage}</ErrorText>
|
||||
<button type="submit" className={"btn mt-2 w-full btn-primary" + (loading ? " loading" : "")}>Login</button>
|
||||
|
||||
<div className='text-center mt-4'>Don't have an account yet? <Link to="/register"><span className=" inline-block hover:text-primary hover:underline hover:cursor-pointer transition duration-200">Register</span></Link></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login
|
|
@ -1,73 +0,0 @@
|
|||
import {useState, useRef} from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
import LandingIntro from './LandingIntro'
|
||||
import ErrorText from '../../components/Typography/ErrorText'
|
||||
import InputText from '../../components/Input/InputText'
|
||||
|
||||
function Register(){
|
||||
|
||||
const INITIAL_REGISTER_OBJ = {
|
||||
name : "",
|
||||
password : "",
|
||||
emailId : ""
|
||||
}
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState("")
|
||||
const [registerObj, setRegisterObj] = useState(INITIAL_REGISTER_OBJ)
|
||||
|
||||
const submitForm = (e) =>{
|
||||
e.preventDefault()
|
||||
setErrorMessage("")
|
||||
|
||||
if(registerObj.name.trim() === "")return setErrorMessage("Name is required! (use any value)")
|
||||
if(registerObj.emailId.trim() === "")return setErrorMessage("Email Id is required! (use any value)")
|
||||
if(registerObj.password.trim() === "")return setErrorMessage("Password is required! (use any value)")
|
||||
else{
|
||||
setLoading(true)
|
||||
// Call API to check user credentials and save token in localstorage
|
||||
localStorage.setItem("token", "DumyTokenHere")
|
||||
setLoading(false)
|
||||
window.location.href = '/app/welcome'
|
||||
}
|
||||
}
|
||||
|
||||
const updateFormValue = ({updateType, value}) => {
|
||||
setErrorMessage("")
|
||||
setRegisterObj({...registerObj, [updateType] : value})
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="min-h-screen bg-base-200 flex items-center">
|
||||
<div className="card mx-auto w-full max-w-5xl shadow-xl">
|
||||
<div className="grid md:grid-cols-2 grid-cols-1 bg-base-100 rounded-xl">
|
||||
<div className=''>
|
||||
<LandingIntro />
|
||||
</div>
|
||||
<div className='py-24 px-10'>
|
||||
<h2 className='text-2xl font-semibold mb-2 text-center'>Register</h2>
|
||||
<form onSubmit={(e) => submitForm(e)}>
|
||||
|
||||
<div className="mb-4">
|
||||
|
||||
<InputText defaultValue={registerObj.name} updateType="name" containerStyle="mt-4" labelTitle="Name" updateFormValue={updateFormValue}/>
|
||||
|
||||
<InputText defaultValue={registerObj.emailId} updateType="emailId" containerStyle="mt-4" labelTitle="Email Id" updateFormValue={updateFormValue}/>
|
||||
|
||||
<InputText defaultValue={registerObj.password} type="password" updateType="password" containerStyle="mt-4" labelTitle="Password" updateFormValue={updateFormValue}/>
|
||||
|
||||
</div>
|
||||
|
||||
<ErrorText styleClass="mt-8">{errorMessage}</ErrorText>
|
||||
<button type="submit" className={"btn mt-2 w-full btn-primary" + (loading ? " loading" : "")}>Register</button>
|
||||
|
||||
<div className='text-center mt-4'>Already have an account? <Link to="/login"><span className=" inline-block hover:text-primary hover:underline hover:cursor-pointer transition duration-200">Login</span></Link></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Register
|
|
@ -1,14 +0,0 @@
|
|||
function TemplatePointers(){
|
||||
return(
|
||||
<>
|
||||
<h1 className="text-2xl mt-8 font-bold">Admin Dashboard Starter Kit</h1>
|
||||
<p className="py-2 mt-4">✓ <span className="font-semibold">Light/dark</span> mode toggle</p>
|
||||
<p className="py-2 ">✓ <span className="font-semibold">Redux toolkit</span> and other utility libraries configured</p>
|
||||
<p className="py-2">✓ <span className="font-semibold">Calendar, Modal, Sidebar </span> components</p>
|
||||
<p className="py-2 ">✓ User-friendly <span className="font-semibold">documentation</span></p>
|
||||
<p className="py-2 mb-4">✓ <span className="font-semibold">Daisy UI</span> components, <span className="font-semibold">Tailwind CSS</span> support</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TemplatePointers
|
|
@ -1,59 +0,0 @@
|
|||
import { useEffect } from 'react';
|
||||
import GuestLayout from '@/Layouts/GuestLayout';
|
||||
import InputError from '@/Components/InputError';
|
||||
import InputLabel from '@/Components/InputLabel';
|
||||
import PrimaryButton from '@/Components/PrimaryButton';
|
||||
import TextInput from '@/Components/TextInput';
|
||||
import { Head, useForm } from '@inertiajs/react';
|
||||
|
||||
export default function ConfirmPassword() {
|
||||
const { data, setData, post, processing, errors, reset } = useForm({
|
||||
password: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
reset('password');
|
||||
};
|
||||
}, []);
|
||||
|
||||
const submit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
post(route('password.confirm'));
|
||||
};
|
||||
|
||||
return (
|
||||
<GuestLayout>
|
||||
<Head title="Confirm Password" />
|
||||
|
||||
<div className="mb-4 text-sm text-gray-600">
|
||||
This is a secure area of the application. Please confirm your password before continuing.
|
||||
</div>
|
||||
|
||||
<form onSubmit={submit}>
|
||||
<div className="mt-4">
|
||||
<InputLabel htmlFor="password" value="Password" />
|
||||
|
||||
<TextInput
|
||||
id="password"
|
||||
type="password"
|
||||
name="password"
|
||||
value={data.password}
|
||||
className="mt-1 block w-full"
|
||||
isFocused={true}
|
||||
onChange={(e) => setData('password', e.target.value)}
|
||||
/>
|
||||
|
||||
<InputError message={errors.password} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end mt-4">
|
||||
<PrimaryButton className="ms-4" disabled={processing}>
|
||||
Confirm
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</GuestLayout>
|
||||
);
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import GuestLayout from '@/Layouts/GuestLayout';
|
||||
import InputError from '@/Components/InputError';
|
||||
import PrimaryButton from '@/Components/PrimaryButton';
|
||||
import TextInput from '@/Components/TextInput';
|
||||
import { Head, useForm } from '@inertiajs/react';
|
||||
|
||||
export default function ForgotPassword({ status }) {
|
||||
const { data, setData, post, processing, errors } = useForm({
|
||||
email: '',
|
||||
});
|
||||
|
||||
const submit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
post(route('password.email'));
|
||||
};
|
||||
|
||||
return (
|
||||
<GuestLayout>
|
||||
<Head title="Forgot Password" />
|
||||
|
||||
<div className="mb-4 text-sm text-gray-600">
|
||||
Forgot your password? No problem. Just let us know your email address and we will email you a password
|
||||
reset link that will allow you to choose a new one.
|
||||
</div>
|
||||
|
||||
{status && <div className="mb-4 font-medium text-sm text-green-600">{status}</div>}
|
||||
|
||||
<form onSubmit={submit}>
|
||||
<TextInput
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
value={data.email}
|
||||
className="mt-1 block w-full"
|
||||
isFocused={true}
|
||||
onChange={(e) => setData('email', e.target.value)}
|
||||
/>
|
||||
|
||||
<InputError message={errors.email} className="mt-2" />
|
||||
|
||||
<div className="flex items-center justify-end mt-4">
|
||||
<PrimaryButton className="ms-4" disabled={processing}>
|
||||
Email Password Reset Link
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</GuestLayout>
|
||||
);
|
||||
}
|
|
@ -1,15 +1,10 @@
|
|||
import { useEffect } from 'react';
|
||||
import Checkbox from '../../Components/Checkbox';
|
||||
import GuestLayout from '../../Layouts/GuestLayout';
|
||||
import InputError from '../../Components/InputError';
|
||||
import InputLabel from '../../Components/InputLabel';
|
||||
import PrimaryButton from '../../Components/PrimaryButton';
|
||||
import TextInput from '../../Components/TextInput';
|
||||
import { Head, Link, useForm } from '@inertiajs/react';
|
||||
// import { InertiaLink, usePage } from '@inertiajs/react';
|
||||
|
||||
export default function Login({ status, canResetPassword }) {
|
||||
function Login({ status }) {
|
||||
const { data, setData, post, processing, errors, reset } = useForm({
|
||||
email: '',
|
||||
nis: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
});
|
||||
|
@ -22,76 +17,83 @@ export default function Login({ status, canResetPassword }) {
|
|||
|
||||
const submit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
post(route('login'));
|
||||
};
|
||||
|
||||
return (
|
||||
<GuestLayout>
|
||||
<Head title="Log in" />
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="w-full max-w-md p-8 rounded-lg bg-gray-800">
|
||||
<Head title="Log in" />
|
||||
|
||||
{status && <div className="mb-4 font-medium text-sm text-green-600">{status}</div>}
|
||||
<h1 className="text-3xl font-bold mb-8 text-white">Masuk Untuk Melanjutkan</h1>
|
||||
|
||||
<form onSubmit={submit}>
|
||||
<div>
|
||||
<InputLabel htmlFor="email" value="Email" />
|
||||
{status && <div className="mb-4 font-medium text-sm text-green-500">{status}</div>}
|
||||
|
||||
<TextInput
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
value={data.email}
|
||||
className="mt-1 block w-full"
|
||||
autoComplete="username"
|
||||
isFocused={true}
|
||||
onChange={(e) => setData('email', e.target.value)}
|
||||
/>
|
||||
|
||||
<InputError message={errors.email} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<InputLabel htmlFor="password" value="Password" />
|
||||
|
||||
<TextInput
|
||||
id="password"
|
||||
type="password"
|
||||
name="password"
|
||||
value={data.password}
|
||||
className="mt-1 block w-full"
|
||||
autoComplete="current-password"
|
||||
onChange={(e) => setData('password', e.target.value)}
|
||||
/>
|
||||
|
||||
<InputError message={errors.password} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="block mt-4">
|
||||
<label className="flex items-center">
|
||||
<Checkbox
|
||||
name="remember"
|
||||
checked={data.remember}
|
||||
onChange={(e) => setData('remember', e.target.checked)}
|
||||
<form onSubmit={submit}>
|
||||
<div className="mb-6">
|
||||
<label htmlFor="nis" className="block text-xl mb-2 text-white">
|
||||
Masukkan NIS
|
||||
</label>
|
||||
<input
|
||||
id="nis"
|
||||
type="text"
|
||||
name="nis"
|
||||
value={data.nis}
|
||||
className="w-full p-3 border border-gray-700 rounded-lg text-gray-700"
|
||||
placeholder="name@company.com"
|
||||
autoComplete="username"
|
||||
onChange={(e) => setData('nis', e.target.value)}
|
||||
/>
|
||||
<span className="ms-2 text-sm text-gray-600">Remember me</span>
|
||||
</label>
|
||||
</div>
|
||||
{errors.nis && <p className="mt-1 text-sm text-red-500">{errors.nis}</p>}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end mt-4">
|
||||
{canResetPassword && (
|
||||
<Link
|
||||
href={route('password.request')}
|
||||
className="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Forgot your password?
|
||||
</Link>
|
||||
)}
|
||||
<div className="mb-6">
|
||||
<label htmlFor="password" className="block text-xl mb-2 text-white">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
name="password"
|
||||
value={data.password}
|
||||
className="w-full p-3 border border-gray-700 rounded-lg"
|
||||
placeholder="••••••••"
|
||||
autoComplete="current-password"
|
||||
onChange={(e) => setData('password', e.target.value)}
|
||||
/>
|
||||
{errors.password && <p className="mt-1 text-sm text-red-500">{errors.password}</p>}
|
||||
</div>
|
||||
|
||||
<PrimaryButton className="ms-4" disabled={processing}>
|
||||
Log in
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</GuestLayout>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="remember"
|
||||
type="checkbox"
|
||||
className="w-4 h-4 border border-gray-700 rounded"
|
||||
name="remember"
|
||||
checked={data.remember}
|
||||
onChange={(e) => setData('remember', e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="remember" className="ml-2 text-sm text-white">
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={processing}
|
||||
className="w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg text-center"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Login.layout = (page) => page;
|
||||
|
||||
export default Login;
|
||||
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
import { useEffect } from 'react';
|
||||
import GuestLayout from '@/Layouts/GuestLayout';
|
||||
import InputError from '@/Components/InputError';
|
||||
import InputLabel from '@/Components/InputLabel';
|
||||
import PrimaryButton from '@/Components/PrimaryButton';
|
||||
import TextInput from '@/Components/TextInput';
|
||||
import { Head, Link, useForm } from '@inertiajs/react';
|
||||
|
||||
export default function Register() {
|
||||
const { data, setData, post, processing, errors, reset } = useForm({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
reset('password', 'password_confirmation');
|
||||
};
|
||||
}, []);
|
||||
|
||||
const submit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
post(route('register'));
|
||||
};
|
||||
|
||||
return (
|
||||
<GuestLayout>
|
||||
<Head title="Register" />
|
||||
|
||||
<form onSubmit={submit}>
|
||||
<div>
|
||||
<InputLabel htmlFor="name" value="Name" />
|
||||
|
||||
<TextInput
|
||||
id="name"
|
||||
name="name"
|
||||
value={data.name}
|
||||
className="mt-1 block w-full"
|
||||
autoComplete="name"
|
||||
isFocused={true}
|
||||
onChange={(e) => setData('name', e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<InputError message={errors.name} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<InputLabel htmlFor="email" value="Email" />
|
||||
|
||||
<TextInput
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
value={data.email}
|
||||
className="mt-1 block w-full"
|
||||
autoComplete="username"
|
||||
onChange={(e) => setData('email', e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<InputError message={errors.email} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<InputLabel htmlFor="password" value="Password" />
|
||||
|
||||
<TextInput
|
||||
id="password"
|
||||
type="password"
|
||||
name="password"
|
||||
value={data.password}
|
||||
className="mt-1 block w-full"
|
||||
autoComplete="new-password"
|
||||
onChange={(e) => setData('password', e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<InputError message={errors.password} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<InputLabel htmlFor="password_confirmation" value="Confirm Password" />
|
||||
|
||||
<TextInput
|
||||
id="password_confirmation"
|
||||
type="password"
|
||||
name="password_confirmation"
|
||||
value={data.password_confirmation}
|
||||
className="mt-1 block w-full"
|
||||
autoComplete="new-password"
|
||||
onChange={(e) => setData('password_confirmation', e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<InputError message={errors.password_confirmation} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end mt-4">
|
||||
<Link
|
||||
href={route('login')}
|
||||
className="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Already registered?
|
||||
</Link>
|
||||
|
||||
<PrimaryButton className="ms-4" disabled={processing}>
|
||||
Register
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</GuestLayout>
|
||||
);
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
import { useEffect } from 'react';
|
||||
import GuestLayout from '@/Layouts/GuestLayout';
|
||||
import InputError from '@/Components/InputError';
|
||||
import InputLabel from '@/Components/InputLabel';
|
||||
import PrimaryButton from '@/Components/PrimaryButton';
|
||||
import TextInput from '@/Components/TextInput';
|
||||
import { Head, useForm } from '@inertiajs/react';
|
||||
|
||||
export default function ResetPassword({ token, email }) {
|
||||
const { data, setData, post, processing, errors, reset } = useForm({
|
||||
token: token,
|
||||
email: email,
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
reset('password', 'password_confirmation');
|
||||
};
|
||||
}, []);
|
||||
|
||||
const submit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
post(route('password.store'));
|
||||
};
|
||||
|
||||
return (
|
||||
<GuestLayout>
|
||||
<Head title="Reset Password" />
|
||||
|
||||
<form onSubmit={submit}>
|
||||
<div>
|
||||
<InputLabel htmlFor="email" value="Email" />
|
||||
|
||||
<TextInput
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
value={data.email}
|
||||
className="mt-1 block w-full"
|
||||
autoComplete="username"
|
||||
onChange={(e) => setData('email', e.target.value)}
|
||||
/>
|
||||
|
||||
<InputError message={errors.email} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<InputLabel htmlFor="password" value="Password" />
|
||||
|
||||
<TextInput
|
||||
id="password"
|
||||
type="password"
|
||||
name="password"
|
||||
value={data.password}
|
||||
className="mt-1 block w-full"
|
||||
autoComplete="new-password"
|
||||
isFocused={true}
|
||||
onChange={(e) => setData('password', e.target.value)}
|
||||
/>
|
||||
|
||||
<InputError message={errors.password} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<InputLabel htmlFor="password_confirmation" value="Confirm Password" />
|
||||
|
||||
<TextInput
|
||||
type="password"
|
||||
name="password_confirmation"
|
||||
value={data.password_confirmation}
|
||||
className="mt-1 block w-full"
|
||||
autoComplete="new-password"
|
||||
onChange={(e) => setData('password_confirmation', e.target.value)}
|
||||
/>
|
||||
|
||||
<InputError message={errors.password_confirmation} className="mt-2" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end mt-4">
|
||||
<PrimaryButton className="ms-4" disabled={processing}>
|
||||
Reset Password
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</GuestLayout>
|
||||
);
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import GuestLayout from '@/Layouts/GuestLayout';
|
||||
import PrimaryButton from '@/Components/PrimaryButton';
|
||||
import { Head, Link, useForm } from '@inertiajs/react';
|
||||
|
||||
export default function VerifyEmail({ status }) {
|
||||
const { post, processing } = useForm({});
|
||||
|
||||
const submit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
post(route('verification.send'));
|
||||
};
|
||||
|
||||
return (
|
||||
<GuestLayout>
|
||||
<Head title="Email Verification" />
|
||||
|
||||
<div className="mb-4 text-sm text-gray-600">
|
||||
Thanks for signing up! Before getting started, could you verify your email address by clicking on the
|
||||
link we just emailed to you? If you didn't receive the email, we will gladly send you another.
|
||||
</div>
|
||||
|
||||
{status === 'verification-link-sent' && (
|
||||
<div className="mb-4 font-medium text-sm text-green-600">
|
||||
A new verification link has been sent to the email address you provided during registration.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={submit}>
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<PrimaryButton disabled={processing}>Resend Verification Email</PrimaryButton>
|
||||
|
||||
<Link
|
||||
href={route('logout')}
|
||||
method="post"
|
||||
as="button"
|
||||
className="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Log Out
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</GuestLayout>
|
||||
);
|
||||
}
|
|
@ -1,21 +1,216 @@
|
|||
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
|
||||
import React from 'react';
|
||||
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement } from 'chart.js';
|
||||
import { Bar, Doughnut } from 'react-chartjs-2';
|
||||
import { Head } from '@inertiajs/react';
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||
import Squares2X2Icon from '@heroicons/react/24/outline/Squares2X2Icon'
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement);
|
||||
|
||||
const Dashboard = () => {
|
||||
const barChartData = {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'CHN',
|
||||
data: [12, 19, 8, 15, 12, 8, 16, 20, 15, 14],
|
||||
backgroundColor: 'rgb(196, 151, 239)',
|
||||
},
|
||||
{
|
||||
label: 'USA',
|
||||
data: [19, 12, 15, 8, 9, 16, 14, 12, 10, 15],
|
||||
backgroundColor: 'rgb(86, 207, 225)',
|
||||
},
|
||||
{
|
||||
label: 'UK',
|
||||
data: [15, 15, 10, 12, 20, 14, 8, 16, 10, 18],
|
||||
backgroundColor: 'rgb(255, 170, 145)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const doughnutChartData = {
|
||||
labels: ['Social Media', 'Direct', 'Email', 'Organic'],
|
||||
datasets: [
|
||||
{
|
||||
data: [35, 30, 15, 20],
|
||||
backgroundColor: [
|
||||
'rgb(255, 99, 132)',
|
||||
'rgb(54, 162, 235)',
|
||||
'rgb(153, 102, 255)',
|
||||
'rgb(75, 192, 192)',
|
||||
],
|
||||
borderWidth: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const barChartOptions = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
|
||||
const doughnutChartOptions = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
cutout: '70%',
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setPageTitle("Dashboard"));
|
||||
}, [dispatch]);
|
||||
|
||||
export default function Dashboard({ auth }) {
|
||||
return (
|
||||
<AuthenticatedLayout
|
||||
user={auth.user}
|
||||
header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Dashboard</h2>}
|
||||
>
|
||||
<div className="card bg-base-100 shadow-xl">
|
||||
<Head title="Dashboard" />
|
||||
<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">
|
||||
<Squares2X2Icon className="h-6 w-6 text-white" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold">Dashboard</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="py-12">
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div className="p-6 text-gray-900">You're logged in!</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div className="rounded-lg overflow-hidden bg-gradient-to-r from-orange-300 to-pink-400 text-white p-6">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<p className="text-xl font-medium mb-4">Weekly Sales</p>
|
||||
<p className="text-4xl font-bold mb-6">$ 15,0000</p>
|
||||
<p>Increased by 60%</p>
|
||||
</div>
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10 text-white opacity-80" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg overflow-hidden bg-gradient-to-r from-blue-300 to-blue-500 text-white p-6">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<p className="text-xl font-medium mb-4">Weekly Orders</p>
|
||||
<p className="text-4xl font-bold mb-6">45,6334</p>
|
||||
<p>Decreased by 10%</p>
|
||||
</div>
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10 text-white opacity-80" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg overflow-hidden bg-gradient-to-r from-green-300 to-teal-500 text-white p-6">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<p className="text-xl font-medium mb-4">Visitors Online</p>
|
||||
<p className="text-4xl font-bold mb-6">95,5741</p>
|
||||
<p>Increased by 5%</p>
|
||||
</div>
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10 text-white opacity-80" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 12l-1.25-1.25M12 20l1.25-1.25M4 12l1.25 1.25M12 4l-1.25 1.25M20 12h-2M12 20v-2M4 12h2M12 4v2" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<div className=" rounded-lg p-6 shadow">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-xl font-bold">Visit And Sales Statistics</h2>
|
||||
<div className="flex space-x-4">
|
||||
<div className="flex items-center">
|
||||
<span className="h-3 w-3 rounded-full bg-purple-400 mr-1"></span>
|
||||
<span className="text-gray-500 text-sm">CHN</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="h-3 w-3 rounded-full bg-cyan-400 mr-1"></span>
|
||||
<span className="text-gray-500 text-sm">USA</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="h-3 w-3 rounded-full bg-orange-300 mr-1"></span>
|
||||
<span className="text-gray-500 text-sm">UK</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-64">
|
||||
<Bar data={barChartData} options={barChartOptions} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg p-6 shadow">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-bold">Traffic Sources</h2>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<div className="h-64 w-64">
|
||||
<Doughnut data={doughnutChartData} options={doughnutChartOptions} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mt-6">
|
||||
<div className="flex items-center justify-center">
|
||||
<span className="h-3 w-3 rounded-full bg-pink-400 mr-2"></span>
|
||||
<span className="text-gray-700 text-sm">Social Media</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<span className="h-3 w-3 rounded-full bg-blue-400 mr-2"></span>
|
||||
<span className="text-gray-700 text-sm">Direct</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<span className="h-3 w-3 rounded-full bg-purple-400 mr-2"></span>
|
||||
<span className="text-gray-700 text-sm">Email</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<span className="h-3 w-3 rounded-full bg-teal-400 mr-2"></span>
|
||||
<span className="text-gray-700 text-sm">Organic</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AuthenticatedLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Dashboard;
|
|
@ -1,90 +1,214 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Head } from '@inertiajs/react';
|
||||
import ModalInput from '@/Components/ModalInput';
|
||||
import Layout from '@/Components/Layout'
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||
import { CurrencyDollarIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
|
||||
export default function ManualPayment({ santri, penalty, bill, fields, options }) {
|
||||
export default function ManualPayment({ santri, fields, options }) {
|
||||
const [selectedSantri, setSelectedSantri] = useState(null);
|
||||
// console.log(LeftSidebar)
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [filteredSantri, setFilteredSantri] = useState([]);
|
||||
const [selectedPayments, setSelectedPayments] = useState(null);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setPageTitle("Data Payment"));
|
||||
}, [dispatch]);
|
||||
|
||||
// console.log(santri.id)
|
||||
useEffect(() => {
|
||||
if (santri) {
|
||||
setFilteredSantri(
|
||||
santri.filter(item =>
|
||||
item.nama.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.nis.toString().includes(searchTerm)
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [searchTerm, santri]);
|
||||
|
||||
const handleSearch = (e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className='text-red-500'>
|
||||
{/* <Layout /> */}
|
||||
<div>
|
||||
<Head title="Manual Payment" />
|
||||
<ModalInput showPayments={true} fields={fields} options={options} tableName='payments' initialData={selectedSantri} onClose={() => setSelectedSantri(null)} />
|
||||
{santri && santri.length > 0 ? santri.map((item, i) => (
|
||||
<div key={i} className="p-4 border-b">
|
||||
<p>Nis: {item.user.nis}</p>
|
||||
<p><strong>Santri:</strong> {item.nama}</p>
|
||||
<ModalInput
|
||||
showPayments={true}
|
||||
fields={fields}
|
||||
options={options}
|
||||
tableName="payments"
|
||||
initialData={selectedSantri}
|
||||
onClose={() => setSelectedSantri(null)}
|
||||
/>
|
||||
|
||||
{item.payments && item.payments.length > 0 ? (
|
||||
<div className="ml-4">
|
||||
<p className="font-semibold">Payments:</p>
|
||||
{item.payments.map((payment) => (
|
||||
<div key={payment.id} className="ml-4">
|
||||
{payment.detail_payments.map((detail) => (
|
||||
<div key={detail.id}>
|
||||
<p>{detail.penalty ? `Denda: Rp ${detail.penalty}` : 'Tidak ada denda'}</p>
|
||||
<p>{detail.status}</p>
|
||||
<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 Pembayaran</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>
|
||||
|
||||
<label htmlFor="my_modal_7" className="btn">show pembayaran</label>
|
||||
|
||||
<input type="checkbox" id="my_modal_7" className="modal-toggle" />
|
||||
<div className="modal" role="dialog">
|
||||
<div className="modal-box">
|
||||
<h3 className="text-lg font-bold">data pembayaran</h3>
|
||||
<p className="py-4">{detail.payment_type?.payment_type}</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>tipe pembayaran</th>
|
||||
<th>tahun</th>
|
||||
<th>bulan</th>
|
||||
<th>denda</th>
|
||||
<th>status pemabayaran</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{detail.payment_type?.payment_type}</th>
|
||||
<td className='text-center'>{detail.payment_year}</td>
|
||||
<td className='text-center'>{detail.payment_month}</td>
|
||||
<td className='text-center'>{detail.penalty}</td>
|
||||
<td className='text-center'>
|
||||
<div className='p-2 bg-red-600 text-white rounded-md'>
|
||||
{detail.status}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nama</th>
|
||||
<th>Nis</th>
|
||||
<th>Status</th>
|
||||
<th>Aksi</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.role_santri}</div>
|
||||
</div>
|
||||
</div>
|
||||
<label className="modal-backdrop" htmlFor="my_modal_7">Close</label>
|
||||
</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>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
className="btn btn-sm btn-accent text-white"
|
||||
onClick={() => {
|
||||
setSelectedSantri({
|
||||
id: item.id,
|
||||
nama: item.nama,
|
||||
nis: item.nis,
|
||||
status_santri: item.status_santri,
|
||||
role_santri: item.role_santri
|
||||
});
|
||||
document.getElementById('modal_input').checked = true;
|
||||
}}
|
||||
>
|
||||
Bayar
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-primary text-white"
|
||||
onClick={() => setSelectedPayments(item.payments)}
|
||||
>
|
||||
Show Pembayaran
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="4" className="text-center">Tidak ada data santri.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="flex justify-center mt-4">
|
||||
{santri?.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>
|
||||
) : <p className="ml-4 text-gray-500">Tidak ada pembayaran.</p>}
|
||||
|
||||
<div>
|
||||
<button className='btn btn-accent text-white mt-2' onClick={() => {
|
||||
setSelectedSantri({
|
||||
id: item.id,
|
||||
nama: item.nama,
|
||||
alamat: item.alamat,
|
||||
status_santri: item.status_santri,
|
||||
role_santri: item.role_santri,
|
||||
nis: item.user?.nis
|
||||
})
|
||||
document.getElementById('modal_input').checked = true
|
||||
}}>Bayar</button>
|
||||
</div>
|
||||
</div>
|
||||
)) : <p>Tidak ada data santri.</p>}
|
||||
</div>
|
||||
|
||||
{selectedPayments && (
|
||||
<div>
|
||||
<input type="checkbox" id="modal_show_payments" className="modal-toggle" defaultChecked />
|
||||
<div className="modal" role="dialog">
|
||||
<div className="modal-box">
|
||||
<h3 className="text-lg font-bold">Riwayat Pembayaran</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tipe Pembayaran</th>
|
||||
<th>Tahun</th>
|
||||
<th>Bulan</th>
|
||||
<th>Denda</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{selectedSantri ? selectedPayments.map((payment, idx) =>
|
||||
payment.detail_payments.map((detail) => (
|
||||
<tr key={`${idx}-${detail.id}`}>
|
||||
<td>{detail.payment_type?.payment_type}</td>
|
||||
<td className="text-center">{detail.payment_year}</td>
|
||||
<td className="text-center">{detail.payment_month}</td>
|
||||
<td className="text-center">{detail.penalty}</td>
|
||||
<td className="text-center">
|
||||
<span className={`p-2 rounded-md ${detail.status === 'paid' ? 'bg-green-500 text-white' : 'bg-red-600 text-white'}`}>
|
||||
{detail.status}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : 'tidak ada data pembayaran santri'}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/* <div className="modal-action">
|
||||
<label className="btn" onClick={() => setSelectedPayments(null)}>
|
||||
Tutup
|
||||
</label>
|
||||
</div> */}
|
||||
</div>
|
||||
<label className="modal-backdrop" htmlFor="modal_show_payments" onClick={() => setSelectedPayments(null)}></label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|