Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/filter in email by domain id and api #108

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Email" ADD COLUMN "apiId" INTEGER;
1 change: 1 addition & 0 deletions apps/web/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ model Email {
latestStatus EmailStatus @default(QUEUED)
teamId Int
domainId Int?
apiId Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
scheduledAt DateTime?
Expand Down
140 changes: 106 additions & 34 deletions apps/web/src/app/(dashboard)/emails/email-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,36 +40,64 @@ import {
import { Input } from "@unsend/ui/src/input";
import { DEFAULT_QUERY_LIMIT } from "~/lib/constants";
import { useDebouncedCallback } from "use-debounce";
import { useState } from "react";

/* Stupid hydrating error. And I so stupid to understand the stupid NextJS docs */
const DynamicSheetWithNoSSR = dynamic(
() => import("@unsend/ui/src/sheet").then((mod) => mod.Sheet),
{ ssr: false }
{ ssr: false },
);

const DynamicSheetContentWithNoSSR = dynamic(
() => import("@unsend/ui/src/sheet").then((mod) => mod.SheetContent),
{ ssr: false }
{ ssr: false },
);

export default function EmailsList() {
const [selectedEmail, setSelectedEmail] = useUrlState("emailId");
const [page, setPage] = useUrlState("page", "1");
const [status, setStatus] = useUrlState("status");
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 domainId = Number(domain);
const apiId = Number(apikey);

const emailsQuery = api.email.emails.useQuery({
page: pageNumber,
status: status?.toUpperCase() as EmailStatus,
domain: domainId,
search,
apiId: apiId,
});

const { data: domainsQuery } = api.domain.domains.useQuery();
const { data: apiKeysQuery } = api.apiKey.getApiKeys.useQuery();

const handleSelectEmail = (emailId: string) => {
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) => {
if (!isOpen) {
setSelectedEmail(null);
Expand All @@ -89,39 +117,83 @@ export default function EmailsList() {
defaultValue={search ?? ""}
onChange={(e) => debouncedSearch(e.target.value)}
/>
<Select
value={status ?? "All statuses"}
onValueChange={(val) =>
setStatus(val === "All statuses" ? null : val)
}
>
<SelectTrigger className="w-[180px] capitalize">
{status ? status.toLowerCase().replace("_", " ") : "All statuses"}
</SelectTrigger>
<SelectContent>
<SelectItem value="All statuses" className=" capitalize">
All statuses
</SelectItem>
{Object.values([
"SENT",
"SCHEDULED",
"QUEUED",
"DELIVERED",
"BOUNCED",
"CLICKED",
"OPENED",
"DELIVERY_DELAYED",
"COMPLAINED",
]).map((status) => (
<SelectItem value={status} className=" capitalize">
{status.toLowerCase().replace("_", " ")}
<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
value={status ?? "All statuses"}
onValueChange={(val) =>
setStatus(val === "All statuses" ? null : val)
}
>
<SelectTrigger className="w-[180px] capitalize">
{status ? status.toLowerCase().replace("_", " ") : "All statuses"}
</SelectTrigger>
<SelectContent>
<SelectItem value="All statuses" className=" capitalize">
All statuses
</SelectItem>
))}
{/* <SelectItem value="light">Light</SelectItem>
{Object.values([
"SENT",
"SCHEDULED",
"QUEUED",
"DELIVERED",
"BOUNCED",
"CLICKED",
"OPENED",
"DELIVERY_DELAYED",
"COMPLAINED",
]).map((status) => (
<SelectItem value={status} className=" capitalize">
{status.toLowerCase().replace("_", " ")}
</SelectItem>
))}
{/* <SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem> */}
</SelectContent>
</Select>
</SelectContent>
</Select>
</div>
</div>
<div className="flex flex-col rounded-xl border shadow">
<Table className="">
Expand Down Expand Up @@ -171,7 +243,7 @@ export default function EmailsList() {
Scheduled at{" "}
{formatDate(
email.scheduledAt,
"MMM dd'th', hh:mm a"
"MMM dd'th', hh:mm a",
)}
</TooltipContent>
</Tooltip>
Expand All @@ -187,7 +259,7 @@ export default function EmailsList() {
{email.latestStatus !== "SCHEDULED"
? formatDate(
email.scheduledAt ?? email.createdAt,
"MMM do, hh:mm a"
"MMM do, hh:mm a",
)
: "--"}
</TableCell>
Expand Down
8 changes: 5 additions & 3 deletions apps/web/src/server/api/routers/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export const emailRouter = createTRPCRouter({
status: z.enum(statuses).optional().nullable(),
domain: z.number().optional(),
search: z.string().optional().nullable(),
})
apiId: z.number().optional(),
}),
)
.query(async ({ ctx, input }) => {
const page = input.page || 1;
Expand All @@ -40,6 +41,7 @@ export const emailRouter = createTRPCRouter({
WHERE "teamId" = ${ctx.team.id}
${input.status ? Prisma.sql`AND "latestStatus"::text = ${input.status}` : Prisma.sql``}
${input.domain ? Prisma.sql`AND "domainId" = ${input.domain}` : Prisma.sql``}
${input.apiId ? Prisma.sql`AND "apiId" = ${input.apiId}` : Prisma.sql``}
${
input.search
? Prisma.sql`AND (
Expand All @@ -63,7 +65,7 @@ export const emailRouter = createTRPCRouter({
.input(
z.object({
days: z.number().optional(),
})
}),
)
.query(async ({ ctx, input }) => {
const { team } = ctx;
Expand Down Expand Up @@ -146,7 +148,7 @@ export const emailRouter = createTRPCRouter({
clicked: 0,
bounced: 0,
complained: 0,
}
},
);

return { result: filledResult, totalCounts };
Expand Down
Loading