Feature/filter in email by domain id and api (#108)
* feat: filter by domainId * feat: filter on apikey in email * Update pnpm-lock.yaml --------- Co-authored-by: KM Koushik <koushikmohan1996@gmail.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Email" ADD COLUMN "apiId" INTEGER;
|
@@ -197,6 +197,7 @@ model Email {
|
|||||||
latestStatus EmailStatus @default(QUEUED)
|
latestStatus EmailStatus @default(QUEUED)
|
||||||
teamId Int
|
teamId Int
|
||||||
domainId Int?
|
domainId Int?
|
||||||
|
apiId Int?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
scheduledAt DateTime?
|
scheduledAt DateTime?
|
||||||
|
@@ -40,16 +40,17 @@ import {
|
|||||||
import { Input } from "@unsend/ui/src/input";
|
import { Input } from "@unsend/ui/src/input";
|
||||||
import { DEFAULT_QUERY_LIMIT } from "~/lib/constants";
|
import { DEFAULT_QUERY_LIMIT } from "~/lib/constants";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
/* Stupid hydrating error. And I so stupid to understand the stupid NextJS docs */
|
/* Stupid hydrating error. And I so stupid to understand the stupid NextJS docs */
|
||||||
const DynamicSheetWithNoSSR = dynamic(
|
const DynamicSheetWithNoSSR = dynamic(
|
||||||
() => import("@unsend/ui/src/sheet").then((mod) => mod.Sheet),
|
() => import("@unsend/ui/src/sheet").then((mod) => mod.Sheet),
|
||||||
{ ssr: false }
|
{ ssr: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
const DynamicSheetContentWithNoSSR = dynamic(
|
const DynamicSheetContentWithNoSSR = dynamic(
|
||||||
() => import("@unsend/ui/src/sheet").then((mod) => mod.SheetContent),
|
() => import("@unsend/ui/src/sheet").then((mod) => mod.SheetContent),
|
||||||
{ ssr: false }
|
{ ssr: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function EmailsList() {
|
export default function EmailsList() {
|
||||||
@@ -57,19 +58,46 @@ export default function EmailsList() {
|
|||||||
const [page, setPage] = useUrlState("page", "1");
|
const [page, setPage] = useUrlState("page", "1");
|
||||||
const [status, setStatus] = useUrlState("status");
|
const [status, setStatus] = useUrlState("status");
|
||||||
const [search, setSearch] = useUrlState("search");
|
const [search, setSearch] = useUrlState("search");
|
||||||
|
const [domainName, setDomainName] = useUrlState("domain");
|
||||||
|
const [apiKeyName, setApiKeyName] = useUrlState("apikey");
|
||||||
|
const [domain, setDomain] = useState<string | null>(null);
|
||||||
|
const [apikey, setApikey] = useState<string | null>(null);
|
||||||
|
|
||||||
const pageNumber = Number(page);
|
const pageNumber = Number(page);
|
||||||
|
const domainId = Number(domain);
|
||||||
|
const apiId = Number(apikey);
|
||||||
|
|
||||||
const emailsQuery = api.email.emails.useQuery({
|
const emailsQuery = api.email.emails.useQuery({
|
||||||
page: pageNumber,
|
page: pageNumber,
|
||||||
status: status?.toUpperCase() as EmailStatus,
|
status: status?.toUpperCase() as EmailStatus,
|
||||||
|
domain: domainId,
|
||||||
search,
|
search,
|
||||||
|
apiId: apiId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: domainsQuery } = api.domain.domains.useQuery();
|
||||||
|
const { data: apiKeysQuery } = api.apiKey.getApiKeys.useQuery();
|
||||||
|
|
||||||
const handleSelectEmail = (emailId: string) => {
|
const handleSelectEmail = (emailId: string) => {
|
||||||
setSelectedEmail(emailId);
|
setSelectedEmail(emailId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDomainName = (val: string) => {
|
||||||
|
setDomain(val === "All Domain" ? null : val);
|
||||||
|
const nameOfDomain = domainsQuery?.find(
|
||||||
|
(item) => item.id.toString() === val,
|
||||||
|
);
|
||||||
|
setDomainName(nameOfDomain?.name || "All Domain");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleApiKeyName = (val: string) => {
|
||||||
|
setApikey(val === "All ApiKey" ? null : val);
|
||||||
|
const nameOfApiKey = apiKeysQuery?.find(
|
||||||
|
(item) => item.id.toString() === val,
|
||||||
|
);
|
||||||
|
setApiKeyName(nameOfApiKey?.name || "All ApiKey");
|
||||||
|
};
|
||||||
|
|
||||||
const handleSheetChange = (isOpen: boolean) => {
|
const handleSheetChange = (isOpen: boolean) => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
setSelectedEmail(null);
|
setSelectedEmail(null);
|
||||||
@@ -89,6 +117,49 @@ export default function EmailsList() {
|
|||||||
defaultValue={search ?? ""}
|
defaultValue={search ?? ""}
|
||||||
onChange={(e) => debouncedSearch(e.target.value)}
|
onChange={(e) => debouncedSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<div className="flex justify-center items-center gap-x-3">
|
||||||
|
<Select
|
||||||
|
value={apikey ?? "All ApiKey"}
|
||||||
|
onValueChange={(val) => handleApiKeyName(val)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[180px]">
|
||||||
|
{apiKeyName ? apiKeyName : "All ApiKey"}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="All ApiKey">All ApiKey</SelectItem>
|
||||||
|
{apiKeysQuery &&
|
||||||
|
apiKeysQuery.map((apikey) => (
|
||||||
|
<SelectItem value={apikey.id.toString()}>
|
||||||
|
{apikey.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
{/* <SelectItem value="light">Light</SelectItem>
|
||||||
|
<SelectItem value="dark">Dark</SelectItem>
|
||||||
|
<SelectItem value="system">System</SelectItem> */}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
value={domain ?? "All Domain"}
|
||||||
|
onValueChange={(val) => handleDomainName(val)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[180px]">
|
||||||
|
{domainName ? domainName : "All Domain"}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="All Domain" className=" capitalize">
|
||||||
|
All Domain
|
||||||
|
</SelectItem>
|
||||||
|
{domainsQuery &&
|
||||||
|
domainsQuery.map((domain) => (
|
||||||
|
<SelectItem value={domain.id.toString()}>
|
||||||
|
{domain.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
{/* <SelectItem value="light">Light</SelectItem>
|
||||||
|
<SelectItem value="dark">Dark</SelectItem>
|
||||||
|
<SelectItem value="system">System</SelectItem> */}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
<Select
|
<Select
|
||||||
value={status ?? "All statuses"}
|
value={status ?? "All statuses"}
|
||||||
onValueChange={(val) =>
|
onValueChange={(val) =>
|
||||||
@@ -123,6 +194,7 @@ export default function EmailsList() {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex flex-col rounded-xl border shadow">
|
<div className="flex flex-col rounded-xl border shadow">
|
||||||
<Table className="">
|
<Table className="">
|
||||||
<TableHeader className="">
|
<TableHeader className="">
|
||||||
@@ -171,7 +243,7 @@ export default function EmailsList() {
|
|||||||
Scheduled at{" "}
|
Scheduled at{" "}
|
||||||
{formatDate(
|
{formatDate(
|
||||||
email.scheduledAt,
|
email.scheduledAt,
|
||||||
"MMM dd'th', hh:mm a"
|
"MMM dd'th', hh:mm a",
|
||||||
)}
|
)}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -187,7 +259,7 @@ export default function EmailsList() {
|
|||||||
{email.latestStatus !== "SCHEDULED"
|
{email.latestStatus !== "SCHEDULED"
|
||||||
? formatDate(
|
? formatDate(
|
||||||
email.scheduledAt ?? email.createdAt,
|
email.scheduledAt ?? email.createdAt,
|
||||||
"MMM do, hh:mm a"
|
"MMM do, hh:mm a",
|
||||||
)
|
)
|
||||||
: "--"}
|
: "--"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@@ -21,7 +21,8 @@ export const emailRouter = createTRPCRouter({
|
|||||||
status: z.enum(statuses).optional().nullable(),
|
status: z.enum(statuses).optional().nullable(),
|
||||||
domain: z.number().optional(),
|
domain: z.number().optional(),
|
||||||
search: z.string().optional().nullable(),
|
search: z.string().optional().nullable(),
|
||||||
})
|
apiId: z.number().optional(),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const page = input.page || 1;
|
const page = input.page || 1;
|
||||||
@@ -40,6 +41,7 @@ export const emailRouter = createTRPCRouter({
|
|||||||
WHERE "teamId" = ${ctx.team.id}
|
WHERE "teamId" = ${ctx.team.id}
|
||||||
${input.status ? Prisma.sql`AND "latestStatus"::text = ${input.status}` : Prisma.sql``}
|
${input.status ? Prisma.sql`AND "latestStatus"::text = ${input.status}` : Prisma.sql``}
|
||||||
${input.domain ? Prisma.sql`AND "domainId" = ${input.domain}` : Prisma.sql``}
|
${input.domain ? Prisma.sql`AND "domainId" = ${input.domain}` : Prisma.sql``}
|
||||||
|
${input.apiId ? Prisma.sql`AND "apiId" = ${input.apiId}` : Prisma.sql``}
|
||||||
${
|
${
|
||||||
input.search
|
input.search
|
||||||
? Prisma.sql`AND (
|
? Prisma.sql`AND (
|
||||||
@@ -63,7 +65,7 @@ export const emailRouter = createTRPCRouter({
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
days: z.number().optional(),
|
days: z.number().optional(),
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { team } = ctx;
|
const { team } = ctx;
|
||||||
@@ -146,7 +148,7 @@ export const emailRouter = createTRPCRouter({
|
|||||||
clicked: 0,
|
clicked: 0,
|
||||||
bounced: 0,
|
bounced: 0,
|
||||||
complained: 0,
|
complained: 0,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return { result: filledResult, totalCounts };
|
return { result: filledResult, totalCounts };
|
||||||
|
5
pnpm-lock.yaml
generated
5
pnpm-lock.yaml
generated
@@ -736,6 +736,7 @@ packages:
|
|||||||
preact-render-to-string: 5.2.3(preact@10.11.3)
|
preact-render-to-string: 5.2.3(preact@10.11.3)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
|
||||||
/@auth/prisma-adapter@1.5.0(@prisma/client@6.3.1):
|
/@auth/prisma-adapter@1.5.0(@prisma/client@6.3.1):
|
||||||
resolution: {integrity: sha512-TNDY7z5VhC3E5xetb4qhTmSmjvyEurafaTM6dlYlhTGj+7K3xTki7fJ0oJwgkFsQ2fT1weJJJSBmjtH8Nqsnuw==}
|
resolution: {integrity: sha512-TNDY7z5VhC3E5xetb4qhTmSmjvyEurafaTM6dlYlhTGj+7K3xTki7fJ0oJwgkFsQ2fT1weJJJSBmjtH8Nqsnuw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -8241,6 +8242,7 @@ packages:
|
|||||||
negotiator: 0.6.3
|
negotiator: 0.6.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
|
||||||
/acorn-jsx@5.3.2(acorn@8.11.3):
|
/acorn-jsx@5.3.2(acorn@8.11.3):
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -8267,7 +8269,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
|
resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
|
||||||
|
|
||||||
/add@2.0.6:
|
/add@2.0.6:
|
||||||
resolution: {integrity: sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q==}
|
resolution: {integrity: sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q==}
|
||||||
@@ -13724,6 +13725,7 @@ packages:
|
|||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
|
||||||
/next@14.2.1(react-dom@18.2.0)(react@18.2.0):
|
/next@14.2.1(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-SF3TJnKdH43PMkCcErLPv+x/DY1YCklslk3ZmwaVoyUfDgHKexuKlf9sEfBQ69w+ue8jQ3msLb+hSj1T19hGag==}
|
resolution: {integrity: sha512-SF3TJnKdH43PMkCcErLPv+x/DY1YCklslk3ZmwaVoyUfDgHKexuKlf9sEfBQ69w+ue8jQ3msLb+hSj1T19hGag==}
|
||||||
engines: {node: '>=18.17.0'}
|
engines: {node: '>=18.17.0'}
|
||||||
@@ -15720,6 +15722,7 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
|
||||||
/resolve-alpn@1.2.1:
|
/resolve-alpn@1.2.1:
|
||||||
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
Reference in New Issue
Block a user