Merged develop into feature-paymentFilters

This commit is contained in:
João Ramos
2023-12-27 14:52:53 +00:00
4 changed files with 200 additions and 73 deletions

View File

@@ -6,11 +6,15 @@ interface Props {
isChecked: boolean; isChecked: boolean;
onChange: (isChecked: boolean) => void; onChange: (isChecked: boolean) => void;
children: ReactNode; children: ReactNode;
disabled?: boolean;
} }
export default function Checkbox({isChecked, onChange, children}: Props) { export default function Checkbox({isChecked, onChange, children, disabled}: Props) {
return ( return (
<div className="flex gap-3 items-center text-mti-gray-dim text-sm cursor-pointer" onClick={() => onChange(!isChecked)}> <div className="flex gap-3 items-center text-mti-gray-dim text-sm cursor-pointer" onClick={() => {
if(disabled) return;
onChange(!isChecked);
}}>
<input type="checkbox" className="hidden" /> <input type="checkbox" className="hidden" />
<div <div
className={clsx( className={clsx(

View File

@@ -36,9 +36,30 @@ interface Props {
onViewStudents?: () => void; onViewStudents?: () => void;
onViewTeachers?: () => void; onViewTeachers?: () => void;
onViewCorporate?: () => void; onViewCorporate?: () => void;
disabled?: boolean;
} }
const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers, onViewCorporate}: Props) => { const USER_STATUS_OPTIONS = [
{
value: 'active',
label: 'Active',
}, {
value: 'disabled',
label: 'Disabled',
}, {
value: 'paymentDue',
label: 'Payment Due',
}
];
const USER_TYPE_OPTIONS = Object.keys(USER_TYPE_LABELS).map((type) => ({
value: type,
label: USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]
}));
const CURRENCIES_OPTIONS = CURRENCIES.map(({ label, currency}) => ({ value: currency, label }));
const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers, onViewCorporate, disabled = false}: Props) => {
const [expiryDate, setExpiryDate] = useState<Date | null | undefined>(user.subscriptionExpirationDate); const [expiryDate, setExpiryDate] = useState<Date | null | undefined>(user.subscriptionExpirationDate);
const [type, setType] = useState(user.type); const [type, setType] = useState(user.type);
const [status, setStatus] = useState(user.status); const [status, setStatus] = useState(user.status);
@@ -154,6 +175,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
placeholder="Enter corporate name" placeholder="Enter corporate name"
defaultValue={companyName} defaultValue={companyName}
required required
disabled={disabled}
/> />
<Input <Input
label="Commercial Registration" label="Commercial Registration"
@@ -163,6 +185,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
placeholder="Enter commercial registration" placeholder="Enter commercial registration"
defaultValue={commercialRegistration} defaultValue={commercialRegistration}
required required
disabled={disabled}
/> />
</div> </div>
<Divider className="w-full !m-0" /> <Divider className="w-full !m-0" />
@@ -178,6 +201,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
onChange={setCompanyName} onChange={setCompanyName}
placeholder="Enter corporate name" placeholder="Enter corporate name"
defaultValue={companyName} defaultValue={companyName}
disabled={disabled}
/> />
<Input <Input
label="Number of Users" label="Number of Users"
@@ -186,6 +210,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
onChange={(e) => setUserAmount(e ? parseInt(e) : undefined)} onChange={(e) => setUserAmount(e ? parseInt(e) : undefined)}
placeholder="Enter number of users" placeholder="Enter number of users"
defaultValue={userAmount} defaultValue={userAmount}
disabled={disabled}
/> />
<Input <Input
label="Monthly Duration" label="Monthly Duration"
@@ -194,6 +219,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
onChange={(e) => setMonthlyDuration(e ? parseInt(e) : undefined)} onChange={(e) => setMonthlyDuration(e ? parseInt(e) : undefined)}
placeholder="Enter monthly duration" placeholder="Enter monthly duration"
defaultValue={monthlyDuration} defaultValue={monthlyDuration}
disabled={disabled}
/> />
<div className="flex flex-col gap-3 w-full lg:col-span-2"> <div className="flex flex-col gap-3 w-full lg:col-span-2">
<label className="font-normal text-base text-mti-gray-dim">Pricing</label> <label className="font-normal text-base text-mti-gray-dim">Pricing</label>
@@ -204,17 +230,31 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
type="number" type="number"
defaultValue={paymentValue || 0} defaultValue={paymentValue || 0}
className="col-span-3" className="col-span-3"
disabled={disabled}
/>
<Select
className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
options={CURRENCIES_OPTIONS}
value={CURRENCIES_OPTIONS.find((c) => c.value === paymentCurrency)}
onChange={(value) => setPaymentCurrency(value?.value)}
styles={{
control: (styles) => ({
...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
":focus": {
outline: "none",
},
}),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
isDisabled={disabled}
/> />
<select
defaultValue={paymentCurrency}
onChange={(e) => setPaymentCurrency(e.target.value)}
className="p-6 col-span-2 w-full min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer bg-white">
{CURRENCIES.map(({label, currency}) => (
<option value={currency} key={currency}>
{label}
</option>
))}
</select>
</div> </div>
</div> </div>
</div> </div>
@@ -249,6 +289,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
isDisabled={disabled}
/> />
)} )}
</div> </div>
@@ -262,6 +303,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
type="number" type="number"
defaultValue={commissionValue || 0} defaultValue={commissionValue || 0}
className="col-span-3" className="col-span-3"
disabled={disabled}
/> />
</> </>
) : ( ) : (
@@ -316,7 +358,8 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
<label className="font-normal text-base text-mti-gray-dim">Employment Status</label> <label className="font-normal text-base text-mti-gray-dim">Employment Status</label>
<RadioGroup <RadioGroup
value={user.demographicInformation?.employment} value={user.demographicInformation?.employment}
className="grid grid-cols-2 items-center gap-4 place-items-center"> className="grid grid-cols-2 items-center gap-4 place-items-center"
disabled={disabled}>
{EMPLOYMENT_STATUS.map(({status, label}) => ( {EMPLOYMENT_STATUS.map(({status, label}) => (
<RadioGroup.Option value={status} key={status}> <RadioGroup.Option value={status} key={status}>
{({checked}) => ( {({checked}) => (
@@ -351,7 +394,11 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
<div className="flex flex-col gap-8 w-full"> <div className="flex flex-col gap-8 w-full">
<div className="relative flex flex-col gap-3 w-full"> <div className="relative flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Gender</label> <label className="font-normal text-base text-mti-gray-dim">Gender</label>
<RadioGroup value={user.demographicInformation?.gender} className="flex flex-row gap-4 justify-between"> <RadioGroup
value={user.demographicInformation?.gender}
className="flex flex-row gap-4 justify-between"
disabled={disabled}
>
<RadioGroup.Option value="male"> <RadioGroup.Option value="male">
{({checked}) => ( {({checked}) => (
<span <span
@@ -401,7 +448,9 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
<label className="font-normal text-base text-mti-gray-dim">Expiry Date</label> <label className="font-normal text-base text-mti-gray-dim">Expiry Date</label>
<Checkbox <Checkbox
isChecked={!!expiryDate} isChecked={!!expiryDate}
onChange={(checked) => setExpiryDate(checked ? user.subscriptionExpirationDate || new Date() : null)}> onChange={(checked) => setExpiryDate(checked ? user.subscriptionExpirationDate || new Date() : null)}
disabled={disabled}
>
Enabled Enabled
</Checkbox> </Checkbox>
</div> </div>
@@ -434,6 +483,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
dateFormat="dd/MM/yyyy" dateFormat="dd/MM/yyyy"
selected={moment(expiryDate).toDate()} selected={moment(expiryDate).toDate()}
onChange={(date) => setExpiryDate(date)} onChange={(date) => setExpiryDate(date)}
disabled={disabled}
/> />
)} )}
</div> </div>
@@ -445,27 +495,55 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
<div className="flex flex-col md:flex-row gap-8 w-full"> <div className="flex flex-col md:flex-row gap-8 w-full">
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Status</label> <label className="font-normal text-base text-mti-gray-dim">Status</label>
<select <Select
defaultValue={user.status} className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
onChange={(e) => setStatus(e.target.value as typeof user.status)} options={USER_STATUS_OPTIONS}
className="p-6 w-full min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer bg-white"> value={USER_STATUS_OPTIONS.find((o) => o.value === status)}
<option value="active">Active</option> onChange={(value) => setStatus(value?.value as typeof user.status)}
<option value="disabled">Disabled</option> styles={{
<option value="paymentDue">Payment Due</option> control: (styles) => ({
</select> ...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
":focus": {
outline: "none",
},
}),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
isDisabled={disabled}
/>
</div> </div>
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Type</label> <label className="font-normal text-base text-mti-gray-dim">Type</label>
<select <Select
defaultValue={user.type} className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
onChange={(e) => setType(e.target.value as typeof user.type)} options={USER_TYPE_OPTIONS}
className="p-6 w-full min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer bg-white"> value={USER_TYPE_OPTIONS.find((o) => o.value === type)}
{Object.keys(USER_TYPE_LABELS).map((type) => ( onChange={(value) => setType(value?.value as typeof user.type)}
<option key={type} value={type}> styles={{
{USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]} control: (styles) => ({
</option> ...styles,
))} paddingLeft: "4px",
</select> border: "none",
outline: "none",
":focus": {
outline: "none",
},
}),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
isDisabled={disabled}
/>
</div> </div>
</div> </div>
</> </>
@@ -494,7 +572,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
<Button className="w-full max-w-[200px]" variant="outline" onClick={onClose}> <Button className="w-full max-w-[200px]" variant="outline" onClick={onClose}>
Close Close
</Button> </Button>
<Button onClick={updateUser} className="w-full max-w-[200px]"> <Button disabled={disabled} onClick={updateUser} className="w-full max-w-[200px]">
Update Update
</Button> </Button>
</div> </div>

View File

@@ -249,7 +249,8 @@ const IS_PAID_OPTIONS = [
}, },
]; ];
export default function PaymentRecord() { export default function PaymentRecord() {
const [selectedUser, setSelectedUser] = useState<User>(); const [selectedCorporateUser, setSelectedCorporateUser] = useState<User>();
const [selectedAgentUser, setSelectedAgentUser] = useState<User>();
const [isCreatingPayment, setIsCreatingPayment] = useState(false); const [isCreatingPayment, setIsCreatingPayment] = useState(false);
const [filters, setFilters] = useState<{filter: (p: Payment) => boolean; id: string}[]>([]); const [filters, setFilters] = useState<{filter: (p: Payment) => boolean; id: string}[]>([]);
const [displayPayments, setDisplayPayments] = useState<Payment[]>([]); const [displayPayments, setDisplayPayments] = useState<Payment[]>([]);
@@ -465,7 +466,7 @@ export default function PaymentRecord() {
className={clsx( className={clsx(
"underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer", "underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer",
)} )}
onClick={() => setSelectedUser(user)}> onClick={() => setSelectedCorporateUser(user)}>
{user?.corporateInformation.companyInformation.name || user?.name} {user?.corporateInformation.companyInformation.name || user?.name}
</div> </div>
); );
@@ -491,7 +492,7 @@ export default function PaymentRecord() {
cell: (info) => ( cell: (info) => (
<div <div
className={clsx("underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer")} className={clsx("underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer")}
onClick={() => setSelectedUser(users.find((x) => x.id === info.row.original.agent))}> onClick={() => setSelectedAgentUser(users.find((x) => x.id === info.row.original.agent))}>
{(users.find((x) => x.id === info.row.original.agent) as AgentUser)?.name} {(users.find((x) => x.id === info.row.original.agent) as AgentUser)?.name}
</div> </div>
), ),
@@ -545,6 +546,55 @@ export default function PaymentRecord() {
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
const getUserModal = () => {
if(user) {
if(selectedCorporateUser) {
return (
<Modal isOpen={!!selectedCorporateUser} onClose={() => setSelectedCorporateUser(undefined)}>
<>
{selectedCorporateUser && (
<div className="w-full flex flex-col gap-8">
<UserCard
loggedInUser={user}
onClose={(shouldReload) => {
setSelectedCorporateUser(undefined);
if (shouldReload) reload();
}}
user={selectedCorporateUser}
disabled
/>
</div>
)}
</>
</Modal>
);
}
if(selectedAgentUser) {
return (
<Modal isOpen={!!selectedAgentUser} onClose={() => setSelectedAgentUser(undefined)}>
<>
{selectedAgentUser && (
<div className="w-full flex flex-col gap-8">
<UserCard
loggedInUser={user}
onClose={(shouldReload) => {
setSelectedAgentUser(undefined);
if (shouldReload) reload();
}}
user={selectedAgentUser}
/>
</div>
)}
</>
</Modal>
);
}
}
return null;
}
return ( return (
<> <>
<Head> <Head>
@@ -559,23 +609,7 @@ export default function PaymentRecord() {
<ToastContainer /> <ToastContainer />
{user && ( {user && (
<Layout user={user} className="gap-6"> <Layout user={user} className="gap-6">
<Modal isOpen={!!selectedUser} onClose={() => setSelectedUser(undefined)}> {getUserModal()}
<>
{selectedUser && (
<div className="w-full flex flex-col gap-8">
<UserCard
loggedInUser={user}
onClose={(shouldReload) => {
setSelectedUser(undefined);
if (shouldReload) reload();
}}
user={selectedUser}
/>
</div>
)}
</>
</Modal>
<Modal isOpen={isCreatingPayment} onClose={() => setIsCreatingPayment(false)}> <Modal isOpen={isCreatingPayment} onClose={() => setIsCreatingPayment(false)}>
<PaymentCreator <PaymentCreator
onClose={() => setIsCreatingPayment(false)} onClose={() => setIsCreatingPayment(false)}

View File

@@ -361,26 +361,37 @@ export default function Home() {
</div> </div>
</form> </form>
</div> </div>
<div <div className="flex flex-col gap-6 w-48">
className="flex flex-col gap-3 items-center w-48 h-fit cursor-pointer group" <div
onClick={() => (profilePictureInput.current as any)?.click()}> className="flex flex-col gap-3 items-center h-fit cursor-pointer group"
<div className="relative overflow-hidden h-48 w-48 rounded-full"> onClick={() => (profilePictureInput.current as any)?.click()}>
<div <div className="relative overflow-hidden h-48 w-48 rounded-full">
className={clsx( <div
"absolute top-0 left-0 bg-mti-purple-light/60 w-full h-full z-20 flex items-center justify-center opacity-0 group-hover:opacity-100", className={clsx(
"transition ease-in-out duration-300", "absolute top-0 left-0 bg-mti-purple-light/60 w-full h-full z-20 flex items-center justify-center opacity-0 group-hover:opacity-100",
)}> "transition ease-in-out duration-300",
<BsCamera className="text-6xl text-mti-purple-ultralight/80" /> )}>
<BsCamera className="text-6xl text-mti-purple-ultralight/80" />
</div>
<img src={profilePicture} alt={user.name} className="aspect-square drop-shadow-xl self-end object-cover" />
</div> </div>
<img src={profilePicture} alt={user.name} className="aspect-square drop-shadow-xl self-end object-cover" /> <input type="file" className="hidden" onChange={uploadProfilePicture} accept="image/*" ref={profilePictureInput} />
<span
onClick={() => (profilePictureInput.current as any)?.click()}
className="cursor-pointer text-mti-purple-light text-sm">
Change picture
</span>
<h6 className="font-normal text-base text-mti-gray-taupe">{USER_TYPE_LABELS[user.type]}</h6>
</div> </div>
<input type="file" className="hidden" onChange={uploadProfilePicture} accept="image/*" ref={profilePictureInput} /> {user.type === 'agent' && (
<span <div className="flag items-center h-fit">
onClick={() => (profilePictureInput.current as any)?.click()} <img
className="cursor-pointer text-mti-purple-light text-sm"> alt={user.demographicInformation?.country.toLowerCase() + '_flag'}
Change picture src={`https://flagcdn.com/w320/${user.demographicInformation?.country.toLowerCase()}.png`}
</span> width="320"
<h6 className="font-normal text-base text-mti-gray-taupe">{USER_TYPE_LABELS[user.type]}</h6> />
</div>
)}
</div> </div>
</div> </div>
<div className="flex flex-col gap-4 mt-8 mb-20"> <div className="flex flex-col gap-4 mt-8 mb-20">