semifinal
This commit is contained in:
parent
8617fce4aa
commit
91dff8b258
|
@ -34,6 +34,8 @@ public function store(LoginRequest $request): RedirectResponse
|
||||||
|
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
|
|
||||||
|
session()->flash('success', 'Login berhasil!');
|
||||||
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
return redirect()->intended(RouteServiceProvider::HOME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +50,8 @@ public function destroy(Request $request): RedirectResponse
|
||||||
|
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
session()->flash('success', 'Logout berhasil!');
|
||||||
|
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,6 @@ public function manualPayment(Request $request, $paymentId)
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
|
||||||
// Ambil semua payment type sekaligus
|
|
||||||
$paymentTypes = PaymentType::pluck('nominal', 'id');
|
$paymentTypes = PaymentType::pluck('nominal', 'id');
|
||||||
|
|
||||||
$existingPayment = Payment::where('user_id', $userId)
|
$existingPayment = Payment::where('user_id', $userId)
|
||||||
|
@ -80,7 +79,6 @@ public function manualPayment(Request $request, $paymentId)
|
||||||
$range = $item['range'];
|
$range = $item['range'];
|
||||||
$nominal = $paymentTypes[$typeId] ?? 0;
|
$nominal = $paymentTypes[$typeId] ?? 0;
|
||||||
|
|
||||||
// Ambil unpaid details
|
|
||||||
$unpaidDetails = DetailPayment::where('payment_id', $existingPayment->id)
|
$unpaidDetails = DetailPayment::where('payment_id', $existingPayment->id)
|
||||||
->where('status', 'unpaid')
|
->where('status', 'unpaid')
|
||||||
->where('type_id', $typeId)
|
->where('type_id', $typeId)
|
||||||
|
@ -88,7 +86,6 @@ public function manualPayment(Request $request, $paymentId)
|
||||||
->orderBy('payment_month')
|
->orderBy('payment_month')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
// Proses unpaid details
|
|
||||||
$toPay = $unpaidDetails->take($range);
|
$toPay = $unpaidDetails->take($range);
|
||||||
$toPay->each(function ($detail) use (&$totalAmount, $nominal) {
|
$toPay->each(function ($detail) use (&$totalAmount, $nominal) {
|
||||||
$detail->update([
|
$detail->update([
|
||||||
|
@ -99,10 +96,8 @@ public function manualPayment(Request $request, $paymentId)
|
||||||
$totalAmount += $nominal + $detail->penalty;
|
$totalAmount += $nominal + $detail->penalty;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hitung sisa bulan yang perlu dibuat
|
|
||||||
$sisa = $range - $toPay->count();
|
$sisa = $range - $toPay->count();
|
||||||
if ($sisa > 0) {
|
if ($sisa > 0) {
|
||||||
// Ambil bulan/tahun terakhir
|
|
||||||
$lastDetail = DetailPayment::where('payment_id', $existingPayment->id)
|
$lastDetail = DetailPayment::where('payment_id', $existingPayment->id)
|
||||||
->orderBy('payment_year', 'desc')
|
->orderBy('payment_year', 'desc')
|
||||||
->orderBy('payment_month', 'desc')
|
->orderBy('payment_month', 'desc')
|
||||||
|
@ -133,7 +128,6 @@ public function manualPayment(Request $request, $paymentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update payment status
|
|
||||||
$existingPayment->update([
|
$existingPayment->update([
|
||||||
'amount_payment' => DetailPayment::where('payment_id', $existingPayment->id)->sum('amount'),
|
'amount_payment' => DetailPayment::where('payment_id', $existingPayment->id)->sum('amount'),
|
||||||
'payment_status' => DetailPayment::where('payment_id', $existingPayment->id)
|
'payment_status' => DetailPayment::where('payment_id', $existingPayment->id)
|
||||||
|
@ -145,7 +139,6 @@ public function manualPayment(Request $request, $paymentId)
|
||||||
return response()->json(['message' => 'Pembayaran berhasil diupdate']);
|
return response()->json(['message' => 'Pembayaran berhasil diupdate']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jika tidak ada existing payment, buat baru
|
|
||||||
$newPayment = Payment::create([
|
$newPayment = Payment::create([
|
||||||
'payment_status' => 'success',
|
'payment_status' => 'success',
|
||||||
'amount_payment' => 0,
|
'amount_payment' => 0,
|
||||||
|
@ -184,12 +177,12 @@ public function manualPayment(Request $request, $paymentId)
|
||||||
|
|
||||||
$newPayment->update(['amount_payment' => $totalAmount]);
|
$newPayment->update(['amount_payment' => $totalAmount]);
|
||||||
DB::commit();
|
DB::commit();
|
||||||
dd('berhasil lur');
|
// dd('berhasil lur');
|
||||||
|
|
||||||
// return redirect()->back()->with(['success' => 'Pembayaran baru berhasil dibuat']);
|
return redirect()->back()->with(['success' => 'Pembayaran baru berhasil dibuat']);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
dd('gagal' . $e->getMessage());
|
// dd('gagal' . $e->getMessage());
|
||||||
return redirect()->back()->with(['error' => 'gagal' . $e->getMessage()]);
|
return redirect()->back()->with(['error' => 'gagal' . $e->getMessage()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ public function store(Request $request)
|
||||||
'nominal' => $request->nominal
|
'nominal' => $request->nominal
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'berhasil insert data');
|
return redirect()->back()->with('success', 'berhasil insert data tipe pembayaran');
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
return redirect()->back()->with('error', 'Data gagal di tambahkan' . $th->getMessage());
|
return redirect()->back()->with('error', 'Data gagal di tambahkan' . $th->getMessage());
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,10 @@ public function share(Request $request): array
|
||||||
'auth' => [
|
'auth' => [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
],
|
],
|
||||||
|
'flash' => [
|
||||||
|
'success' => fn() => $request->session()->get('success'),
|
||||||
|
'error' => fn() => $request->session()->get('error'),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
@ -19,6 +20,11 @@ public function register(): void
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
Inertia::share([
|
||||||
|
'flash' => [
|
||||||
|
'success' => session('success'),
|
||||||
|
'error' => session('error'),
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"react-chartjs-2": "^5.3.0",
|
"react-chartjs-2": "^5.3.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-tailwindcss-datepicker": "^2.0.0",
|
"react-tailwindcss-datepicker": "^2.0.0",
|
||||||
|
"sweetalert2": "^11.21.0",
|
||||||
"theme-change": "^2.5.0",
|
"theme-change": "^2.5.0",
|
||||||
"web-vitals": "^4.2.4"
|
"web-vitals": "^4.2.4"
|
||||||
},
|
},
|
||||||
|
@ -25,7 +26,6 @@
|
||||||
"laravel-vite-plugin": "^0.7.2",
|
"laravel-vite-plugin": "^0.7.2",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"tailwindcss": "^3.2.1",
|
"tailwindcss": "^3.2.1",
|
||||||
"vite": "^4.0.0"
|
"vite": "^4.0.0"
|
||||||
}
|
}
|
||||||
|
@ -2449,6 +2449,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.2"
|
"scheduler": "^0.23.2"
|
||||||
|
@ -2610,6 +2611,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
|
@ -2863,6 +2865,15 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sweetalert2": {
|
||||||
|
"version": "11.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.21.0.tgz",
|
||||||
|
"integrity": "sha512-fiEK7SqRY/QD/wC2uqEHlfYGZ7qe2UcyQbJpbpj4YRVqplBgcI+euPZLZL+evLINcvbtXmL1SFUdZHKqBHGAAQ==",
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/limonte"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.17",
|
"version": "3.4.17",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"react-chartjs-2": "^5.3.0",
|
"react-chartjs-2": "^5.3.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-tailwindcss-datepicker": "^2.0.0",
|
"react-tailwindcss-datepicker": "^2.0.0",
|
||||||
|
"sweetalert2": "^11.21.0",
|
||||||
"theme-change": "^2.5.0",
|
"theme-change": "^2.5.0",
|
||||||
"web-vitals": "^4.2.4"
|
"web-vitals": "^4.2.4"
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 226 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 758 KiB |
|
@ -2,13 +2,11 @@ import { configureStore } from '@reduxjs/toolkit'
|
||||||
import headerSlice from '../Components/features/common/headerSlice'
|
import headerSlice from '../Components/features/common/headerSlice'
|
||||||
import modalSlice from '../Components/features/common/modalSlice'
|
import modalSlice from '../Components/features/common/modalSlice'
|
||||||
import rightDrawerSlice from '../Components/features/common/rightDrawerSlice'
|
import rightDrawerSlice from '../Components/features/common/rightDrawerSlice'
|
||||||
import leadsSlice from '../Components/features/leads/leadSlice'
|
|
||||||
|
|
||||||
const combinedReducer = {
|
const combinedReducer = {
|
||||||
header: headerSlice,
|
header: headerSlice,
|
||||||
rightDrawer: rightDrawerSlice,
|
rightDrawer: rightDrawerSlice,
|
||||||
modal: modalSlice,
|
modal: modalSlice,
|
||||||
lead: leadsSlice
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default configureStore({
|
export default configureStore({
|
||||||
|
|
|
@ -8,12 +8,12 @@ import { usePage } from "@inertiajs/react";
|
||||||
|
|
||||||
function Layout({ children }) {
|
function Layout({ children }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { url } = usePage(); // Gunakan usePage() dari Inertia
|
const { url, } = usePage();
|
||||||
const [showSidebar, setShowSidebar] = useState(true);
|
const [showSidebar, setShowSidebar] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("Route berubah (Inertia):", url);
|
// console.log("Route berubah (Inertia):", url);
|
||||||
setShowSidebar(!url.startsWith("/login")); // Sidebar disembunyikan di halaman login
|
setShowSidebar(!url.startsWith("/login"));
|
||||||
}, [url]);
|
}, [url]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -3,13 +3,30 @@ import { Suspense, useEffect, useRef, useState } from "react";
|
||||||
import SuspenseContent from "./SuspenseContent";
|
import SuspenseContent from "./SuspenseContent";
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { usePage } from "@inertiajs/react";
|
import { usePage } from "@inertiajs/react";
|
||||||
|
import Swal from "sweetalert2"
|
||||||
|
|
||||||
function PageContent({ children }) {
|
function PageContent({ children }) {
|
||||||
const mainContentRef = useRef(null);
|
const mainContentRef = useRef(null);
|
||||||
const { pageTitle } = useSelector(state => state.header);
|
const { pageTitle } = useSelector(state => state.header);
|
||||||
const { url } = usePage(); // Ambil URL dari Inertia
|
const { url } = usePage(); // Ambil URL dari Inertia
|
||||||
const [isLoginPage, setIsLoginPage] = useState(false);
|
const [isLoginPage, setIsLoginPage] = useState(false);
|
||||||
|
const { flash } = usePage().props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (flash.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
} else if (flash.error) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [flash]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mainContentRef.current?.scroll({
|
mainContentRef.current?.scroll({
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function SuspenseContent(){
|
function SuspenseContent() {
|
||||||
return(
|
return (
|
||||||
<div className="w-full h-screen text-gray-300 dark:text-gray-200 bg-base-100">
|
<div className="w-full h-screen text-gray-300 dark:text-gray-200 bg-base-100">
|
||||||
Loading...
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
function AmountStats({}){
|
|
||||||
return(
|
|
||||||
<div className="stats bg-base-100 shadow">
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-title">Amount to be Collected</div>
|
|
||||||
<div className="stat-value">$25,600</div>
|
|
||||||
<div className="stat-actions">
|
|
||||||
<button className="btn btn-xs">View Users</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-title">Cash in hand</div>
|
|
||||||
<div className="stat-value">$5,600</div>
|
|
||||||
<div className="stat-actions">
|
|
||||||
<button className="btn btn-xs">View Members</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AmountStats
|
|
|
@ -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
|
|
|
@ -1,23 +0,0 @@
|
||||||
function DashboardStats({title, icon, value, description, colorIndex}){
|
|
||||||
|
|
||||||
const COLORS = ["primary", "primary"]
|
|
||||||
|
|
||||||
const getDescStyle = () => {
|
|
||||||
if(description.includes("↗︎"))return "font-bold text-green-700 dark:text-green-300"
|
|
||||||
else if(description.includes("↙"))return "font-bold text-rose-500 dark:text-red-400"
|
|
||||||
else return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div className="stats shadow">
|
|
||||||
<div className="stat">
|
|
||||||
<div className={`stat-figure dark:text-slate-300 text-${COLORS[colorIndex%2]}`}>{icon}</div>
|
|
||||||
<div className="stat-title dark:text-slate-300">{title}</div>
|
|
||||||
<div className={`stat-value dark:text-slate-300 text-${COLORS[colorIndex%2]}`}>{value}</div>
|
|
||||||
<div className={"stat-desc " + getDescStyle()}>{description}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DashboardStats
|
|
|
@ -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
|
|
|
@ -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,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
|
|
|
@ -1,30 +0,0 @@
|
||||||
import HeartIcon from '@heroicons/react/24/outline/HeartIcon'
|
|
||||||
import BoltIcon from '@heroicons/react/24/outline/BoltIcon'
|
|
||||||
|
|
||||||
|
|
||||||
function PageStats({}){
|
|
||||||
return(
|
|
||||||
<div className="stats bg-base-100 shadow">
|
|
||||||
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-figure invisible md:visible">
|
|
||||||
<HeartIcon className='w-8 h-8'/>
|
|
||||||
</div>
|
|
||||||
<div className="stat-title">Total Likes</div>
|
|
||||||
<div className="stat-value">25.6K</div>
|
|
||||||
<div className="stat-desc">21% more than last month</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-figure invisible md:visible">
|
|
||||||
<BoltIcon className='w-8 h-8'/>
|
|
||||||
</div>
|
|
||||||
<div className="stat-title">Page Views</div>
|
|
||||||
<div className="stat-value">2.6M</div>
|
|
||||||
<div className="stat-desc">14% more than last month</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PageStats
|
|
|
@ -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
|
|
|
@ -1,78 +0,0 @@
|
||||||
import DashboardStats from './components/DashboardStats'
|
|
||||||
import AmountStats from './components/AmountStats'
|
|
||||||
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 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 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%)" },
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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 }))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/** ---------------------- 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} />
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/** ---------------------- Different charts ------------------------- */}
|
|
||||||
<div className="grid lg:grid-cols-2 mt-4 grid-cols-1 gap-6">
|
|
||||||
<LineChart />
|
|
||||||
<BarChart />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/** ---------------------- 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 ------------------------- */}
|
|
||||||
|
|
||||||
<div className="grid lg:grid-cols-2 mt-4 grid-cols-1 gap-6">
|
|
||||||
<UserChannels />
|
|
||||||
<DoughnutChart />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Dashboard
|
|
|
@ -1,62 +0,0 @@
|
||||||
import { useState } from "react"
|
|
||||||
import { useDispatch } from "react-redux"
|
|
||||||
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 : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function AddLeadModalBody({closeModal}){
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const [errorMessage, setErrorMessage] = useState("")
|
|
||||||
const [leadObj, setLeadObj] = useState(INITIAL_LEAD_OBJ)
|
|
||||||
|
|
||||||
|
|
||||||
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{
|
|
||||||
let newLeadObj = {
|
|
||||||
"id": 7,
|
|
||||||
"email": leadObj.email,
|
|
||||||
"first_name": leadObj.first_name,
|
|
||||||
"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}))
|
|
||||||
closeModal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateFormValue = ({updateType, value}) => {
|
|
||||||
setErrorMessage("")
|
|
||||||
setLeadObj({...leadObj, [updateType] : value})
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
|
|
||||||
<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="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>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AddLeadModalBody
|
|
|
@ -1,104 +0,0 @@
|
||||||
import moment from "moment"
|
|
||||||
import { useEffect } from "react"
|
|
||||||
import { useDispatch, useSelector } from "react-redux"
|
|
||||||
import TitleCard from "../../components/Cards/TitleCard"
|
|
||||||
import { openModal } from "../common/modalSlice"
|
|
||||||
import { deleteLead, getLeadsContent } from "./leadSlice"
|
|
||||||
import { CONFIRMATION_MODAL_CLOSE_TYPES, MODAL_BODY_TYPES } from '../../utils/globalConstantUtil'
|
|
||||||
import TrashIcon from '@heroicons/react/24/outline/TrashIcon'
|
|
||||||
import { showNotification } from '../common/headerSlice'
|
|
||||||
|
|
||||||
const TopSideButtons = () => {
|
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
const openAddNewLeadModal = () => {
|
|
||||||
dispatch(openModal({title : "Add New Lead", bodyType : MODAL_BODY_TYPES.LEAD_ADD_NEW}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div className="inline-block float-right">
|
|
||||||
<button className="btn px-6 btn-sm normal-case btn-primary" onClick={() => openAddNewLeadModal()}>Add New</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Leads(){
|
|
||||||
|
|
||||||
const {leads } = useSelector(state => state.lead)
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(getLeadsContent())
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getDummyStatus = (index) => {
|
|
||||||
if(index % 5 === 0)return <div className="badge">Not Interested</div>
|
|
||||||
else if(index % 5 === 1)return <div className="badge badge-primary">In Progress</div>
|
|
||||||
else if(index % 5 === 2)return <div className="badge badge-secondary">Sold</div>
|
|
||||||
else if(index % 5 === 3)return <div className="badge badge-accent">Need Followup</div>
|
|
||||||
else return <div className="badge badge-ghost">Open</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteCurrentLead = (index) => {
|
|
||||||
dispatch(openModal({title : "Confirmation", bodyType : MODAL_BODY_TYPES.CONFIRMATION,
|
|
||||||
extraObject : { message : `Are you sure you want to delete this lead?`, type : CONFIRMATION_MODAL_CLOSE_TYPES.LEAD_DELETE, index}}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
|
|
||||||
<TitleCard title="Current Leads" topMargin="mt-2" TopSideButtons={<TopSideButtons />}>
|
|
||||||
|
|
||||||
{/* Leads List in table format loaded from slice after api call */}
|
|
||||||
<div className="overflow-x-auto w-full">
|
|
||||||
<table className="table w-full">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Email Id</th>
|
|
||||||
<th>Created At</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Assigned To</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{
|
|
||||||
leads.map((l, k) => {
|
|
||||||
return(
|
|
||||||
<tr key={k}>
|
|
||||||
<td>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div className="avatar">
|
|
||||||
<div className="mask mask-squircle w-12 h-12">
|
|
||||||
<img src={l.avatar} alt="Avatar" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="font-bold">{l.first_name}</div>
|
|
||||||
<div className="text-sm opacity-50">{l.last_name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>{l.email}</td>
|
|
||||||
<td>{moment(new Date()).add(-5*(k+2), 'days').format("DD MMM YY")}</td>
|
|
||||||
<td>{getDummyStatus(k)}</td>
|
|
||||||
<td>{l.last_name}</td>
|
|
||||||
<td><button className="btn btn-square btn-ghost" onClick={() => deleteCurrentLead(k)}><TrashIcon className="w-5"/></button></td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</TitleCard>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default Leads
|
|
|
@ -1,49 +0,0 @@
|
||||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const getLeadsContent = createAsyncThunk('/leads/content', async () => {
|
|
||||||
const response = await axios.get('/api/users?page=2', {})
|
|
||||||
return response.data;
|
|
||||||
})
|
|
||||||
|
|
||||||
export const leadsSlice = createSlice({
|
|
||||||
name: 'leads',
|
|
||||||
initialState: {
|
|
||||||
isLoading: false,
|
|
||||||
leads: []
|
|
||||||
},
|
|
||||||
reducers: {
|
|
||||||
|
|
||||||
|
|
||||||
addNewLead: (state, action) => {
|
|
||||||
let { newLeadObj } = action.payload
|
|
||||||
state.leads = [...state.leads, newLeadObj]
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteLead: (state, action) => {
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
export const { addNewLead, deleteLead } = leadsSlice.actions
|
|
||||||
|
|
||||||
export default leadsSlice.reducer
|
|
|
@ -1,82 +0,0 @@
|
||||||
import moment from "moment"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { useDispatch, useSelector } from "react-redux"
|
|
||||||
import TitleCard from "../../../components/Cards/TitleCard"
|
|
||||||
import { showNotification } from '../../common/headerSlice'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const BILLS = [
|
|
||||||
{invoiceNo : "#4567", amount : "23,989", description : "Product usages", status : "Pending", generatedOn : moment(new Date()).add(-30*1, 'days').format("DD MMM YYYY"), paidOn : "-"},
|
|
||||||
|
|
||||||
{invoiceNo : "#4523", amount : "34,989", description : "Product usages", status : "Pending", generatedOn : moment(new Date()).add(-30*2, 'days').format("DD MMM YYYY"), paidOn : "-"},
|
|
||||||
|
|
||||||
{invoiceNo : "#4453", amount : "39,989", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*3, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*2, 'days').format("DD MMM YYYY")},
|
|
||||||
|
|
||||||
{invoiceNo : "#4359", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*4, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*3, 'days').format("DD MMM YYYY")},
|
|
||||||
|
|
||||||
{invoiceNo : "#3359", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*5, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*4, 'days').format("DD MMM YYYY")},
|
|
||||||
|
|
||||||
{invoiceNo : "#3367", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*6, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*5, 'days').format("DD MMM YYYY")},
|
|
||||||
|
|
||||||
{invoiceNo : "#3359", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*7, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*6, 'days').format("DD MMM YYYY")},
|
|
||||||
|
|
||||||
{invoiceNo : "#2359", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*8, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*7, 'days').format("DD MMM YYYY")},
|
|
||||||
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
function Billing(){
|
|
||||||
|
|
||||||
|
|
||||||
const [bills, setBills] = useState(BILLS)
|
|
||||||
|
|
||||||
const getPaymentStatus = (status) => {
|
|
||||||
if(status === "Paid")return <div className="badge badge-success">{status}</div>
|
|
||||||
if(status === "Pending")return <div className="badge badge-primary">{status}</div>
|
|
||||||
else return <div className="badge badge-ghost">{status}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
|
|
||||||
<TitleCard title="Billing History" topMargin="mt-2">
|
|
||||||
|
|
||||||
{/* Invoice list in table format loaded constant */}
|
|
||||||
<div className="overflow-x-auto w-full">
|
|
||||||
<table className="table w-full">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Invoice No</th>
|
|
||||||
<th>Invoice Generated On</th>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>Amount</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Invoice Paid On</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{
|
|
||||||
bills.map((l, k) => {
|
|
||||||
return(
|
|
||||||
<tr key={k}>
|
|
||||||
<td>{l.invoiceNo}</td>
|
|
||||||
<td>{l.generatedOn}</td>
|
|
||||||
<td>{l.description}</td>
|
|
||||||
<td>${l.amount}</td>
|
|
||||||
<td>{getPaymentStatus(l.status)}</td>
|
|
||||||
<td>{l.paidOn}</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</TitleCard>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default Billing
|
|
|
@ -1,51 +0,0 @@
|
||||||
import moment from "moment"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { useDispatch, useSelector } from "react-redux"
|
|
||||||
import TitleCard from "../../../components/Cards/TitleCard"
|
|
||||||
import { showNotification } from '../../common/headerSlice'
|
|
||||||
import InputText from '../../../components/Input/InputText'
|
|
||||||
import TextAreaInput from '../../../components/Input/TextAreaInput'
|
|
||||||
import ToogleInput from '../../../components/Input/ToogleInput'
|
|
||||||
|
|
||||||
function ProfileSettings(){
|
|
||||||
|
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
// Call API to update profile settings changes
|
|
||||||
const updateProfile = () => {
|
|
||||||
dispatch(showNotification({message : "Profile Updated", status : 1}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateFormValue = ({updateType, value}) => {
|
|
||||||
console.log(updateType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
|
|
||||||
<TitleCard title="Profile Settings" topMargin="mt-2">
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<InputText labelTitle="Name" defaultValue="Alex" updateFormValue={updateFormValue}/>
|
|
||||||
<InputText labelTitle="Email Id" defaultValue="alex@dashwind.com" updateFormValue={updateFormValue}/>
|
|
||||||
<InputText labelTitle="Title" defaultValue="UI/UX Designer" updateFormValue={updateFormValue}/>
|
|
||||||
<InputText labelTitle="Place" defaultValue="California" updateFormValue={updateFormValue}/>
|
|
||||||
<TextAreaInput labelTitle="About" defaultValue="Doing what I love, part time traveller" updateFormValue={updateFormValue}/>
|
|
||||||
</div>
|
|
||||||
<div className="divider" ></div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<InputText labelTitle="Language" defaultValue="English" updateFormValue={updateFormValue}/>
|
|
||||||
<InputText labelTitle="Timezone" defaultValue="IST" updateFormValue={updateFormValue}/>
|
|
||||||
<ToogleInput updateType="syncData" labelTitle="Sync Data" defaultValue={true} updateFormValue={updateFormValue}/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-16"><button className="btn btn-primary float-right" onClick={() => updateProfile()}>Update</button></div>
|
|
||||||
</TitleCard>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default ProfileSettings
|
|
|
@ -1,97 +0,0 @@
|
||||||
import moment from "moment"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { useDispatch, useSelector } from "react-redux"
|
|
||||||
import TitleCard from "../../../components/Cards/TitleCard"
|
|
||||||
import { showNotification } from '../../common/headerSlice'
|
|
||||||
|
|
||||||
const TopSideButtons = () => {
|
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
const addNewTeamMember = () => {
|
|
||||||
dispatch(showNotification({message : "Add New Member clicked", status : 1}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div className="inline-block float-right">
|
|
||||||
<button className="btn px-6 btn-sm normal-case btn-primary" onClick={() => addNewTeamMember()}>Invite New</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const TEAM_MEMBERS = [
|
|
||||||
{name : "Alex", avatar : "https://reqres.in/img/faces/1-image.jpg", email : "alex@dashwind.com", role : "Owner", joinedOn : moment(new Date()).add(-5*1, 'days').format("DD MMM YYYY"), lastActive : "5 hr ago"},
|
|
||||||
{name : "Ereena", avatar : "https://reqres.in/img/faces/2-image.jpg", email : "ereena@dashwind.com", role : "Admin", joinedOn : moment(new Date()).add(-5*2, 'days').format("DD MMM YYYY"), lastActive : "15 min ago"},
|
|
||||||
{name : "John", avatar : "https://reqres.in/img/faces/3-image.jpg", email : "jhon@dashwind.com", role : "Admin", joinedOn : moment(new Date()).add(-5*3, 'days').format("DD MMM YYYY"), lastActive : "20 hr ago"},
|
|
||||||
{name : "Matrix", avatar : "https://reqres.in/img/faces/4-image.jpg", email : "matrix@dashwind.com", role : "Manager", joinedOn : moment(new Date()).add(-5*4, 'days').format("DD MMM YYYY"), lastActive : "1 hr ago"},
|
|
||||||
{name : "Virat", avatar : "https://reqres.in/img/faces/5-image.jpg", email : "virat@dashwind.com", role : "Support", joinedOn : moment(new Date()).add(-5*5, 'days').format("DD MMM YYYY"), lastActive : "40 min ago"},
|
|
||||||
{name : "Miya", avatar : "https://reqres.in/img/faces/6-image.jpg", email : "miya@dashwind.com", role : "Support", joinedOn : moment(new Date()).add(-5*7, 'days').format("DD MMM YYYY"), lastActive : "5 hr ago"},
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
function Team(){
|
|
||||||
|
|
||||||
|
|
||||||
const [members, setMembers] = useState(TEAM_MEMBERS)
|
|
||||||
|
|
||||||
const getRoleComponent = (role) => {
|
|
||||||
if(role === "Admin")return <div className="badge badge-secondary">{role}</div>
|
|
||||||
if(role === "Manager")return <div className="badge">{role}</div>
|
|
||||||
if(role === "Owner")return <div className="badge badge-primary">{role}</div>
|
|
||||||
if(role === "Support")return <div className="badge badge-accent">{role}</div>
|
|
||||||
else return <div className="badge badge-ghost">{role}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
|
|
||||||
<TitleCard title="Active Members" topMargin="mt-2" TopSideButtons={<TopSideButtons />}>
|
|
||||||
|
|
||||||
{/* Team Member list in table format loaded constant */}
|
|
||||||
<div className="overflow-x-auto w-full">
|
|
||||||
<table className="table w-full">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Email Id</th>
|
|
||||||
<th>Joined On</th>
|
|
||||||
<th>Role</th>
|
|
||||||
<th>Last Active</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{
|
|
||||||
members.map((l, k) => {
|
|
||||||
return(
|
|
||||||
<tr key={k}>
|
|
||||||
<td>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div className="avatar">
|
|
||||||
<div className="mask mask-circle w-12 h-12">
|
|
||||||
<img src={l.avatar} alt="Avatar" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="font-bold">{l.name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>{l.email}</td>
|
|
||||||
<td>{l.joinedOn}</td>
|
|
||||||
<td>{getRoleComponent(l.role)}</td>
|
|
||||||
<td>{l.lastActive}</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</TitleCard>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default Team
|
|
|
@ -1,128 +0,0 @@
|
||||||
import moment from "moment"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { useDispatch, useSelector } from "react-redux"
|
|
||||||
import { showNotification } from "../common/headerSlice"
|
|
||||||
import TitleCard from "../../components/Cards/TitleCard"
|
|
||||||
import { RECENT_TRANSACTIONS } from "../../utils/dummyData"
|
|
||||||
import FunnelIcon from '@heroicons/react/24/outline/FunnelIcon'
|
|
||||||
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon'
|
|
||||||
import SearchBar from "../../components/Input/SearchBar"
|
|
||||||
|
|
||||||
const TopSideButtons = ({removeFilter, applyFilter, applySearch}) => {
|
|
||||||
|
|
||||||
const [filterParam, setFilterParam] = useState("")
|
|
||||||
const [searchText, setSearchText] = useState("")
|
|
||||||
const locationFilters = ["Paris", "London", "Canada", "Peru", "Tokyo"]
|
|
||||||
|
|
||||||
const showFiltersAndApply = (params) => {
|
|
||||||
applyFilter(params)
|
|
||||||
setFilterParam(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeAppliedFilter = () => {
|
|
||||||
removeFilter()
|
|
||||||
setFilterParam("")
|
|
||||||
setSearchText("")
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if(searchText == ""){
|
|
||||||
removeAppliedFilter()
|
|
||||||
}else{
|
|
||||||
applySearch(searchText)
|
|
||||||
}
|
|
||||||
}, [searchText])
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div className="inline-block float-right">
|
|
||||||
<SearchBar searchText={searchText} styleClass="mr-4" setSearchText={setSearchText}/>
|
|
||||||
{filterParam != "" && <button onClick={() => removeAppliedFilter()} className="btn btn-xs mr-2 btn-active btn-ghost normal-case">{filterParam}<XMarkIcon className="w-4 ml-2"/></button>}
|
|
||||||
<div className="dropdown dropdown-bottom dropdown-end">
|
|
||||||
<label tabIndex={0} className="btn btn-sm btn-outline"><FunnelIcon className="w-5 mr-2"/>Filter</label>
|
|
||||||
<ul tabIndex={0} className="dropdown-content menu p-2 text-sm shadow bg-base-100 rounded-box w-52">
|
|
||||||
{
|
|
||||||
locationFilters.map((l, k) => {
|
|
||||||
return <li key={k}><a onClick={() => showFiltersAndApply(l)}>{l}</a></li>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
<div className="divider mt-0 mb-0"></div>
|
|
||||||
<li><a onClick={() => removeAppliedFilter()}>Remove Filter</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function Transactions(){
|
|
||||||
|
|
||||||
|
|
||||||
const [trans, setTrans] = useState(RECENT_TRANSACTIONS)
|
|
||||||
|
|
||||||
const removeFilter = () => {
|
|
||||||
setTrans(RECENT_TRANSACTIONS)
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyFilter = (params) => {
|
|
||||||
let filteredTransactions = RECENT_TRANSACTIONS.filter((t) => {return t.location == params})
|
|
||||||
setTrans(filteredTransactions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search according to name
|
|
||||||
const applySearch = (value) => {
|
|
||||||
let filteredTransactions = RECENT_TRANSACTIONS.filter((t) => {return t.email.toLowerCase().includes(value.toLowerCase()) || t.email.toLowerCase().includes(value.toLowerCase())})
|
|
||||||
setTrans(filteredTransactions)
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
|
|
||||||
<TitleCard title="Recent Transactions" topMargin="mt-2" TopSideButtons={<TopSideButtons applySearch={applySearch} applyFilter={applyFilter} removeFilter={removeFilter}/>}>
|
|
||||||
|
|
||||||
{/* Team Member list in table format loaded constant */}
|
|
||||||
<div className="overflow-x-auto w-full">
|
|
||||||
<table className="table w-full">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Email Id</th>
|
|
||||||
<th>Location</th>
|
|
||||||
<th>Amount</th>
|
|
||||||
<th>Transaction Date</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{
|
|
||||||
trans.map((l, k) => {
|
|
||||||
return(
|
|
||||||
<tr key={k}>
|
|
||||||
<td>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div className="avatar">
|
|
||||||
<div className="mask mask-circle w-12 h-12">
|
|
||||||
<img src={l.avatar} alt="Avatar" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="font-bold">{l.name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>{l.email}</td>
|
|
||||||
<td>{l.location}</td>
|
|
||||||
<td>${l.amount}</td>
|
|
||||||
<td>{moment(l.date).format("D MMM")}</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</TitleCard>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default Transactions
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Head, Link, useForm } from '@inertiajs/react';
|
import { Head, Link, useForm } from '@inertiajs/react';
|
||||||
|
import { usePage } from '@inertiajs/react';
|
||||||
|
import Swal from "sweetalert2"
|
||||||
// import { InertiaLink, usePage } from '@inertiajs/react';
|
// import { InertiaLink, usePage } from '@inertiajs/react';
|
||||||
|
|
||||||
function Login({ status }) {
|
function Login({ status }) {
|
||||||
|
@ -8,6 +10,23 @@ function Login({ status }) {
|
||||||
password: '',
|
password: '',
|
||||||
remember: false,
|
remember: false,
|
||||||
});
|
});
|
||||||
|
const { flash } = usePage().props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (flash.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
} else if (flash.error) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [flash]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
|
|
||||||
import DeleteUserForm from './Partials/DeleteUserForm';
|
|
||||||
import UpdatePasswordForm from './Partials/UpdatePasswordForm';
|
|
||||||
import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm';
|
|
||||||
import { Head } from '@inertiajs/react';
|
|
||||||
|
|
||||||
export default function Edit({ auth, mustVerifyEmail, status }) {
|
|
||||||
return (
|
|
||||||
<AuthenticatedLayout
|
|
||||||
user={auth.user}
|
|
||||||
header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Profile</h2>}
|
|
||||||
>
|
|
||||||
<Head title="Profile" />
|
|
||||||
|
|
||||||
<div className="py-12">
|
|
||||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
|
||||||
<div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
|
||||||
<UpdateProfileInformationForm
|
|
||||||
mustVerifyEmail={mustVerifyEmail}
|
|
||||||
status={status}
|
|
||||||
className="max-w-xl"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
|
||||||
<UpdatePasswordForm className="max-w-xl" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
|
||||||
<DeleteUserForm className="max-w-xl" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AuthenticatedLayout>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
import { useRef, useState } from 'react';
|
|
||||||
import DangerButton from '@/Components/DangerButton';
|
|
||||||
import InputError from '@/Components/InputError';
|
|
||||||
import InputLabel from '@/Components/InputLabel';
|
|
||||||
import Modal from '@/Components/ModalInput';
|
|
||||||
import SecondaryButton from '@/Components/SecondaryButton';
|
|
||||||
import TextInput from '@/Components/TextInput';
|
|
||||||
import { useForm } from '@inertiajs/react';
|
|
||||||
|
|
||||||
export default function DeleteUserForm({ className = '' }) {
|
|
||||||
const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false);
|
|
||||||
const passwordInput = useRef();
|
|
||||||
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
setData,
|
|
||||||
delete: destroy,
|
|
||||||
processing,
|
|
||||||
reset,
|
|
||||||
errors,
|
|
||||||
} = useForm({
|
|
||||||
password: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const confirmUserDeletion = () => {
|
|
||||||
setConfirmingUserDeletion(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteUser = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
destroy(route('profile.destroy'), {
|
|
||||||
preserveScroll: true,
|
|
||||||
onSuccess: () => closeModal(),
|
|
||||||
onError: () => passwordInput.current.focus(),
|
|
||||||
onFinish: () => reset(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeModal = () => {
|
|
||||||
setConfirmingUserDeletion(false);
|
|
||||||
|
|
||||||
reset();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={`space-y-6 ${className}`}>
|
|
||||||
<header>
|
|
||||||
<h2 className="text-lg font-medium text-gray-900">Delete Account</h2>
|
|
||||||
|
|
||||||
<p className="mt-1 text-sm text-gray-600">
|
|
||||||
Once your account is deleted, all of its resources and data will be permanently deleted. Before
|
|
||||||
deleting your account, please download any data or information that you wish to retain.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<DangerButton onClick={confirmUserDeletion}>Delete Account</DangerButton>
|
|
||||||
|
|
||||||
<Modal show={confirmingUserDeletion} onClose={closeModal}>
|
|
||||||
<form onSubmit={deleteUser} className="p-6">
|
|
||||||
<h2 className="text-lg font-medium text-gray-900">
|
|
||||||
Are you sure you want to delete your account?
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p className="mt-1 text-sm text-gray-600">
|
|
||||||
Once your account is deleted, all of its resources and data will be permanently deleted. Please
|
|
||||||
enter your password to confirm you would like to permanently delete your account.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mt-6">
|
|
||||||
<InputLabel htmlFor="password" value="Password" className="sr-only" />
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
ref={passwordInput}
|
|
||||||
value={data.password}
|
|
||||||
onChange={(e) => setData('password', e.target.value)}
|
|
||||||
className="mt-1 block w-3/4"
|
|
||||||
isFocused
|
|
||||||
placeholder="Password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError message={errors.password} className="mt-2" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end">
|
|
||||||
<SecondaryButton onClick={closeModal}>Cancel</SecondaryButton>
|
|
||||||
|
|
||||||
<DangerButton className="ms-3" disabled={processing}>
|
|
||||||
Delete Account
|
|
||||||
</DangerButton>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
import { useRef } from 'react';
|
|
||||||
import InputError from '@/Components/InputError';
|
|
||||||
import InputLabel from '@/Components/InputLabel';
|
|
||||||
import PrimaryButton from '@/Components/PrimaryButton';
|
|
||||||
import TextInput from '@/Components/TextInput';
|
|
||||||
import { useForm } from '@inertiajs/react';
|
|
||||||
import { Transition } from '@headlessui/react';
|
|
||||||
|
|
||||||
export default function UpdatePasswordForm({ className = '' }) {
|
|
||||||
const passwordInput = useRef();
|
|
||||||
const currentPasswordInput = useRef();
|
|
||||||
|
|
||||||
const { data, setData, errors, put, reset, processing, recentlySuccessful } = useForm({
|
|
||||||
current_password: '',
|
|
||||||
password: '',
|
|
||||||
password_confirmation: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatePassword = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
put(route('password.update'), {
|
|
||||||
preserveScroll: true,
|
|
||||||
onSuccess: () => reset(),
|
|
||||||
onError: (errors) => {
|
|
||||||
if (errors.password) {
|
|
||||||
reset('password', 'password_confirmation');
|
|
||||||
passwordInput.current.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.current_password) {
|
|
||||||
reset('current_password');
|
|
||||||
currentPasswordInput.current.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={className}>
|
|
||||||
<header>
|
|
||||||
<h2 className="text-lg font-medium text-gray-900">Update Password</h2>
|
|
||||||
|
|
||||||
<p className="mt-1 text-sm text-gray-600">
|
|
||||||
Ensure your account is using a long, random password to stay secure.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<form onSubmit={updatePassword} className="mt-6 space-y-6">
|
|
||||||
<div>
|
|
||||||
<InputLabel htmlFor="current_password" value="Current Password" />
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id="current_password"
|
|
||||||
ref={currentPasswordInput}
|
|
||||||
value={data.current_password}
|
|
||||||
onChange={(e) => setData('current_password', e.target.value)}
|
|
||||||
type="password"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
autoComplete="current-password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError message={errors.current_password} className="mt-2" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<InputLabel htmlFor="password" value="New Password" />
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id="password"
|
|
||||||
ref={passwordInput}
|
|
||||||
value={data.password}
|
|
||||||
onChange={(e) => setData('password', e.target.value)}
|
|
||||||
type="password"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError message={errors.password} className="mt-2" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<InputLabel htmlFor="password_confirmation" value="Confirm Password" />
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id="password_confirmation"
|
|
||||||
value={data.password_confirmation}
|
|
||||||
onChange={(e) => setData('password_confirmation', e.target.value)}
|
|
||||||
type="password"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError message={errors.password_confirmation} className="mt-2" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<PrimaryButton disabled={processing}>Save</PrimaryButton>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
show={recentlySuccessful}
|
|
||||||
enter="transition ease-in-out"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
leave="transition ease-in-out"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<p className="text-sm text-gray-600">Saved.</p>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
import InputError from '@/Components/InputError';
|
|
||||||
import InputLabel from '@/Components/InputLabel';
|
|
||||||
import PrimaryButton from '@/Components/PrimaryButton';
|
|
||||||
import TextInput from '@/Components/TextInput';
|
|
||||||
import { Link, useForm, usePage } from '@inertiajs/react';
|
|
||||||
import { Transition } from '@headlessui/react';
|
|
||||||
|
|
||||||
export default function UpdateProfileInformation({ mustVerifyEmail, status, className = '' }) {
|
|
||||||
const user = usePage().props.auth.user;
|
|
||||||
|
|
||||||
const { data, setData, patch, errors, processing, recentlySuccessful } = useForm({
|
|
||||||
name: user.name,
|
|
||||||
email: user.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
patch(route('profile.update'));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={className}>
|
|
||||||
<header>
|
|
||||||
<h2 className="text-lg font-medium text-gray-900">Profile Information</h2>
|
|
||||||
|
|
||||||
<p className="mt-1 text-sm text-gray-600">
|
|
||||||
Update your account's profile information and email address.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<form onSubmit={submit} className="mt-6 space-y-6">
|
|
||||||
<div>
|
|
||||||
<InputLabel htmlFor="name" value="Name" />
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id="name"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
value={data.name}
|
|
||||||
onChange={(e) => setData('name', e.target.value)}
|
|
||||||
required
|
|
||||||
isFocused
|
|
||||||
autoComplete="name"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError className="mt-2" message={errors.name} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<InputLabel htmlFor="email" value="Email" />
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
value={data.email}
|
|
||||||
onChange={(e) => setData('email', e.target.value)}
|
|
||||||
required
|
|
||||||
autoComplete="username"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError className="mt-2" message={errors.email} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{mustVerifyEmail && user.email_verified_at === null && (
|
|
||||||
<div>
|
|
||||||
<p className="text-sm mt-2 text-gray-800">
|
|
||||||
Your email address is unverified.
|
|
||||||
<Link
|
|
||||||
href={route('verification.send')}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
Click here to re-send the verification email.
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{status === 'verification-link-sent' && (
|
|
||||||
<div className="mt-2 font-medium text-sm text-green-600">
|
|
||||||
A new verification link has been sent to your email address.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<PrimaryButton disabled={processing}>Save</PrimaryButton>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
show={recentlySuccessful}
|
|
||||||
enter="transition ease-in-out"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
leave="transition ease-in-out"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<p className="text-sm text-gray-600">Saved.</p>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -44,71 +44,73 @@ export default function ProfilePage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-4xl mx-auto bg-base-100 shadow-sm rounded-lg overflow-hidden">
|
<div className="w-full card max-w-4xl mx-auto bg-base-100 shadow-sm rounded-lg overflow-hidden">
|
||||||
<Head title="Profile Page" />
|
<Head title="Profile Page" />
|
||||||
|
|
||||||
<div className="px-6 py-4 flex items-center justify-between">
|
<div className="card-body">
|
||||||
<div className="flex items-center">
|
<div className="px-6 py-4 flex items-center justify-between">
|
||||||
<div className="relative h-16 w-16 rounded-full overflow-hidden mr-4 border border-gray-200 cursor-pointer group">
|
<div className="flex items-center">
|
||||||
<label htmlFor="photo-upload" className="block relative h-16 w-16 rounded-full overflow-hidden border-4 border-transparent hover:border-blue-500 cursor-pointer">
|
<div className="relative h-16 w-16 rounded-full overflow-hidden mr-4 border border-gray-200 cursor-pointer group">
|
||||||
<img
|
<label htmlFor="photo-upload" className="block relative h-16 w-16 rounded-full overflow-hidden border-4 border-transparent hover:border-blue-500 cursor-pointer">
|
||||||
src={preview || auth.user.foto || "/fotoSantri/no-pic.png"}
|
<img
|
||||||
alt="Profile"
|
src={preview || auth.user.foto || "/fotoSantri/no-pic.png"}
|
||||||
className="h-full w-full object-cover transition duration-200"
|
alt="Profile"
|
||||||
/>
|
className="h-full w-full object-cover transition duration-200"
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black opacity-0 hover:opacity-30 transition duration-200 rounded-full">
|
/>
|
||||||
<svg
|
<div className="absolute inset-0 flex items-center justify-center bg-black opacity-0 hover:opacity-30 transition duration-200 rounded-full">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<svg
|
||||||
className="h-8 w-8 text-white"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
className="h-8 w-8 text-white"
|
||||||
viewBox="0 0 24 24"
|
fill="none"
|
||||||
stroke="currentColor"
|
viewBox="0 0 24 24"
|
||||||
>
|
stroke="currentColor"
|
||||||
<path
|
>
|
||||||
strokeLinecap="round"
|
<path
|
||||||
strokeLinejoin="round"
|
strokeLinecap="round"
|
||||||
strokeWidth="2"
|
strokeLinejoin="round"
|
||||||
d="M12 4.5c-3.315 0-6 2.685-6 6s2.685 6 6 6 6-2.685 6-6-2.685-6-6-6zM12 10.5a2.25 2.25 0 100-4.5 2.25 2.25 0 000 4.5zm-6 5.25h12c.828 0 1.5.672 1.5 1.5v3c0 .828-.672 1.5-1.5 1.5H6c-.828 0-1.5-.672-1.5-1.5v-3c0-.828.672-1.5 1.5-1.5z"
|
strokeWidth="2"
|
||||||
/>
|
d="M12 4.5c-3.315 0-6 2.685-6 6s2.685 6 6 6 6-2.685 6-6-2.685-6-6-6zM12 10.5a2.25 2.25 0 100-4.5 2.25 2.25 0 000 4.5zm-6 5.25h12c.828 0 1.5.672 1.5 1.5v3c0 .828-.672 1.5-1.5 1.5H6c-.828 0-1.5-.672-1.5-1.5v-3c0-.828.672-1.5 1.5-1.5z"
|
||||||
</svg>
|
/>
|
||||||
</div>
|
</svg>
|
||||||
<input
|
</div>
|
||||||
id="photo-upload"
|
<input
|
||||||
type="file"
|
id="photo-upload"
|
||||||
accept="image/*"
|
type="file"
|
||||||
className="hidden"
|
accept="image/*"
|
||||||
onChange={handlePhotoChange}
|
className="hidden"
|
||||||
/>
|
onChange={handlePhotoChange}
|
||||||
</label>
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold">{auth.user.nama}</h2>
|
<h2 className="text-xl font-semibold">{auth.user.nama}</h2>
|
||||||
<p className="text-sm">{auth.user.level == 1 ? 'Admin' : ''}</p>
|
<p className="text-sm">{auth.user.level == 1 ? 'Admin' : ''}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="px-6 py-4" encType='multipart/form-data'>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<Input label="Nama Lengkap" name="nama" value={data.nama} onChange={setData} error={errors.nama} />
|
||||||
|
<Input label="Alamat" name="alamat" value={data.alamat} onChange={setData} error={errors.alamat} />
|
||||||
|
<Input label="No Telepon" name="no_telp" value={data.no_telp} onChange={setData} error={errors.no_telp} />
|
||||||
|
<Input label="Tanggal Lahir" name="tanggal_lahir" type="date" value={data.tanggal_lahir} onChange={setData} error={errors.tanggal_lahir} />
|
||||||
|
<Select label="Jenis Kelamin" name="jk" value={data.jk} onChange={setData} options={['laki laki', 'perempuan']} error={errors.jk} />
|
||||||
|
<Input label="Password" name="password" type="password" value={data.password} onChange={setData} error={errors.password} placeholder="Kosongkan jika tidak ingin mengganti password" />
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 text-right">
|
||||||
|
<button
|
||||||
|
type='submit'
|
||||||
|
className="bg-blue-500 text-white px-4 py-2 rounded-md text-sm font-medium"
|
||||||
|
disabled={processing}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="px-6 py-4" encType='multipart/form-data'>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<Input label="Nama Lengkap" name="nama" value={data.nama} onChange={setData} error={errors.nama} />
|
|
||||||
<Input label="Alamat" name="alamat" value={data.alamat} onChange={setData} error={errors.alamat} />
|
|
||||||
<Input label="No Telepon" name="no_telp" value={data.no_telp} onChange={setData} error={errors.no_telp} />
|
|
||||||
<Input label="Tanggal Lahir" name="tanggal_lahir" type="date" value={data.tanggal_lahir} onChange={setData} error={errors.tanggal_lahir} />
|
|
||||||
<Select label="Jenis Kelamin" name="jk" value={data.jk} onChange={setData} options={['laki laki', 'perempuan']} error={errors.jk} />
|
|
||||||
<Input label="Password" name="password" type="password" value={data.password} onChange={setData} error={errors.password} placeholder="Kosongkan jika tidak ingin mengganti password" />
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 text-right">
|
|
||||||
<button
|
|
||||||
type='submit'
|
|
||||||
className="bg-blue-500 text-white px-4 py-2 rounded-md text-sm font-medium"
|
|
||||||
disabled={processing}
|
|
||||||
>
|
|
||||||
Simpan
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import ModalInput from '@/Components/ModalInput';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||||
import { CurrencyDollarIcon } from '@heroicons/react/24/outline'
|
import { CurrencyDollarIcon } from '@heroicons/react/24/outline'
|
||||||
|
import { usePage } from '@inertiajs/react';
|
||||||
|
import Swal from "sweetalert2"
|
||||||
|
|
||||||
|
|
||||||
export default function ManualPayment({ santri, fields, options }) {
|
export default function ManualPayment({ santri, fields, options }) {
|
||||||
|
@ -11,9 +13,26 @@ export default function ManualPayment({ santri, fields, options }) {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [filteredSantri, setFilteredSantri] = useState([]);
|
const [filteredSantri, setFilteredSantri] = useState([]);
|
||||||
const [selectedPayments, setSelectedPayments] = useState(null);
|
const [selectedPayments, setSelectedPayments] = useState(null);
|
||||||
|
const { flash } = usePage().props;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (flash.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
} else if (flash.error) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [flash]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(setPageTitle("Data Payment"));
|
dispatch(setPageTitle("Data Payment"));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
|
@ -4,15 +4,34 @@ import ModalInput from '@/Components/ModalInput';
|
||||||
import DeleteButton from '@/Components/DeleteButton';
|
import DeleteButton from '@/Components/DeleteButton';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||||
|
import { usePage } from '@inertiajs/react';
|
||||||
|
import Swal from "sweetalert2"
|
||||||
|
|
||||||
export default function PaymentType({ paymentType, fields }) {
|
export default function PaymentType({ paymentType, fields }) {
|
||||||
const [selectedPaymentType, setSelectedPaymentType] = useState(null);
|
const [selectedPaymentType, setSelectedPaymentType] = useState(null);
|
||||||
const [isDeleteOpen, setDeleteOpen] = useState(false);
|
const [isDeleteOpen, setDeleteOpen] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [filteredPaymentTypes, setFilteredPaymentTypes] = useState([]);
|
const [filteredPaymentTypes, setFilteredPaymentTypes] = useState([]);
|
||||||
|
const { flash } = usePage().props;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (flash.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
} else if (flash.error) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [flash]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(setPageTitle("Data Tipe Pembayaran"));
|
dispatch(setPageTitle("Data Tipe Pembayaran"));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||||
import ModalInput from '@/Components/ModalInput';
|
import ModalInput from '@/Components/ModalInput';
|
||||||
import DeleteButton from '@/Components/DeleteButton';
|
import DeleteButton from '@/Components/DeleteButton';
|
||||||
import { UserGroupIcon } from '@heroicons/react/24/outline'
|
import { UserGroupIcon } from '@heroicons/react/24/outline'
|
||||||
|
import { usePage } from '@inertiajs/react';
|
||||||
|
import Swal from "sweetalert2"
|
||||||
|
|
||||||
|
|
||||||
export default function IndexSantri({ santri, fields, options }) {
|
export default function IndexSantri({ santri, fields, options }) {
|
||||||
|
@ -13,12 +15,29 @@ export default function IndexSantri({ santri, fields, options }) {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [filteredSantri, setFilteredSantri] = useState([]);
|
const [filteredSantri, setFilteredSantri] = useState([]);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const { flash } = usePage().props;
|
||||||
|
|
||||||
const openDeleteModal = (item) => {
|
const openDeleteModal = (item) => {
|
||||||
setSelectedSantri(item)
|
setSelectedSantri(item)
|
||||||
setDeleteOpen(true)
|
setDeleteOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (flash.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
} else if (flash.error) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: flash.success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [flash]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(setPageTitle("Data Santri"));
|
dispatch(setPageTitle("Data Santri"));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
Loading…
Reference in New Issue