improve user table filters

This commit is contained in:
vincanger 2023-11-21 13:07:37 +01:00
parent af979e2cd0
commit b6fd33f9e2
9 changed files with 157 additions and 142 deletions

View File

@ -363,6 +363,7 @@ job dailyStats {
schedule: { schedule: {
// every hour // every hour
cron: "0 * * * *" cron: "0 * * * *"
// cron: "* * * * *"
}, },
entities: [User, DailyStats, Logs] entities: [User, DailyStats, Logs]
} }

View File

@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
const CheckboxOne = () => { const CheckboxOne = () => {
const [isChecked, setIsChecked] = useState<boolean>(false); const [isChecked, setIsChecked] = useState<boolean>( false);
return ( return (
<div> <div>

View File

@ -1,47 +1,37 @@
import { useState } from 'react'; import { useState } from 'react';
const CheckboxTwo = () => { const CheckboxTwo = () => {
const [isChecked, setIsChecked] = useState<boolean>(false); const [enabled, setEnabled] = useState<boolean>(false);
return ( return (
<div> <div>
<label <label htmlFor='checkboxLabelTwo' className='flex cursor-pointer text-sm text-gray-700 select-none items-center'>
htmlFor="checkboxLabelTwo" hasPaid:
className="flex cursor-pointer select-none items-center" <div className='relative'>
>
<div className="relative">
<input <input
type="checkbox" type='checkbox'
id="checkboxLabelTwo" id='checkboxLabelTwo'
className="sr-only" className='sr-only'
onChange={() => { onChange={() => {
setIsChecked(!isChecked); setEnabled(!enabled);
}} }}
/> />
<div <div
className={`mr-4 flex h-5 w-5 items-center justify-center rounded border ${ className={`ml-2 flex h-5 w-5 items-center justify-center rounded border ${
isChecked && 'border-primary bg-gray dark:bg-transparent' enabled && 'border-primary bg-gray dark:bg-transparent'
}`} }`}
> >
<span className={`opacity-0 ${isChecked && '!opacity-100'}`}> <span className={`opacity-0 ${enabled && '!opacity-100'}`}>
<svg <svg width='11' height='8' viewBox='0 0 11 8' fill='none' xmlns='http://www.w3.org/2000/svg'>
width="11"
height="8"
viewBox="0 0 11 8"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path <path
d="M10.0915 0.951972L10.0867 0.946075L10.0813 0.940568C9.90076 0.753564 9.61034 0.753146 9.42927 0.939309L4.16201 6.22962L1.58507 3.63469C1.40401 3.44841 1.11351 3.44879 0.932892 3.63584C0.755703 3.81933 0.755703 4.10875 0.932892 4.29224L0.932878 4.29225L0.934851 4.29424L3.58046 6.95832C3.73676 7.11955 3.94983 7.2 4.1473 7.2C4.36196 7.2 4.55963 7.11773 4.71406 6.9584L10.0468 1.60234C10.2436 1.4199 10.2421 1.1339 10.0915 0.951972ZM4.2327 6.30081L4.2317 6.2998C4.23206 6.30015 4.23237 6.30049 4.23269 6.30082L4.2327 6.30081Z" d='M10.0915 0.951972L10.0867 0.946075L10.0813 0.940568C9.90076 0.753564 9.61034 0.753146 9.42927 0.939309L4.16201 6.22962L1.58507 3.63469C1.40401 3.44841 1.11351 3.44879 0.932892 3.63584C0.755703 3.81933 0.755703 4.10875 0.932892 4.29224L0.932878 4.29225L0.934851 4.29424L3.58046 6.95832C3.73676 7.11955 3.94983 7.2 4.1473 7.2C4.36196 7.2 4.55963 7.11773 4.71406 6.9584L10.0468 1.60234C10.2436 1.4199 10.2421 1.1339 10.0915 0.951972ZM4.2327 6.30081L4.2317 6.2998C4.23206 6.30015 4.23237 6.30049 4.23269 6.30082L4.2327 6.30081Z'
fill="#3056D3" fill='#3056D3'
stroke="#3056D3" stroke='#3056D3'
strokeWidth="0.4" strokeWidth='0.4'
></path> ></path>
</svg> </svg>
</span> </span>
</div> </div>
</div> </div>
Checkbox Text
</label> </label>
</div> </div>
); );

View File

@ -1,13 +1,9 @@
import { useState, useEffect } from 'react'; import { useState } from 'react';
import { User } from '@wasp/entities'; import { User } from '@wasp/entities';
const SwitcherOne = ({ user, updateUserById}: { user?: Partial<User>, updateUserById?: any}) => { const SwitcherOne = ({ user, updateUserById}: { user?: Partial<User>, updateUserById?: any}) => {
const [enabled, setEnabled] = useState<boolean>(user?.hasPaid || false); const [enabled, setEnabled] = useState<boolean>(user?.hasPaid || false);
// useEffect(() => {
// console.table({ hasPaid: user?.hasPaid})
// }, [user])
return ( return (
<div className='relative'> <div className='relative'>
<label htmlFor={`toggle1-${user?.id}`} className='flex cursor-pointer select-none items-center'> <label htmlFor={`toggle1-${user?.id}`} className='flex cursor-pointer select-none items-center'>

View File

@ -6,16 +6,17 @@ import getPaginatedUsers from '@wasp/queries/getPaginatedUsers';
import updateUserById from '@wasp/actions/updateUserById'; import updateUserById from '@wasp/actions/updateUserById';
import Loader from '../common/Loader'; import Loader from '../common/Loader';
// TODO extract hasPaid to its own value type StatusOptions = 'past_due' | 'canceled' | 'active';
type StatusOptions = 'hasPaid' | 'past_due' | 'canceled' | 'active';
const UsersTable = () => { const UsersTable = () => {
const [skip, setskip] = useState(0); const [skip, setskip] = useState(0);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [email, setEmail] = useState<string | undefined>(undefined); const [email, setEmail] = useState<string | undefined>(undefined);
const [statusOptions, setStatusOptions] = useState<StatusOptions[]>([]); const [statusOptions, setStatusOptions] = useState<StatusOptions[]>([]);
const [hasPaidFilter, setHasPaidFilter] = useState<boolean | undefined>(undefined);
const { data, isLoading, error } = useQuery(getPaginatedUsers, { const { data, isLoading, error } = useQuery(getPaginatedUsers, {
skip, skip,
hasPaidFilter: hasPaidFilter,
emailContains: email, emailContains: email,
subscriptionStatus: statusOptions?.length > 0 ? statusOptions : undefined, subscriptionStatus: statusOptions?.length > 0 ? statusOptions : undefined,
}); });
@ -31,19 +32,25 @@ const UsersTable = () => {
return ( return (
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
<div className='rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark'> <div className='rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark'>
<div className='flex items-center justify-between gap-3 w-full py-6 px-4 md:px-6 xl:px-7.5'> <div className='flex-col flex items-start justify-between p-6 gap-3 w-full '>
<div className='relative flex items-center gap-3 p-4'> <span className='text-sm font-semibold text-gray-700'>Filters:</span>
<span>Filters:</span> <div className='flex items-center justify-between gap-3 w-full px-2'>
{/* <label className='block text-black dark:text-white whitespace-nowrap'>Search by Email</label> */} <div className='relative flex items-center gap-3 '>
<label htmlFor='email-filter' className='block text-sm text-gray-700 dark:text-white'>
email:
</label>
<input <input
type='text' type='text'
id='email-filter'
placeholder='dude@example.com' placeholder='dude@example.com'
onChange={(e) => { onChange={(e) => {
setEmail(e.currentTarget.value); setEmail(e.currentTarget.value);
}} }}
className='rounded border border-stroke bg-transparent py-2 px-5 outline-none transition focus:border-primary active:border-primary disabled:cursor-default disabled:bg-whiter dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary' className='rounded border border-stroke bg-transparent py-2 px-5 outline-none transition focus:border-primary active:border-primary disabled:cursor-default disabled:bg-whiter dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary'
/> />
{/* <label className='mb-3 block text-black dark:text-white whitespace-nowrap'>Multiselect Dropdown</label> */} <label htmlFor='status-filter' className='block text-sm ml-2 text-gray-700 dark:text-white'>
status:
</label>
<div className='flex-grow relative z-20 rounded border border-stroke pr-8 outline-none transition focus:border-primary active:border-primary dark:border-form-strokedark dark:bg-form-input'> <div className='flex-grow relative z-20 rounded border border-stroke pr-8 outline-none transition focus:border-primary active:border-primary dark:border-form-strokedark dark:bg-form-input'>
<div className='flex items-center'> <div className='flex items-center'>
{!!statusOptions && statusOptions.length > 0 ? ( {!!statusOptions && statusOptions.length > 0 ? (
@ -62,7 +69,13 @@ const UsersTable = () => {
}} }}
className='z-30 cursor-pointer pl-2 hover:text-danger' className='z-30 cursor-pointer pl-2 hover:text-danger'
> >
<svg width='14' height='14' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'> <svg
width='14'
height='14'
viewBox='0 0 12 12'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path <path
fillRule='evenodd' fillRule='evenodd'
clipRule='evenodd' clipRule='evenodd'
@ -75,7 +88,7 @@ const UsersTable = () => {
)) ))
) : ( ) : (
<span className='bg-transparent text-gray-500 py-2 px-5 outline-none transition focus:border-primary active:border-primary disabled:cursor-default disabled:bg-whiter dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary'> <span className='bg-transparent text-gray-500 py-2 px-5 outline-none transition focus:border-primary active:border-primary disabled:cursor-default disabled:bg-whiter dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary'>
Select Payment Status Filters Select Status Filters
</span> </span>
)} )}
</div> </div>
@ -91,12 +104,12 @@ const UsersTable = () => {
} }
}); });
}} }}
name='status' name='status-filter'
id='status' id='status-filter'
className='absolute top-0 left-0 z-20 h-full w-full bg-transparent opacity-0' className='absolute top-0 left-0 z-20 h-full w-full bg-transparent opacity-0'
> >
<option value=''>Select filters</option> <option value=''>Select filters</option>
{['hasPaid', 'past_due', 'canceled', 'active'].map((status) => { {['past_due', 'canceled', 'active'].map((status) => {
if (!statusOptions.includes(status as StatusOptions)) { if (!statusOptions.includes(status as StatusOptions)) {
return <option value={status}>{status}</option>; return <option value={status}>{status}</option>;
} }
@ -115,8 +128,27 @@ const UsersTable = () => {
</svg> </svg>
</span> </span>
</div> </div>
<div className='flex items-center gap-2'>
<label htmlFor='hasPaid-filter' className='block text-sm ml-2 text-gray-700 dark:text-white'>
hasPaid:
</label>
<select
name='hasPaid-filter'
onChange={(e) => {
if (e.target.value === 'both') {
setHasPaidFilter(undefined);
} else {
setHasPaidFilter(e.target.value === 'true');
}
}}
className='relative z-20 w-full appearance-none rounded border border-stroke bg-transparent p-2 pl-4 pr-8 outline-none transition focus:border-primary active:border-primary dark:border-form-strokedark dark:bg-form-input'
>
<option value='both'>both</option>
<option value='true'>true</option>
<option value='false'>false</option>
</select>
</div>
</div> </div>
{!isLoading && ( {!isLoading && (
<div className='max-w-60'> <div className='max-w-60'>
<span className='text-md mr-2 text-black dark:text-white'>page</span> <span className='text-md mr-2 text-black dark:text-white'>page</span>
@ -133,8 +165,9 @@ const UsersTable = () => {
</div> </div>
)} )}
</div> </div>
</div>
<div className='grid grid-cols-12 border-t border-stroke py-4.5 px-4 dark:border-strokedark md:px-6 '> <div className='grid grid-cols-12 border-t-4 border-stroke py-4.5 px-4 dark:border-strokedark md:px-6 '>
<div className='col-span-3 flex items-center'> <div className='col-span-3 flex items-center'>
<p className='font-medium'>Email</p> <p className='font-medium'>Email</p>
</div> </div>

View File

@ -1,6 +1,5 @@
import Breadcrumb from '../../components/Breadcrumb'; import Breadcrumb from '../../components/Breadcrumb';
import CheckboxOne from '../../components/CheckboxOne'; import CheckboxOne from '../../components/CheckboxOne';
import CheckboxTwo from '../../components/CheckboxTwo';
import SwitcherOne from '../../components/SwitcherOne'; import SwitcherOne from '../../components/SwitcherOne';
import SwitcherTwo from '../../components/SwitcherTwo'; import SwitcherTwo from '../../components/SwitcherTwo';
import DefaultLayout from '../../layout/DefaultLayout'; import DefaultLayout from '../../layout/DefaultLayout';
@ -188,7 +187,6 @@ const FormElements = () => {
</div> </div>
<div className="flex flex-col gap-5.5 p-6.5"> <div className="flex flex-col gap-5.5 p-6.5">
<CheckboxOne /> <CheckboxOne />
<CheckboxTwo />
</div> </div>
</div> </div>

View File

@ -6,7 +6,6 @@ const Users = () => {
return ( return (
<DefaultLayout> <DefaultLayout>
<Breadcrumb pageName="Users" /> <Breadcrumb pageName="Users" />
<div className="flex flex-col gap-10"> <div className="flex flex-col gap-10">
<UsersTable /> <UsersTable />
</div> </div>

View File

@ -65,6 +65,7 @@ export const getReferrerStats: GetReferrerStats<void, ReferrerWithSanitizedUsers
type GetPaginatedUsersInput = { type GetPaginatedUsersInput = {
skip: number; skip: number;
cursor?: number | undefined; cursor?: number | undefined;
hasPaidFilter: boolean | undefined;
emailContains?: string; emailContains?: string;
subscriptionStatus?: string[] subscriptionStatus?: string[]
}; };
@ -78,11 +79,6 @@ export const getPaginatedUsers: GetPaginatedUsers<GetPaginatedUsersInput, GetPag
context context
) => { ) => {
let hasPaid = undefined
if (!!args.subscriptionStatus && args.subscriptionStatus.includes('hasPaid')) {
hasPaid = true
}
let subscriptionStatus = args.subscriptionStatus?.filter((status) => status !== 'hasPaid') let subscriptionStatus = args.subscriptionStatus?.filter((status) => status !== 'hasPaid')
subscriptionStatus = subscriptionStatus?.length ? subscriptionStatus : undefined subscriptionStatus = subscriptionStatus?.length ? subscriptionStatus : undefined
@ -94,7 +90,7 @@ export const getPaginatedUsers: GetPaginatedUsers<GetPaginatedUsersInput, GetPag
contains: args.emailContains || undefined, contains: args.emailContains || undefined,
mode: 'insensitive', mode: 'insensitive',
}, },
hasPaid, hasPaid: args.hasPaidFilter,
subscriptionStatus: { subscriptionStatus: {
in: subscriptionStatus || undefined, in: subscriptionStatus || undefined,
}, },
@ -117,7 +113,7 @@ export const getPaginatedUsers: GetPaginatedUsers<GetPaginatedUsersInput, GetPag
email: { email: {
contains: args.emailContains || undefined, contains: args.emailContains || undefined,
}, },
hasPaid, hasPaid: args.hasPaidFilter,
subscriptionStatus: { subscriptionStatus: {
in: subscriptionStatus || undefined, in: subscriptionStatus || undefined,
}, },

View File

@ -43,7 +43,7 @@ export const calculateDailyStats: DailyStats<never, void> = async (_args, contex
const newRunningTotal = await calculateTotalRevenue(context); const newRunningTotal = await calculateTotalRevenue(context);
await context.entities.DailyStats.upsert({ const newDailyStat = await context.entities.DailyStats.upsert({
where: { where: {
date: nowUTC, date: nowUTC,
}, },
@ -64,6 +64,8 @@ export const calculateDailyStats: DailyStats<never, void> = async (_args, contex
}, },
}); });
console.table({ newDailyStat })
} catch (error: any) { } catch (error: any) {
console.error('Error calculating daily stats: ', error); console.error('Error calculating daily stats: ', error);
await context.entities.Logs.create({ await context.entities.Logs.create({