semifinal
This commit is contained in:
parent
8617fce4aa
commit
91dff8b258
|
@ -34,6 +34,8 @@ public function store(LoginRequest $request): RedirectResponse
|
|||
|
||||
$request->session()->regenerate();
|
||||
|
||||
session()->flash('success', 'Login berhasil!');
|
||||
|
||||
return redirect()->intended(RouteServiceProvider::HOME);
|
||||
}
|
||||
|
||||
|
@ -48,6 +50,8 @@ public function destroy(Request $request): RedirectResponse
|
|||
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
session()->flash('success', 'Logout berhasil!');
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,6 @@ public function manualPayment(Request $request, $paymentId)
|
|||
DB::beginTransaction();
|
||||
|
||||
|
||||
// Ambil semua payment type sekaligus
|
||||
$paymentTypes = PaymentType::pluck('nominal', 'id');
|
||||
|
||||
$existingPayment = Payment::where('user_id', $userId)
|
||||
|
@ -80,7 +79,6 @@ public function manualPayment(Request $request, $paymentId)
|
|||
$range = $item['range'];
|
||||
$nominal = $paymentTypes[$typeId] ?? 0;
|
||||
|
||||
// Ambil unpaid details
|
||||
$unpaidDetails = DetailPayment::where('payment_id', $existingPayment->id)
|
||||
->where('status', 'unpaid')
|
||||
->where('type_id', $typeId)
|
||||
|
@ -88,7 +86,6 @@ public function manualPayment(Request $request, $paymentId)
|
|||
->orderBy('payment_month')
|
||||
->get();
|
||||
|
||||
// Proses unpaid details
|
||||
$toPay = $unpaidDetails->take($range);
|
||||
$toPay->each(function ($detail) use (&$totalAmount, $nominal) {
|
||||
$detail->update([
|
||||
|
@ -99,10 +96,8 @@ public function manualPayment(Request $request, $paymentId)
|
|||
$totalAmount += $nominal + $detail->penalty;
|
||||
});
|
||||
|
||||
// Hitung sisa bulan yang perlu dibuat
|
||||
$sisa = $range - $toPay->count();
|
||||
if ($sisa > 0) {
|
||||
// Ambil bulan/tahun terakhir
|
||||
$lastDetail = DetailPayment::where('payment_id', $existingPayment->id)
|
||||
->orderBy('payment_year', 'desc')
|
||||
->orderBy('payment_month', 'desc')
|
||||
|
@ -133,7 +128,6 @@ public function manualPayment(Request $request, $paymentId)
|
|||
}
|
||||
}
|
||||
|
||||
// Update payment status
|
||||
$existingPayment->update([
|
||||
'amount_payment' => DetailPayment::where('payment_id', $existingPayment->id)->sum('amount'),
|
||||
'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']);
|
||||
}
|
||||
|
||||
// Jika tidak ada existing payment, buat baru
|
||||
$newPayment = Payment::create([
|
||||
'payment_status' => 'success',
|
||||
'amount_payment' => 0,
|
||||
|
@ -184,12 +177,12 @@ public function manualPayment(Request $request, $paymentId)
|
|||
|
||||
$newPayment->update(['amount_payment' => $totalAmount]);
|
||||
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) {
|
||||
DB::rollBack();
|
||||
dd('gagal' . $e->getMessage());
|
||||
// dd('gagal' . $e->getMessage());
|
||||
return redirect()->back()->with(['error' => 'gagal' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public function store(Request $request)
|
|||
'nominal' => $request->nominal
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'berhasil insert data');
|
||||
return redirect()->back()->with('success', 'berhasil insert data tipe pembayaran');
|
||||
} catch (\Throwable $th) {
|
||||
return redirect()->back()->with('error', 'Data gagal di tambahkan' . $th->getMessage());
|
||||
}
|
||||
|
|
|
@ -34,6 +34,10 @@ public function share(Request $request): array
|
|||
'auth' => [
|
||||
'user' => $request->user(),
|
||||
],
|
||||
'flash' => [
|
||||
'success' => fn() => $request->session()->get('success'),
|
||||
'error' => fn() => $request->session()->get('error'),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -19,6 +20,11 @@ public function register(): void
|
|||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
Inertia::share([
|
||||
'flash' => [
|
||||
'success' => session('success'),
|
||||
'error' => session('error'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"react-chartjs-2": "^5.3.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-tailwindcss-datepicker": "^2.0.0",
|
||||
"sweetalert2": "^11.21.0",
|
||||
"theme-change": "^2.5.0",
|
||||
"web-vitals": "^4.2.4"
|
||||
},
|
||||
|
@ -25,7 +26,6 @@
|
|||
"laravel-vite-plugin": "^0.7.2",
|
||||
"postcss": "^8.4.31",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwindcss": "^3.2.1",
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
|
@ -2449,6 +2449,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
|
@ -2610,6 +2611,7 @@
|
|||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
|
@ -2863,6 +2865,15 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sweetalert2": {
|
||||
"version": "11.21.0",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.21.0.tgz",
|
||||
"integrity": "sha512-fiEK7SqRY/QD/wC2uqEHlfYGZ7qe2UcyQbJpbpj4YRVqplBgcI+euPZLZL+evLINcvbtXmL1SFUdZHKqBHGAAQ==",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/limonte"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"react-chartjs-2": "^5.3.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-tailwindcss-datepicker": "^2.0.0",
|
||||
"sweetalert2": "^11.21.0",
|
||||
"theme-change": "^2.5.0",
|
||||
"web-vitals": "^4.2.4"
|
||||
}
|
||||
|
|
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 modalSlice from '../Components/features/common/modalSlice'
|
||||
import rightDrawerSlice from '../Components/features/common/rightDrawerSlice'
|
||||
import leadsSlice from '../Components/features/leads/leadSlice'
|
||||
|
||||
const combinedReducer = {
|
||||
header: headerSlice,
|
||||
rightDrawer: rightDrawerSlice,
|
||||
modal: modalSlice,
|
||||
lead: leadsSlice
|
||||
}
|
||||
|
||||
export default configureStore({
|
||||
|
|
|
@ -8,12 +8,12 @@ import { usePage } from "@inertiajs/react";
|
|||
|
||||
function Layout({ children }) {
|
||||
const dispatch = useDispatch();
|
||||
const { url } = usePage(); // Gunakan usePage() dari Inertia
|
||||
const { url, } = usePage();
|
||||
const [showSidebar, setShowSidebar] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Route berubah (Inertia):", url);
|
||||
setShowSidebar(!url.startsWith("/login")); // Sidebar disembunyikan di halaman login
|
||||
// console.log("Route berubah (Inertia):", url);
|
||||
setShowSidebar(!url.startsWith("/login"));
|
||||
}, [url]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -3,13 +3,30 @@ import { Suspense, useEffect, useRef, useState } from "react";
|
|||
import SuspenseContent from "./SuspenseContent";
|
||||
import { useSelector } from 'react-redux';
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import Swal from "sweetalert2"
|
||||
|
||||
function PageContent({ children }) {
|
||||
const mainContentRef = useRef(null);
|
||||
const { pageTitle } = useSelector(state => state.header);
|
||||
const { url } = usePage(); // Ambil URL dari Inertia
|
||||
const [isLoginPage, setIsLoginPage] = useState(false);
|
||||
const { flash } = usePage().props;
|
||||
|
||||
useEffect(() => {
|
||||
if (flash.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Success',
|
||||
text: flash.success
|
||||
});
|
||||
} else if (flash.error) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Success',
|
||||
text: flash.success
|
||||
});
|
||||
}
|
||||
}, [flash]);
|
||||
useEffect(() => {
|
||||
mainContentRef.current?.scroll({
|
||||
top: 0,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
function SuspenseContent(){
|
||||
return(
|
||||
function SuspenseContent() {
|
||||
return (
|
||||
<div className="w-full h-screen text-gray-300 dark:text-gray-200 bg-base-100">
|
||||
Loading...
|
||||
</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 { Head, Link, useForm } from '@inertiajs/react';
|
||||
import { usePage } from '@inertiajs/react';
|
||||
import Swal from "sweetalert2"
|
||||
// import { InertiaLink, usePage } from '@inertiajs/react';
|
||||
|
||||
function Login({ status }) {
|
||||
|
@ -8,6 +10,23 @@ function Login({ status }) {
|
|||
password: '',
|
||||
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(() => {
|
||||
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 (
|
||||
<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" />
|
||||
|
||||
<div className="px-6 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="relative h-16 w-16 rounded-full overflow-hidden mr-4 border border-gray-200 cursor-pointer group">
|
||||
<label htmlFor="photo-upload" className="block relative h-16 w-16 rounded-full overflow-hidden border-4 border-transparent hover:border-blue-500 cursor-pointer">
|
||||
<img
|
||||
src={preview || auth.user.foto || "/fotoSantri/no-pic.png"}
|
||||
alt="Profile"
|
||||
className="h-full w-full object-cover transition duration-200"
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black opacity-0 hover:opacity-30 transition duration-200 rounded-full">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-8 w-8 text-white"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 4.5c-3.315 0-6 2.685-6 6s2.685 6 6 6 6-2.685 6-6-2.685-6-6-6zM12 10.5a2.25 2.25 0 100-4.5 2.25 2.25 0 000 4.5zm-6 5.25h12c.828 0 1.5.672 1.5 1.5v3c0 .828-.672 1.5-1.5 1.5H6c-.828 0-1.5-.672-1.5-1.5v-3c0-.828.672-1.5 1.5-1.5z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="photo-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handlePhotoChange}
|
||||
/>
|
||||
</label>
|
||||
<div className="card-body">
|
||||
<div className="px-6 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="relative h-16 w-16 rounded-full overflow-hidden mr-4 border border-gray-200 cursor-pointer group">
|
||||
<label htmlFor="photo-upload" className="block relative h-16 w-16 rounded-full overflow-hidden border-4 border-transparent hover:border-blue-500 cursor-pointer">
|
||||
<img
|
||||
src={preview || auth.user.foto || "/fotoSantri/no-pic.png"}
|
||||
alt="Profile"
|
||||
className="h-full w-full object-cover transition duration-200"
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black opacity-0 hover:opacity-30 transition duration-200 rounded-full">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-8 w-8 text-white"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 4.5c-3.315 0-6 2.685-6 6s2.685 6 6 6 6-2.685 6-6-2.685-6-6-6zM12 10.5a2.25 2.25 0 100-4.5 2.25 2.25 0 000 4.5zm-6 5.25h12c.828 0 1.5.672 1.5 1.5v3c0 .828-.672 1.5-1.5 1.5H6c-.828 0-1.5-.672-1.5-1.5v-3c0-.828.672-1.5 1.5-1.5z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="photo-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handlePhotoChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">{auth.user.nama}</h2>
|
||||
<p className="text-sm">{auth.user.level == 1 ? 'Admin' : ''}</p>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">{auth.user.nama}</h2>
|
||||
<p className="text-sm">{auth.user.level == 1 ? 'Admin' : ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="px-6 py-4" encType='multipart/form-data'>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input label="Nama Lengkap" name="nama" value={data.nama} onChange={setData} error={errors.nama} />
|
||||
<Input label="Alamat" name="alamat" value={data.alamat} onChange={setData} error={errors.alamat} />
|
||||
<Input label="No Telepon" name="no_telp" value={data.no_telp} onChange={setData} error={errors.no_telp} />
|
||||
<Input label="Tanggal Lahir" name="tanggal_lahir" type="date" value={data.tanggal_lahir} onChange={setData} error={errors.tanggal_lahir} />
|
||||
<Select label="Jenis Kelamin" name="jk" value={data.jk} onChange={setData} options={['laki laki', 'perempuan']} error={errors.jk} />
|
||||
<Input label="Password" name="password" type="password" value={data.password} onChange={setData} error={errors.password} placeholder="Kosongkan jika tidak ingin mengganti password" />
|
||||
</div>
|
||||
<div className="mt-6 text-right">
|
||||
<button
|
||||
type='submit'
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded-md text-sm font-medium"
|
||||
disabled={processing}
|
||||
>
|
||||
Simpan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import ModalInput from '@/Components/ModalInput';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||
import { CurrencyDollarIcon } from '@heroicons/react/24/outline'
|
||||
import { usePage } from '@inertiajs/react';
|
||||
import Swal from "sweetalert2"
|
||||
|
||||
|
||||
export default function ManualPayment({ santri, fields, options }) {
|
||||
|
@ -11,9 +13,26 @@ export default function ManualPayment({ santri, fields, options }) {
|
|||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [filteredSantri, setFilteredSantri] = useState([]);
|
||||
const [selectedPayments, setSelectedPayments] = useState(null);
|
||||
const { flash } = usePage().props;
|
||||
|
||||
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(() => {
|
||||
dispatch(setPageTitle("Data Payment"));
|
||||
}, [dispatch]);
|
||||
|
|
|
@ -4,15 +4,34 @@ import ModalInput from '@/Components/ModalInput';
|
|||
import DeleteButton from '@/Components/DeleteButton';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
||||
import { usePage } from '@inertiajs/react';
|
||||
import Swal from "sweetalert2"
|
||||
|
||||
export default function PaymentType({ paymentType, fields }) {
|
||||
const [selectedPaymentType, setSelectedPaymentType] = useState(null);
|
||||
const [isDeleteOpen, setDeleteOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [filteredPaymentTypes, setFilteredPaymentTypes] = useState([]);
|
||||
const { flash } = usePage().props;
|
||||
|
||||
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(() => {
|
||||
dispatch(setPageTitle("Data Tipe Pembayaran"));
|
||||
}, [dispatch]);
|
||||
|
|
|
@ -5,6 +5,8 @@ import { setPageTitle } from '@/Components/features/common/headerSlice';
|
|||
import ModalInput from '@/Components/ModalInput';
|
||||
import DeleteButton from '@/Components/DeleteButton';
|
||||
import { UserGroupIcon } from '@heroicons/react/24/outline'
|
||||
import { usePage } from '@inertiajs/react';
|
||||
import Swal from "sweetalert2"
|
||||
|
||||
|
||||
export default function IndexSantri({ santri, fields, options }) {
|
||||
|
@ -13,12 +15,29 @@ export default function IndexSantri({ santri, fields, options }) {
|
|||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [filteredSantri, setFilteredSantri] = useState([]);
|
||||
const dispatch = useDispatch();
|
||||
const { flash } = usePage().props;
|
||||
|
||||
const openDeleteModal = (item) => {
|
||||
setSelectedSantri(item)
|
||||
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(() => {
|
||||
dispatch(setPageTitle("Data Santri"));
|
||||
}, [dispatch]);
|
||||
|
|
Loading…
Reference in New Issue