semifinal

This commit is contained in:
alealien666 2025-05-08 18:24:22 +07:00
parent 8617fce4aa
commit 91dff8b258
40 changed files with 190 additions and 1459 deletions

View File

@ -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('/');
} }
} }

View File

@ -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()]);
} }
} }

View File

@ -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());
} }

View File

@ -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'),
],
]; ];
} }
} }

View File

@ -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'),
],
]);
} }
} }

13
package-lock.json generated
View File

@ -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",

View File

@ -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

View File

@ -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({

View File

@ -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(() => {

View File

@ -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,

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 () => {

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -44,9 +44,10 @@ 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="card-body">
<div className="px-6 py-4 flex items-center justify-between"> <div className="px-6 py-4 flex items-center justify-between">
<div className="flex items-center"> <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"> <div className="relative h-16 w-16 rounded-full overflow-hidden mr-4 border border-gray-200 cursor-pointer group">
@ -110,6 +111,7 @@ export default function ProfilePage() {
</div> </div>
</form> </form>
</div> </div>
</div>
); );
} }

View File

@ -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]);

View File

@ -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]);

View File

@ -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]);