forked from aws-samples/amplify-next-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: payer ID page added, enabled linking of resellers and financial…
… data for resellers
- Loading branch information
Carsten Koch
committed
Oct 31, 2024
1 parent
7b0de11
commit 3424ee1
Showing
21 changed files
with
897 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { type Schema } from "@/amplify/data/resource"; | ||
import { | ||
createPayerAndAccountLink, | ||
deletePayerAccountLink, | ||
} from "@/helpers/payers/api-actions"; | ||
import { generateClient, SelectionSet } from "aws-amplify/data"; | ||
import { map } from "lodash/fp"; | ||
import useSWR from "swr"; | ||
import { handleApiErrors } from "./globals"; | ||
const client = generateClient<Schema>(); | ||
|
||
type PayerData = SelectionSet< | ||
Schema["PayerAccount"]["type"], | ||
typeof selectionSet | ||
>; | ||
|
||
const selectionSet = [ | ||
"awsAccountNumber", | ||
"accounts.accountId", | ||
"resellerId", | ||
"mainContactId", | ||
] as const; | ||
|
||
export type Payer = { | ||
accountNumber: string; | ||
isReseller: boolean; | ||
resellerId?: string; | ||
accountIds: string[]; | ||
mainContactId?: string; | ||
}; | ||
|
||
const mapPayer = ({ | ||
awsAccountNumber, | ||
accounts, | ||
resellerId, | ||
mainContactId, | ||
}: PayerData): Payer => ({ | ||
accountNumber: awsAccountNumber, | ||
isReseller: !!resellerId, | ||
resellerId: resellerId ?? undefined, | ||
accountIds: map(({ accountId }) => accountId)(accounts) ?? [], | ||
mainContactId: mainContactId ?? undefined, | ||
}); | ||
|
||
const fetchPayer = (payerId?: string) => async () => { | ||
if (!payerId) return; | ||
const { data, errors } = await client.models.PayerAccount.get( | ||
{ | ||
awsAccountNumber: payerId, | ||
}, | ||
{ selectionSet } | ||
); | ||
if (errors) { | ||
handleApiErrors(errors, "Loading Payer failed"); | ||
throw errors; | ||
} | ||
if (!data) return; | ||
|
||
try { | ||
return mapPayer(data); | ||
} catch (error) { | ||
console.error("fetchPayer", error); | ||
throw error; | ||
} | ||
}; | ||
|
||
const usePayer = (payerId?: string) => { | ||
const { | ||
data: payer, | ||
isLoading, | ||
error, | ||
mutate, | ||
} = useSWR(`/api/payers/${payerId}`, fetchPayer(payerId)); | ||
|
||
const createPayerAccountLink = async (accountId: string | null) => { | ||
if (!accountId) return; | ||
if (!payer) return; | ||
const updatedPayer = { | ||
...payer, | ||
accountIds: [...payer.accountIds, accountId], | ||
} as Payer; | ||
mutate(updatedPayer, false); | ||
await createPayerAndAccountLink(accountId, payer.accountNumber); | ||
mutate(updatedPayer); | ||
}; | ||
|
||
const deletePayerAccount = async (accountId: string) => { | ||
if (!payer) return; | ||
const updated = { | ||
...payer, | ||
accountIds: payer.accountIds.filter((p) => p !== accountId), | ||
} as Payer; | ||
mutate(updated, false); | ||
await deletePayerAccountLink(accountId, payer.accountNumber); | ||
mutate(updated); | ||
}; | ||
|
||
const attachReseller = async (resellerId: string | null) => { | ||
if (!resellerId) return; | ||
if (!payer) return; | ||
const updatedPayer = { ...payer, resellerId } as Payer; | ||
mutate(updatedPayer, false); | ||
const { data, errors } = await client.models.PayerAccount.update({ | ||
awsAccountNumber: payer.accountNumber, | ||
resellerId, | ||
}); | ||
if (errors) handleApiErrors(errors, "Attaching reseller failed"); | ||
mutate(updatedPayer); | ||
return data; | ||
}; | ||
|
||
return { | ||
payer, | ||
isLoading, | ||
error, | ||
createPayerAccountLink, | ||
deletePayerAccount, | ||
attachReseller, | ||
}; | ||
}; | ||
|
||
export default usePayer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { Account } from "@/api/ContextAccounts"; | ||
import { | ||
RevenueMonth, | ||
setTotalRevenueFromRevenueMonth, | ||
} from "@/helpers/analytics/account-data"; | ||
import { setColumnDefFromMrr } from "@/helpers/analytics/prep-table-data"; | ||
import { | ||
setLastMonthsResellerRevenue, | ||
setResellerColumnDataFromMrr, | ||
} from "@/helpers/analytics/reseller-data"; | ||
import { formatDate, formatRevenue } from "@/helpers/functional"; | ||
import { ColumnDef } from "@tanstack/react-table"; | ||
import { flow } from "lodash/fp"; | ||
import { FC, useEffect, useState } from "react"; | ||
import AnalyticsTable from "../analytics/analytics-table"; | ||
import { AccountMrr } from "../analytics/analytics-table-column"; | ||
import MrrFilterBtnGrp from "../analytics/mrr-filter-btn-grp"; | ||
import { useMrrFilter, withMrrFilter } from "../analytics/useMrrFilter"; | ||
import DefaultAccordionItem from "../ui-elements/accordion/DefaultAccordionItem"; | ||
|
||
type ResellerFinancialsProps = { | ||
account: Account; | ||
showResellerFinancials?: boolean; | ||
}; | ||
|
||
const ResellerFinancials: FC<ResellerFinancialsProps> = ({ | ||
account, | ||
showResellerFinancials, | ||
}) => { | ||
const { mrrFilter, mrr } = useMrrFilter(); | ||
const [noOfMonths, setNoOfMonths] = useState(0); | ||
const [columnDef, setColumnDef] = useState<ColumnDef<AccountMrr>[]>([]); | ||
const [columnData, setColumnData] = useState<AccountMrr[]>([]); | ||
const [revenueLastMonths, setRevenueLastMonths] = useState<RevenueMonth[]>( | ||
[] | ||
); | ||
const [totalRevenue, setTotalRevenue] = useState(0); | ||
|
||
useEffect(() => { | ||
flow(parseInt, setNoOfMonths)(mrrFilter); | ||
}, [mrrFilter]); | ||
|
||
useEffect(() => { | ||
setColumnDefFromMrr(mrr, noOfMonths, setColumnDef); | ||
}, [mrr, noOfMonths]); | ||
|
||
useEffect(() => { | ||
setResellerColumnDataFromMrr(account.id, mrr, noOfMonths, setColumnData); | ||
}, [account, mrr, noOfMonths]); | ||
|
||
useEffect(() => { | ||
setLastMonthsResellerRevenue(account.id, 3, mrr, setRevenueLastMonths); | ||
}, [account, mrr]); | ||
|
||
useEffect(() => { | ||
setTotalRevenueFromRevenueMonth(revenueLastMonths, setTotalRevenue); | ||
}, [revenueLastMonths]); | ||
|
||
return ( | ||
<DefaultAccordionItem | ||
value="reseller-financials" | ||
triggerTitle="AWS Revenue (as Reseller)" | ||
triggerSubTitle={revenueLastMonths.map( | ||
({ month, mrr }) => | ||
`${formatDate("MMM yyyy")(month)}: ${formatRevenue(mrr)}` | ||
)} | ||
isVisible={!!showResellerFinancials && totalRevenue > 0} | ||
> | ||
<div className="space-y-6"> | ||
<MrrFilterBtnGrp /> | ||
<AnalyticsTable columns={columnDef} data={columnData} /> | ||
</div> | ||
</DefaultAccordionItem> | ||
); | ||
}; | ||
|
||
export default withMrrFilter(ResellerFinancials); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { Account, useAccountsContext } from "@/api/ContextAccounts"; | ||
import { find, flow, identity } from "lodash/fp"; | ||
import { FC, useEffect, useState } from "react"; | ||
import AccountDetails from "../accounts/AccountDetails"; | ||
import DefaultAccordionItem from "../ui-elements/accordion/DefaultAccordionItem"; | ||
import LoadingAccordionItem from "../ui-elements/accordion/LoadingAccordionItem"; | ||
import { Button } from "../ui/button"; | ||
|
||
type PayerAccountAccordionProps = { | ||
accountId: string; | ||
removeLinkToPayer: (accountId: string) => void; | ||
}; | ||
|
||
const PayerAccountAccordion: FC<PayerAccountAccordionProps> = ({ | ||
accountId, | ||
removeLinkToPayer, | ||
}) => { | ||
const { accounts } = useAccountsContext(); | ||
const [account, setAccount] = useState<Account | undefined>(); | ||
|
||
useEffect(() => { | ||
flow( | ||
identity<Account[] | undefined>, | ||
find(["id", accountId]), | ||
setAccount | ||
)(accounts); | ||
}, [accounts, accountId]); | ||
|
||
return !account ? ( | ||
<LoadingAccordionItem | ||
value={`account-${accountId}`} | ||
sizeTitle="lg" | ||
sizeSubtitle="base" | ||
/> | ||
) : ( | ||
<DefaultAccordionItem | ||
value={accountId} | ||
triggerTitle={account.name} | ||
link={`/accounts/${accountId}`} | ||
> | ||
<div className="mb-6"> | ||
<Button | ||
variant="outline" | ||
size="sm" | ||
onClick={() => removeLinkToPayer(accountId)} | ||
> | ||
Remove link to payer | ||
</Button> | ||
</div> | ||
<AccountDetails account={account} /> | ||
</DefaultAccordionItem> | ||
); | ||
}; | ||
|
||
export default PayerAccountAccordion; |
Oops, something went wrong.