Skip to content

Commit

Permalink
v0.6.0
Browse files Browse the repository at this point in the history
# Release Notes - TPET v0.6.0

## New Features

- User can check out the email run history.
- User can click the run to see the details of emails in a single run.
  • Loading branch information
chungchihhan authored Aug 5, 2024
1 parent 3a00334 commit 19df89b
Show file tree
Hide file tree
Showing 8 changed files with 819 additions and 0 deletions.
159 changes: 159 additions & 0 deletions src/app/emailHistory/[runId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"use client";
import { useEffect, useState } from "react";
import EmailDetailsTable from "@/app/ui/email-details-table";
import EmailDetailsTableSkeleton from "@/app/ui/skeleton/email-details-table-skeleton";
import { ChevronRight, ChevronLeft } from "lucide-react";

interface PageProps {
params: {
runId: string;
};
}

interface rowDataType {
[key: string]: string;
}

interface dataType {
bcc: string[];
subject: string;
cc: string[];
run_id: string;
created_at: string;
recipient_email: string;
sender_local_part: string;
status: string;
spreadsheet_file_id: string;
row_data: rowDataType;
atatachment_file_ids: string[];
is_generated_certficate: boolean;
sender_username: string;
display_name: string;
sender_id: string;
updated_at: string;
sent_at: string;
template_file_id: string;
reply_to: string;
email_id: string;
}

export default function Page({ params }: PageProps) {
const [data, setData] = useState<dataType[]>([]);
const [status, setStatus] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [previousLastEvaluatedKey, setPreviousLastEvaluatedKey] = useState<
string | null
>(null);
const [currentLastEvaluatedKey, setCurrentLastEvaluatedKey] = useState<
string | null
>(null);
const [nextLastEvaluatedKey, setNextLastEvaluatedKey] = useState<
string | null
>(null);

useEffect(() => {
fetchFiles(10, null, null);
// const intervalId = setInterval(() => {
// fetchFiles(10, null, null);
// }, 3000);

// return () => clearInterval(intervalId);
}, []);

const fetchFiles = async (
limit: number,
status: string | null,
lastEvaluatedKey: string | null
) => {
try {
const base_url = process.env.NEXT_PUBLIC_API_ENDPOINT;
const url = new URL(`${base_url}/runs/${params.runId}/emails`);
url.searchParams.append("limit", limit.toString());
if (status) {
url.searchParams.append("status", status);
}
if (lastEvaluatedKey) {
url.searchParams.append("last_evaluated_key", lastEvaluatedKey);
}

const token = localStorage.getItem("access_token");
const response = await fetch(url.toString(), {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
const errorMessage = `Request failed: ${response.status} - ${response.statusText}`;
throw new Error(errorMessage);
}

const result = await response.json();
setIsLoading(false);
setData(result.data);
console.log(result.data);
setPreviousLastEvaluatedKey(result.previous_last_evaluated_key);
setCurrentLastEvaluatedKey(result.current_last_evaluated_key);
setNextLastEvaluatedKey(result.next_last_evaluated_key);
} catch (error: any) {
alert("Failed to fetch files: " + error.message);
} finally {
// setLoading
}
};

return (
<>
<div className="flex flex-col justify-center items-start">
<p className="text-4xl font-bold pt-2">Emails history</p>
<div className="flex justify-between items-center w-full pb-4">
<p className="text-gray-500 italic">
Details of <strong>one of the runs </strong>is shown here.
</p>
<div className="h-10"></div>
</div>
</div>
<div className="">
{isLoading ? (
<EmailDetailsTableSkeleton />
) : (
<EmailDetailsTable data={data} />
)}
<div className="flex justify-end gap-8 pt-3 pb-1 px-2">
<button
className={`flex items-center gap-1 ${
!currentLastEvaluatedKey
? "cursor-default text-gray-400"
: "hover:text-gray-600 hover:underline"
}`}
onClick={() => {
fetchFiles(10, null, previousLastEvaluatedKey);
}}
disabled={!currentLastEvaluatedKey}
>
<ChevronLeft size={20} />
Previous
</button>
<button
className={`flex items-center gap-1 ${
!nextLastEvaluatedKey
? "cursor-default text-gray-400"
: "hover:text-gray-600 hover:underline"
}`}
onClick={() => {
if (nextLastEvaluatedKey) {
fetchFiles(10, null, nextLastEvaluatedKey);
}
}}
disabled={!nextLastEvaluatedKey}
>
Next
<ChevronRight size={20} />
</button>
</div>
</div>
</>
);
}
12 changes: 12 additions & 0 deletions src/app/emailHistory/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import SideNav from "@/app/ui/side-nav";

export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
<div className="w-full flex-none md:w-64">
<SideNav />
</div>
<div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div>
</div>
);
}
206 changes: 206 additions & 0 deletions src/app/emailHistory/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
"use client";
import { useEffect, useState } from "react";
import { convertToTaipeiTime } from "@/lib/utils/dataUtils";
import EmailHistoryCardLoading from "@/app/ui/skeleton/email-history-card-skeleton";
import {
CalendarDays,
User,
FileText,
Clock,
Send,
Sheet,
ChevronRight,
ChevronLeft,
} from "lucide-react";
import EmailHistoryCard from "../ui/email-history-card";

interface attachmentFilesType {
file_url: string;
uploaded_id: string;
updated_at: string;
file_name: string;
file_id: string;
s3_object_key: string;
created_at: string;
file_extension: string;
file_size: number;
}

interface spreadsheetFileType {
file_url: string;
uploaded_id: string;
updated_at: string;
file_name: string;
file_id: string;
s3_object_key: string;
created_at: string;
file_extension: string;
file_size: number;
}

interface templateFileType {
file_url: string;
uploaded_id: string;
updated_at: string;
file_name: string;
file_id: string;
s3_object_key: string;
created_at: string;
file_extension: string;
file_size: number;
}

interface senderType {
user_id: string;
email: string;
username: string;
}

interface dateType {
bcc: string[];
subject: string;
cc: string[];
run_id: string;
attachment_files: attachmentFilesType[];
created_at: string;
sender_local_part: string;
spreadsheet_id: string;
created_year_month: string;
attachment_file_ids: string[];
is_generated_certficate: boolean;
spreadsheet_file: spreadsheetFileType;
display_name: string;
sender_id: string;
sender: senderType;
template_file_id: string;
success_email_count: number;
expected_email_send_count: number;
reply_to: string;
template_file: templateFileType;
created_year_month_day: string;
created_year: string;
}

// interface responseType {
// data: dateType[];
// previous_last_evaluated_key: string;
// current_last_evaluated_key: string;
// next_last_evaluated_key: string;
// }

export default function Page() {
const [data, setData] = useState<dateType[] | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [previousLastEvaluatedKey, setPreviousLastEvaluatedKey] = useState<
string | null
>(null);
const [currentLastEvaluatedKey, setCurrentLastEvaluatedKey] = useState<
string | null
>(null);
const [nextLastEvaluatedKey, setNextLastEvaluatedKey] = useState<
string | null
>(null);

useEffect(() => {
fetchFiles(10, null);
}, []);

const fetchFiles = async (limit: number, lastEvaluatedKey: string | null) => {
try {
const base_url = process.env.NEXT_PUBLIC_API_ENDPOINT;
const url = new URL(`${base_url}/runs`);

url.searchParams.append("limit", limit.toString());
if (lastEvaluatedKey) {
url.searchParams.append("last_evaluated_key", lastEvaluatedKey);
}

const token = localStorage.getItem("access_token");
const response = await fetch(url.toString(), {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
const errorMessage = `Request failed: ${response.status} - ${response.statusText}`;
throw new Error(errorMessage);
}

const result = await response.json();
// console.log(result);
setIsLoading(false);
setData(result.data);
setPreviousLastEvaluatedKey(result.previous_last_evaluated_key);
setCurrentLastEvaluatedKey(result.current_last_evaluated_key);
setNextLastEvaluatedKey(result.next_last_evaluated_key);
} catch (error: any) {
alert("Failed to fetch files: " + error.message);
} finally {
// setLoading
}
};
// const handleALlFiles = async () => {
// fetchFiles("all", 10, null);
// };

return (
<div>
{/* <button className="bg-blue-300" onClick={handleALlFiles}>
button
</button> */}
<div className="flex flex-col justify-center items-start">
<p className="text-4xl font-bold pt-2">Emails history</p>
<div className="flex justify-between items-center w-full pb-4">
<p className="text-gray-500 italic">
Emails you <strong>have sent</strong> are displayed here.
</p>
<div className="h-10"></div>
</div>
</div>
<div>
<div className="w-full p-3 flex flex-col gap-3 bg-neutral-100 shadow-md rounded-md">
{isLoading ? (
<EmailHistoryCardLoading />
) : (
<EmailHistoryCard data={data} />
)}
<div className="flex justify-end gap-8 pb-1 px-2">
<button
className={`flex items-center gap-1 ${
!currentLastEvaluatedKey
? "cursor-default text-gray-400"
: "hover:text-gray-600 hover:underline"
}`}
onClick={() => {
fetchFiles(5, previousLastEvaluatedKey);
}}
disabled={!currentLastEvaluatedKey}
>
<ChevronLeft size={20} />
Previous
</button>
<button
className={`flex items-center gap-1 ${
!nextLastEvaluatedKey
? "cursor-default text-gray-400"
: "hover:text-gray-600 hover:underline"
}`}
onClick={() => {
if (nextLastEvaluatedKey) {
fetchFiles(5, nextLastEvaluatedKey);
}
}}
disabled={!nextLastEvaluatedKey}
>
Next
<ChevronRight size={20} />
</button>
</div>
</div>
</div>
</div>
);
}
Loading

0 comments on commit 19df89b

Please sign in to comment.