diff --git a/src/app/api/admin/investasi/[status]/route.ts b/src/app/api/admin/investasi/[status]/route.ts new file mode 100644 index 00000000..04acca4f --- /dev/null +++ b/src/app/api/admin/investasi/[status]/route.ts @@ -0,0 +1,164 @@ +import { prisma } from "@/app/lib"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request, { params }: { + params: { status: string } +} +) { + const method = request.method; + if (method !== "GET") { + return NextResponse.json({ + success: false, + message: "Method not allowed", + }, + { status: 405 } + ) + } + + const { status } = params; + const { searchParams } = new URL(request.url); + const search = searchParams.get("search"); + const page = searchParams.get("page"); + const takeData = 10 + const skipData = Number(page) * takeData - takeData; + + console.log("Ini Status", status); + console.log("Ini Page", page) + + try { + let fixData; + const fixStatus = _.startCase(status); + + if (!page && !search) { + fixData = await prisma.investasi.findMany({ + orderBy: { + updatedAt: "desc", + }, + where: { + active: true, + MasterStatusInvestasi: { + name: fixStatus + }, + }, + include: { + author: { + select: { + id: true, + username: true, + Profile: { + select: { + name: true, + }, + }, + }, + }, + MasterStatusInvestasi: true, + }, + }); + } else if (!page && search) { + fixData = await prisma.investasi.findMany({ + orderBy: { + updatedAt: "desc", + }, + where: { + active: true, + MasterStatusInvestasi: { + name: fixStatus + }, + title: { + contains: search, + mode: "insensitive", + }, + }, + include: { + author: { + select: { + id: true, + username: true, + Profile: { + select: { + name: true, + }, + }, + }, + }, + MasterStatusInvestasi: true, + }, + }); + } else if (page && !search) { + + const data = await prisma.investasi.findMany({ + take: takeData, + skip: skipData, + orderBy: [ + { + countDown: "desc", + }, + ], + where: { + active: true, + MasterStatusInvestasi: { + name: fixStatus + } + + }, + include: { + MasterStatusInvestasi: true, + BeritaInvestasi: true, + DokumenInvestasi: true, + ProspektusInvestasi: true, + MasterPembagianDeviden: true, + MasterPencarianInvestor: true, + MasterPeriodeDeviden: true, + author: true, + Investasi_Invoice: { + where: { + statusInvoiceId: "2", + }, + }, + }, + }); + + const nCount = await prisma.investasi.count({ + where: { + active: true, + MasterStatusInvestasi: { + name: fixStatus + } + + }, + }); + + console.log("data >", data) + + fixData = { + data: data, + nPage: _.ceil(nCount / takeData), + }; + + } + + return NextResponse.json({ + success: true, + message: "Success", + data: fixData, + }, + { status: 200 } + ) + + } catch (error) { + console.log(error) + return NextResponse.json({ + success: false, + message: "Failed", + reason: (error as Error).message, + }, + { status: 500 } + ) + + } + + +} \ No newline at end of file diff --git a/src/app/dev/admin/investasi/sub-menu/reject/page.tsx b/src/app/dev/admin/investasi/sub-menu/reject/page.tsx index 9f4af9e0..9d2b186b 100644 --- a/src/app/dev/admin/investasi/sub-menu/reject/page.tsx +++ b/src/app/dev/admin/investasi/sub-menu/reject/page.tsx @@ -2,10 +2,10 @@ import { Admin_TableRejectInvestasi } from "@/app_modules/admin/investasi"; import { adminInvestasi_funGetAllReject } from "@/app_modules/admin/investasi/fun/get/get_all_reject"; export default async function Page() { - const dataInvestsi = await adminInvestasi_funGetAllReject({page: 1}); + // const dataInvestsi = await adminInvestasi_funGetAllReject({page: 1}); return ( <> - + ); } diff --git a/src/app/dev/admin/investasi/sub-menu/review/page.tsx b/src/app/dev/admin/investasi/sub-menu/review/page.tsx index 454397aa..0d493140 100644 --- a/src/app/dev/admin/investasi/sub-menu/review/page.tsx +++ b/src/app/dev/admin/investasi/sub-menu/review/page.tsx @@ -2,10 +2,10 @@ import { Admin_TableReviewInvestasi } from "@/app_modules/admin/investasi"; import { adminInvestasi_funGetAllReview } from "@/app_modules/admin/investasi/fun/get/get_all_review"; export default async function Page() { - const dataInvestsi = await adminInvestasi_funGetAllReview({ page: 1 }); + // const dataInvestsi = await adminInvestasi_funGetAllReview({ page: 1 }); return ( <> - + ); } diff --git a/src/app_modules/admin/event/_view/view_table_review.tsx b/src/app_modules/admin/event/_view/view_table_review.tsx index f8b4b54e..7f1dce9b 100644 --- a/src/app_modules/admin/event/_view/view_table_review.tsx +++ b/src/app_modules/admin/event/_view/view_table_review.tsx @@ -49,6 +49,7 @@ import adminNotifikasi_funCreateToUser from "../../notifikasi/fun/create/fun_cre import { adminEvent_funGetListReview } from "../fun"; import { AdminEvent_funEditStatusPublishById } from "../fun/edit/fun_edit_status_publish_by_id"; import { AdminEvent_funEditCatatanById } from "../fun/edit/fun_edit_status_reject_by_id"; +import { AdminColor } from "@/app_modules/_global/color/color_pallet"; export default function AdminEvent_ComponentTableReview() { const [data, setData] = useState(null); @@ -261,28 +262,28 @@ export default function AdminEvent_ComponentTableReview() { return data.map((e, i) => ( -
+
{e?.Author?.username}
-
+
{e.title}
-
+
{e.lokasi}
-
+
{e.EventMaster_TipeAcara.name}
-
+
{new Intl.DateTimeFormat("id-ID", { dateStyle: "full", @@ -297,7 +298,7 @@ export default function AdminEvent_ComponentTableReview() {
-
+
{new Intl.DateTimeFormat("id-ID", { dateStyle: "full", @@ -313,7 +314,7 @@ export default function AdminEvent_ComponentTableReview() { -
+
) : ( - + {isShowReload && (
@@ -416,35 +417,33 @@ export default function AdminEvent_ComponentTableReview() { horizontalSpacing={"md"} p={"md"} w={1500} - striped - highlightOnHover > -
Username
+
Username
-
Judul
+
Judul
-
Lokasi
+
Lokasi
-
Tipe Acara
+
Tipe Acara
-
Tanggal & Waktu Mulai
+
Tanggal & Waktu Mulai
-
Tanggal & Waktu Selesai
+
Tanggal & Waktu Selesai
-
Deskripsi
+
Deskripsi
-
Aksi
+
Aksi
diff --git a/src/app_modules/admin/event/table_status/table_reject.tsx b/src/app_modules/admin/event/table_status/table_reject.tsx index d3d52ae0..0678bfe9 100644 --- a/src/app_modules/admin/event/table_status/table_reject.tsx +++ b/src/app_modules/admin/event/table_status/table_reject.tsx @@ -137,20 +137,20 @@ function TableStatus() { return data.map((e, i) => ( -
{e?.Author?.username}
+
{e?.Author?.username}
-
{e.title}
+
{e.title}
-
{e.lokasi}
+
{e.lokasi}
-
{e.EventMaster_TipeAcara.name}
+
{e.EventMaster_TipeAcara.name}
-
+
{new Intl.DateTimeFormat("id-ID", { dateStyle: "full", @@ -165,7 +165,7 @@ function TableStatus() {
-
+
{new Intl.DateTimeFormat("id-ID", { dateStyle: "full", @@ -181,7 +181,7 @@ function TableStatus() { -
+
{" "} -
+
) : ( - + diff --git a/src/app_modules/admin/investasi/_lib/api_fetch_admin_investasi.ts b/src/app_modules/admin/investasi/_lib/api_fetch_admin_investasi.ts new file mode 100644 index 00000000..55a1ddab --- /dev/null +++ b/src/app_modules/admin/investasi/_lib/api_fetch_admin_investasi.ts @@ -0,0 +1,51 @@ +export { + apiGetAdminInvestasiCountDashboard, + apiGetAdminInvestasiByStatus, +} +const apiGetAdminInvestasiCountDashboard = async ({ name }: + { name: "Publish" | "Review" | "Reject" }) => { + + + const { token } = await fetch("/api/get-cookie").then((res) => res.json()); + if (!token) return await token.json().catch(() => null); + + + const response = await fetch(`/api/admin/investasi/dashboard/${name}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + "Access-Control-Allow-Origin": "*", + Authorization: `Bearer ${token}`, + }, + }) + + return await response.json().catch(() => null); +}; + +const apiGetAdminInvestasiByStatus = async ({ status, page, search }: { + status: "Publish" | "Review" | "Reject", + page: string, + search: string +}) => { + console.log("dgsdg") + const { token } = await fetch("/api/get-cookie").then((res) => res.json()); + if (!token) return await token.json().catch(() => null); + console.log("Ini token", token) + console.log("Ini Page", page) + console.log("Ini Search", search) + + + const isPage = page ? `?page=${page}` : ""; + const isSearch = search ? `&search=${search}` : ""; + const response = await fetch(`/api/admin/investasi/${status}${isPage}${isSearch}`, { + headers: { + "Content-Type": "application/json", + Accept: "application/json", + "Access-Control-Allow-Origin": "*", + Authorization: `Bearer ${token}`, + }, + }) + console.log("Ini response", response) + return await response.json().catch(() => null); +} \ No newline at end of file diff --git a/src/app_modules/admin/investasi/_lib/api_fetch_count_status.ts b/src/app_modules/admin/investasi/_lib/api_fetch_count_status.ts deleted file mode 100644 index ed19bd40..00000000 --- a/src/app_modules/admin/investasi/_lib/api_fetch_count_status.ts +++ /dev/null @@ -1,22 +0,0 @@ -const apiGetInvestasiCountDashboard = async ({ name }: - { name: "Publish" | "Review" | "Reject" }) => { - - - const { token } = await fetch("/api/get-cookie").then((res) => res.json()); - if (!token) return await token.json().catch(() => null); - - - const response = await fetch(`/api/admin/investasi/dashboard/${name}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - "Access-Control-Allow-Origin": "*", - Authorization: `Bearer ${token}`, - }, - }) - - return await response.json().catch(() => null); -}; - -export default apiGetInvestasiCountDashboard; \ No newline at end of file diff --git a/src/app_modules/admin/investasi/main/table_publish.tsx b/src/app_modules/admin/investasi/main/table_publish.tsx index 0d7252f1..85a5f39b 100644 --- a/src/app_modules/admin/investasi/main/table_publish.tsx +++ b/src/app_modules/admin/investasi/main/table_publish.tsx @@ -25,6 +25,10 @@ import { adminInvestasi_funGetAllPublish } from "../fun/get/get_all_publish"; import { ComponentAdminGlobal_TitlePage } from "../../_admin_global/_component"; import { MainColor } from "@/app_modules/_global/color"; import { AccentColor, AdminColor } from "@/app_modules/_global/color/color_pallet"; +import { apiGetAdminInvestasiByStatus } from "../_lib/api_fetch_admin_investasi"; +import { useShallowEffect } from "@mantine/hooks"; +import { clientLogger } from "@/util/clientLogger"; +import CustomSkeleton from "@/app_modules/components/CustomSkeleton"; export default function Admin_TablePublishInvestasi({ dataInvestsi, @@ -50,83 +54,116 @@ function TableView({ listData }: { listData: any }) { const [isSearch, setSearch] = useState(""); const [isLoading, setLoading] = useState(false); const [idData, setIdData] = useState(""); + const [origin, setOrigin] = useState(""); - async function onSearch(s: string) { - setSearch(s); + useShallowEffect(() => { + if (typeof window !== "undefined") { + setOrigin(window.location.origin); + } + }, []) + + useShallowEffect(() => { + const loadInitialData = async () => { + try { + const response = await apiGetAdminInvestasiByStatus({ + status: "Publish", + page: `${activePage}`, + search: isSearch, + }); + + if (response?.success && response?.data?.data) { + setData(response.data.data); + setNPage(response.data.nPage || 1); + } else { + console.error("Invalid data format received:", response); + setData([]); + } + } catch (error) { + clientLogger.error("Error get data table publish", error); + setData([]); + } + }; + + loadInitialData(); + }, [activePage, isSearch]); + const onSearch = async (searchTerm: string) => { + setSearch(searchTerm); setActivePage(1); - const loadData = await adminInvestasi_funGetAllPublish({ - page: 1, - search: s, - }); - setData(loadData.data as any); - setNPage(loadData.nPage); } - async function onPageClick(p: any) { - setActivePage(p); - const loadData = await adminInvestasi_funGetAllPublish({ - search: isSearch, - page: p, - }); - setData(loadData.data as any); - setNPage(loadData.nPage); + const onPageClick = (page: number) => { + setActivePage(page) } - const tableBody = data.map((e) => ( - - - - - - - - - - )); + const renderTableBody = () => { + if(!Array.isArray(data) || data.length === 0) { + return ( + + + + ); + } + return data.map((e, i) => ( + + + + + + + + + + )); + } + return ( <> @@ -164,8 +201,8 @@ function TableView({ listData }: { listData: any }) { /> */} - {_.isEmpty(data) ? ( - + {!data ? ( + ) : ( @@ -203,7 +240,7 @@ function TableView({ listData }: { listData: any }) { - {tableBody} + {renderTableBody()}
-
Username
+
Username
-
Judul
+
Judul
-
Lokasi
+
Lokasi
-
Tipe Acara
+
Tipe Acara
-
Tanggal & Waktu Mulai
+
Tanggal & Waktu Mulai
-
Tanggal & Waktu Selesai
+
Tanggal & Waktu Selesai
-
Cacatan
+
Cacatan
-
Deskripsi
+
Deskripsi
-
Aksi
+
Aksi
-
- {e.author.username} -
-
-
- {e.title} -
-
-
{_.toNumber(e.progress).toFixed(2)} %
-
-
- {new Intl.NumberFormat("id-ID", { - maximumFractionDigits: 10, - }).format(+e.sisaLembar)} -
-
-
- {new Intl.NumberFormat("id-ID", { - maximumFractionDigits: 10, - }).format(+e.totalLembar)} -
-
-
- {e.Investasi_Invoice.length} -
-
-
- -
-
+
+ Tidak ada data +
+
+
+ {e.author.username} +
+
+
+ {e.title} +
+
+
{_.toNumber(e.progress).toFixed(2)} %
+
+
+ {new Intl.NumberFormat("id-ID", { + maximumFractionDigits: 10, + }).format(+e.sisaLembar)} +
+
+
+ {new Intl.NumberFormat("id-ID", { + maximumFractionDigits: 10, + }).format(+e.totalLembar)} +
+
+
+ {e.Investasi_Invoice.length} +
+
+
+ +
+
diff --git a/src/app_modules/admin/investasi/main/table_reject.tsx b/src/app_modules/admin/investasi/main/table_reject.tsx index 9d81340c..b5d60ce4 100644 --- a/src/app_modules/admin/investasi/main/table_reject.tsx +++ b/src/app_modules/admin/investasi/main/table_reject.tsx @@ -31,162 +31,186 @@ import { RouterAdminInvestasi } from "@/app/lib/router_admin/router_admin_invest import { ComponentAdminGlobal_TitlePage } from "../../_admin_global/_component"; import { MainColor } from "@/app_modules/_global/color"; import { AccentColor, AdminColor } from "@/app_modules/_global/color/color_pallet"; +import { useShallowEffect } from "@mantine/hooks"; +import { clientLogger } from "@/util/clientLogger"; +import { apiGetAdminInvestasiByStatus } from "../_lib/api_fetch_admin_investasi"; +import CustomSkeleton from "@/app_modules/components/CustomSkeleton"; -export default function Admin_TableRejectInvestasi({ - dataInvestsi, -}: { - dataInvestsi: any[]; -}) { - const [investasi, setInvestasi] = useState(dataInvestsi); - const router = useRouter(); - +export default function Admin_TableRejectInvestasi() { return ( <> - + ); - const tableBody = investasi.map((e) => - e.MasterStatusInvestasi.id === "4" ? ( - - - - - {e.author.username} - - - {_.capitalize(e.title)} - {e.catatan} - -
- - - router.push(RouterAdminInvestasi_OLD.konfirmasi + `${e.id}`) - } - > - - - -
- - - ) : ( - "" - ) - ); + // const tableBody = investasi.map((e) => + // e.MasterStatusInvestasi.id === "4" ? ( + // + // + // + // + // {e.author.username} + // + // + // {_.capitalize(e.title)} + // {e.catatan} + // + //
+ // + // + // router.push(RouterAdminInvestasi_OLD.konfirmasi + `${e.id}`) + // } + // > + // + // + // + //
+ // + // + // ) : ( + // "" + // ) + // ); - return ( - <> - - router.push(RouterAdminInvestasi_OLD.main_investasi)} - > - - - - - - Reject - - - - - - - - - - - {tableBody} -
UsernameNama Proyek InvestasiCatatan -
Aksi
-
-
-
-
- - ); + // return ( + // <> + // + // router.push(RouterAdminInvestasi_OLD.main_investasi)} + // > + // + // + // + // + // + // Reject + // + // + // + // + // + // + // + // + // + // + // {tableBody} + //
UsernameNama Proyek InvestasiCatatan + //
Aksi
+ //
+ //
+ //
+ //
+ // + // ); } -function TableView({ listData }: { listData: any }) { +function TableView() { const router = useRouter(); - const [data, setData] = useState(listData.data); - const [nPage, setNPage] = useState(listData.nPage); + const [data, setData] = useState (null); + const [nPage, setNPage] = useState(1); const [activePage, setActivePage] = useState(1); const [isSearch, setSearch] = useState(""); const [isLoading, setLoading] = useState(false); const [idData, setIdData] = useState(""); - async function onSearch(s: string) { - setSearch(s); + + useShallowEffect(() => { + const loadInitialData = async () => { + try { + const response = await apiGetAdminInvestasiByStatus({ + status: "Reject", + page: `${activePage}`, + search: isSearch, + }); + + if (response?.success && response?.data.data) { + setData(response.data.data); + setNPage(response.data.nPage || 1); + } else { + console.error("Invalid data format recieved:", response); + setData([]); + } + } catch (error) { + clientLogger.error("Error get data reject", error); + setData([]); + } + } + loadInitialData(); + }, [activePage, isSearch]); + const onSearch = async (searchTerm: string) => { + setSearch(searchTerm); setActivePage(1); - const loadData = await adminInvestasi_funGetAllReject({ - page: 1, - search: s, - }); - setData(loadData.data as any); - setNPage(loadData.nPage); } - async function onPageClick(p: any) { - setActivePage(p); - const loadData = await adminInvestasi_funGetAllReject({ - search: isSearch, - page: p, - }); - setData(loadData.data as any); - setNPage(loadData.nPage); + async function onPageClick(page: number) { + setActivePage(page); } - const tableBody = data.map((e) => ( - - -
- {e.author.username} -
- - -
- {e.title} -
- - -
- {e.catatan} -
- + const renderTableBody = () => { + if (!Array.isArray(data) || data.length === 0) { + return ( + + +
+ Tidak ada data +
+ + + ) + } + return data.map((e, i) => ( + + +
+ {e.author.username} +
+ + +
+ {e.title} +
+ + +
+ {e.catatan} +
+ + + +
+ +
+ + + )); + } - -
- -
- - - )); + return ( <> @@ -224,8 +248,8 @@ function TableView({ listData }: { listData: any }) { /> */} - {_.isEmpty(data) ? ( - + {!data ? ( + ) : ( @@ -253,7 +277,7 @@ function TableView({ listData }: { listData: any }) { - {tableBody} + {renderTableBody()}
diff --git a/src/app_modules/admin/investasi/main/table_review.tsx b/src/app_modules/admin/investasi/main/table_review.tsx index fc210299..6ee348ab 100644 --- a/src/app_modules/admin/investasi/main/table_review.tsx +++ b/src/app_modules/admin/investasi/main/table_review.tsx @@ -30,26 +30,25 @@ import ComponentAdminGlobal_TampilanRupiahDonasi from "../../_admin_global/tampi import { adminInvestasi_funGetAllReview } from "../fun/get/get_all_review"; import { ComponentAdminGlobal_TitlePage } from "../../_admin_global/_component"; import { AdminColor } from "@/app_modules/_global/color/color_pallet"; +import { apiGetAdminInvestasiByStatus } from "../_lib/api_fetch_admin_investasi"; +import { clientLogger } from "@/util/clientLogger"; +import CustomSkeleton from "@/app_modules/components/CustomSkeleton"; -export default function Admin_TableReviewInvestasi({ - dataInvestsi, -}: { - dataInvestsi: any[]; -}) { +export default function Admin_TableReviewInvestasi() { return ( <> - + ); } -function TableView({ listData }: { listData: any }) { +function TableView() { const router = useRouter(); - const [data, setData] = useState(listData.data); - const [nPage, setNPage] = useState(listData.nPage); + const [data, setData] = useState(null); + const [nPage, setNPage] = useState(1); const [activePage, setActivePage] = useState(1); const [isSearch, setSearch] = useState(""); const [isLoading, setLoading] = useState(false); @@ -62,94 +61,108 @@ function TableView({ listData }: { listData: any }) { const [isLoadingReload, setLoadingReload] = useState(false); useShallowEffect(() => { - if (isAdminInvestasi_TriggerReview) { - setIsShowReload(false); - setIsAdminInvestasi_TriggerReview(false) - } - }, [isAdminInvestasi_TriggerReview]); + loadInitialData(); + }, [activePage, isSearch]); + const loadInitialData = async () => { + try { + const response = await apiGetAdminInvestasiByStatus({ + status: "Review", + page: `${activePage}`, + search: isSearch, + }); + if (response?.success && response?.data?.data) { + setData(response.data.data) + setNPage(response.data.nPage || 1) + } else { + console.error("Invalid data format received:", response); + setData([]); + } + } catch (error) { + clientLogger.error("Error get data table review", error); + setData([]); + } + } + const onSearch = async (searchTerm: string) => { + setSearch(searchTerm); + setActivePage(1); + } + + const onPageClick = (page: number) => { + setActivePage(page); + } async function onLoadData() { - const loadData = await adminInvestasi_funGetAllReview({ page: 1 }); - setData(loadData.data as any); - setNPage(loadData.nPage); - setLoadingReload(false); + loadInitialData(); + setLoading(false); setIsShowReload(false); setIsAdminInvestasi_TriggerReview(false); } - async function onSearch(s: string) { - setSearch(s); - setActivePage(1); - const loadData = await adminInvestasi_funGetAllReview({ - page: 1, - search: s, - }); - setData(loadData.data as any); - setNPage(loadData.nPage); + const renderTableBody = () => { + if (!Array.isArray(data) || data.length === 0) { + return ( + + +
+ Tidak ada data +
+ + + ) + } + return data.map((e, i) => ( + + +
+ {e.author.username} +
+ + +
+ {e.title} +
+ + +
+ {e.roi} % +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + )); } - async function onPageClick(p: any) { - setActivePage(p); - const loadData = await adminInvestasi_funGetAllReview({ - search: isSearch, - page: p, - }); - setData(loadData.data as any); - setNPage(loadData.nPage); - } - - const tableBody = data.map((e) => ( - - -
- {e.author.username} -
- - -
- {e.title} -
- - -
- {e.roi} % -
- - -
- -
- - -
- -
- - -
- -
- - - )); - return ( <> @@ -158,13 +171,13 @@ function TableView({ listData }: { listData: any }) { color={AdminColor.softBlue} component={ } - radius={"xl"} - placeholder="Cari nama proyek" - onChange={(val) => { - onSearch(val.currentTarget.value); - }} - /> + icon={} + radius={"xl"} + placeholder="Cari nama proyek" + onChange={(val) => { + onSearch(val.currentTarget.value); + }} + /> } /> {/* */} - {_.isEmpty(data) ? ( - + {!data ? ( + ) : ( {isShowReload && ( @@ -245,7 +258,7 @@ function TableView({ listData }: { listData: any }) { - {tableBody} + {renderTableBody()}
diff --git a/src/app_modules/admin/investasi/main/view.tsx b/src/app_modules/admin/investasi/main/view.tsx index c27312dc..ddeda2f3 100644 --- a/src/app_modules/admin/investasi/main/view.tsx +++ b/src/app_modules/admin/investasi/main/view.tsx @@ -50,7 +50,8 @@ import { clientLogger } from "@/util/clientLogger"; import CustomSkeleton from "@/app_modules/components/CustomSkeleton"; import global_limit from "@/app/lib/limit"; import { useShallowEffect } from "@mantine/hooks"; -import apiGetInvestasiCountDashboard from "../_lib/api_fetch_count_status"; +import { apiGetAdminInvestasiCountDashboard } from "../_lib/api_fetch_admin_investasi"; + export default function Admin_Investasi({ listInvestasi, @@ -88,7 +89,7 @@ export default function Admin_Investasi({ async function onLoadCountPublish() { try { - const response = await apiGetInvestasiCountDashboard({ + const response = await apiGetAdminInvestasiCountDashboard({ name: "Publish", }); @@ -103,7 +104,7 @@ export default function Admin_Investasi({ } async function onLoadCountReview() { try { - const response = await apiGetInvestasiCountDashboard({ + const response = await apiGetAdminInvestasiCountDashboard({ name: "Review", }); @@ -116,7 +117,7 @@ export default function Admin_Investasi({ } async function onLoadCountReject() { try { - const response = await apiGetInvestasiCountDashboard({ + const response = await apiGetAdminInvestasiCountDashboard({ name: "Reject", }); diff --git a/src/middleware.ts b/src/middleware.ts index 31ad130c..01c2a43d 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -43,6 +43,7 @@ const middlewareConfig: MiddlewareConfig = { // "/api/admin/voting/dashboard/*", // "/api/admin/job/dashboard/*", // "/api/admin/forum/dashboard/*", + // Akses awal diff --git a/templates/ff.config.json b/templates/ff.config.json new file mode 100644 index 00000000..ad05a6f5 --- /dev/null +++ b/templates/ff.config.json @@ -0,0 +1,82 @@ +{ + "name": "base", + "configs": [ + { + "name": "bloc", + "commands": [ + { + "name": "[FF] New Big Pack Bloc", + "templates": ["*"], + "key": "bigpack", + "files": [ + "bloc", + "event", + "index", + "model", + "page", + "provider", + "repository", + "screen", + "state" + ] + }, + { + "name": "[FF] New Small Pack Bloc", + "templates": ["*"], + "key": "smallpack", + "files": ["bloc", "event", "index", "page", "screen", "state"] + }, + { + "name": "[FF] New Bloc", + "templates": ["*"], + "files": ["bloc"] + }, + { + "name": "[FF] New Event", + "templates": ["*"], + "files": ["event"] + }, + { + "name": "[FF] New Model", + "templates": ["*"], + "files": ["model"] + }, + { + "name": "[FF] New Page", + "templates": ["*"], + "files": ["page"] + }, + { + "name": "[FF] New Provider", + "templates": ["*"], + "files": ["provider"] + }, + { + "name": "[FF] New Repository", + "templates": ["*"], + "files": ["repository"] + }, + { + "name": "[FF] New Screen", + "templates": ["*"], + "files": ["screen"] + }, + { + "name": "[FF] New State", + "templates": ["*"], + "files": ["state"] + }, + { + "name": "[FF] New Index", + "templates": ["*"], + "files": ["index"] + }, + { + "name": "[FF] New Navigate(Navme)", + "templates": ["navigate"], + "files": ["navigate"] + } + ] + } + ] +} diff --git a/templates/ff_bloc/bloc.tmpl b/templates/ff_bloc/bloc.tmpl new file mode 100644 index 00000000..f806fd48 --- /dev/null +++ b/templates/ff_bloc/bloc.tmpl @@ -0,0 +1,16 @@ +import 'package:ff_bloc/ff_bloc.dart'; + +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Bloc extends FFBloc<${upperName}Event, ${upperName}State> { + ${upperName}Bloc({ + required this.provider, + super.initialState = const ${upperName}State(), + }); + /// Use this for all requests to backend - you can mock it in tests + final ${upperName}Provider provider; + + @override + ${upperName}State onErrorState(Object error) => state.copy(error: error, isLoading: false); + +} diff --git a/templates/ff_bloc/event.tmpl b/templates/ff_bloc/event.tmpl new file mode 100644 index 00000000..7dcff332 --- /dev/null +++ b/templates/ff_bloc/event.tmpl @@ -0,0 +1,78 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; +import 'package:ff_bloc/ff_bloc.dart'; + +import 'package:${appName}${relative}/index.dart'; + +@immutable +abstract class ${upperName}Event implements FFBlocEvent<${upperName}State, ${upperName}Bloc> {} + +/// Initial Event with load data +class Load${upperName}Event extends ${upperName}Event { + Load${upperName}Event({required this.id}); + final String? id; + + static const String _name = 'Load${upperName}Event'; + + @override + String toString() => _name; + + @override + Stream<${upperName}State> applyAsync({required ${upperName}Bloc bloc}) async* { + // set loading true for show loading + yield bloc.state.copyWithoutError(isLoading: true); + // fetch data + final result = await bloc.provider.fetchAsync(id); + // set data to state + yield bloc.state.copyWithoutError( + isLoading: false, + data: ${upperName}ViewModel(items: result), + ); + } +} + + +class Add${upperName}Event extends ${upperName}Event { + static const String _name = 'Add${upperName}Event'; + + @override + String toString() => _name; + + @override + Stream<${upperName}State> applyAsync({required ${upperName}Bloc bloc}) async* { + yield bloc.state.copyWithoutError(isLoading: true); + final result = await bloc.provider.addMore(bloc.state.data?.items); + yield bloc.state.copyWithoutError( + isLoading: false, + data: ${upperName}ViewModel(items: result), + ); + } +} + +class ErrorYouAwesomeEvent extends YouAwesomeEvent { + static const String _name = 'ErrorYouAwesomeEvent'; + + @override + String toString() => _name; + + @override + Stream applyAsync({required YouAwesomeBloc bloc}) async* { + throw Exception('Test error'); + } +} + +class Clear${upperName}Event extends ${upperName}Event { + static const String _name = 'Clear${upperName}Event'; + + @override + String toString() => _name; + + @override + Stream<${upperName}State> applyAsync({required ${upperName}Bloc bloc}) async* { + yield bloc.state.copyWithoutError(isLoading: true); + yield bloc.state.copyWithoutData( + isLoading: false, + ); + } +} diff --git a/templates/ff_bloc/index.tmpl b/templates/ff_bloc/index.tmpl new file mode 100644 index 00000000..e69de29b diff --git a/templates/ff_bloc/model.tmpl b/templates/ff_bloc/model.tmpl new file mode 100644 index 00000000..4b13e7cb --- /dev/null +++ b/templates/ff_bloc/model.tmpl @@ -0,0 +1,51 @@ +// ignore: depend_on_referenced_packages +import 'package:equatable/equatable.dart'; + +class ${upperName}Model extends Equatable { + const ${upperName}Model({ + required this.name, + }); + final String name; + + @override + List get props => [ name]; + + Map toMap() { + return { + 'name': name, + }; + } + + static ${upperName}Model? fromMap(Map? map) { + if (map == null) { + return null; + } + + return ${upperName}Model( + name: map['name']!.toString(), + ); + } + +} + +class ${upperName}ViewModel extends Equatable { + const ${upperName}ViewModel({ + // TODO(all): add all required constructor parameters + required this.items, + }); + + // TODO(all): declare your fields here + final List<${upperName}Model>? items; + + @override + List get props => [items /*TODO(all): List all fields here*/]; + + // TODO(all): implement copyWith + ${upperName}ViewModel copyWith({ + List<${upperName}Model>? items, + }) { + return ${upperName}ViewModel( + items: items ?? this.items, + ); + } +} diff --git a/templates/ff_bloc/page.tmpl b/templates/ff_bloc/page.tmpl new file mode 100644 index 00000000..d147a0fd --- /dev/null +++ b/templates/ff_bloc/page.tmpl @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:${appName}${relative}/index.dart'; + + +class ${upperName}Page extends StatefulWidget { + const ${upperName}Page({ + required this.bloc, + super.key + }); + static const String routeName = '/${privateName}'; + + final ${upperName}Bloc? bloc; + + @override + State<${upperName}Page> createState() => _${upperName}PageState(); +} + +class _${upperName}PageState extends State<${upperName}Page> { + + ${upperName}Bloc? _bloc; + ${upperName}Bloc get bloc { + // get it by DI in real code. + _bloc ??= widget.bloc ?? ${upperName}Bloc(); + return _bloc!; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text('${upperName}'), + actions: [ + IconButton( + icon: const Icon(Icons.error), + onPressed: () { + bloc.add(ErrorYouAwesomeEvent()); + }, + ), + IconButton( + icon: const Icon(Icons.add), + onPressed: () { + bloc.add(Add${upperName}Event()); + }, + ), + IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + bloc.add(Clear${upperName}Event()); + }, + ), + ], + ), + body: ${upperName}Screen(bloc: bloc), + ); + } +} diff --git a/templates/ff_bloc/provider.tmpl b/templates/ff_bloc/provider.tmpl new file mode 100644 index 00000000..23f68b26 --- /dev/null +++ b/templates/ff_bloc/provider.tmpl @@ -0,0 +1,26 @@ + +import 'dart:async'; +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Provider { + + Future?> fetchAsync(String? id) async { + // write logic here to send request to server + if (id == null) { + return null; + } + return [${upperName}Model(name: id)]; + } + + + Future?> addMore(List<${upperName}Model>? now) async { + // write logic here to send request to server + final result = [ + ...(now ?? <${upperName}Model>[]), + ${upperName}Model(name: now?.length.toString() ?? '0') + ]; + return result; + } + +} + diff --git a/templates/ff_bloc/screen.tmpl b/templates/ff_bloc/screen.tmpl new file mode 100644 index 00000000..7f78ad13 --- /dev/null +++ b/templates/ff_bloc/screen.tmpl @@ -0,0 +1,126 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:${appName}${relative}/index.dart'; + + +class ${upperName}Screen extends StatefulWidget { + const ${upperName}Screen({ + required this.bloc, + super.key, + }) ; + + @protected + final ${upperName}Bloc bloc; + + @override + State<${upperName}Screen> createState() { + return ${upperName}ScreenState(); + } +} + +class ${upperName}ScreenState extends State<${upperName}Screen> { + + @override + void initState() { + super.initState(); + // load data on init widget if bloc has not data + if (!widget.bloc.state.hasData) { + _load(); + } + } + + @override + void dispose() { + // dispose bloc if you use subscriptions in bloc + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder<${upperName}Bloc, ${upperName}State>( + bloc: widget.bloc, + builder: ( + BuildContext context, + ${upperName}State currentState, + ) { + // declaration of bloc states + return currentState.when( + onLoading: ()=>const CircularProgressIndicator(), + onEmpty: (data) => _Empty(), + onData: (data) => _BodyList(data: data), + onError: (e) => Center( + child: Column( + children: [ + Text(e.toString()), + TextButton( + onPressed: _load, + child: const Text('ReLoad'), + ) + ], + ), + ), + ); + }, + ); + } + + void _load() { + widget.bloc.add(Load${upperName}Event(id:'1')); + } + +} + + +class _BodyList extends StatefulWidget { + const _BodyList({required this.data}); + + final ${upperName}ViewModel data; + + @override + State<_BodyList> createState() => _BodyListState(); +} + +class _BodyListState extends State<_BodyList> { + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + + return CustomScrollView( + // primary: true, + slivers: [ + const SliverToBoxAdapter(child: Divider()), + SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + final item = widget.data.items![index]; + if (index == 0) { + return Text('Header $index, id = '+item.name); + } + return Text('Index = $index, id = '+item.name); + }, + childCount: widget.data.items!.length, + ))]); + } +} + + +class _Empty extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text('Empty'), + ], + ); + } +} \ No newline at end of file diff --git a/templates/ff_bloc/state.tmpl b/templates/ff_bloc/state.tmpl new file mode 100644 index 00000000..6bf34a03 --- /dev/null +++ b/templates/ff_bloc/state.tmpl @@ -0,0 +1,15 @@ +import 'package:ff_bloc/ff_bloc.dart'; + +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}State extends FFState<${upperName}State, ${upperName}ViewModel> { + const ${upperName}State({ + super.version = 0, + super.isLoading = false, + super.data, + super.error, + }); + + @override + StateCopyFactory<${upperName}State, ${upperName}ViewModel> getCopyFactory() => ${upperName}State.new; +} diff --git a/templates/mutable/bloc.tmpl b/templates/mutable/bloc.tmpl new file mode 100644 index 00000000..190adf5d --- /dev/null +++ b/templates/mutable/bloc.tmpl @@ -0,0 +1,37 @@ +import 'dart:async'; +import 'dart:developer' as developer; + +import 'package:bloc/bloc.dart'; +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Bloc extends Bloc<${upperName}Event, ${upperName}State> { + // todo: check singleton for logic in project + // use GetIt for DI in projct + static final ${upperName}Bloc _${privateName}BlocSingleton = ${upperName}Bloc._internal(); + factory ${upperName}Bloc() { + return _${privateName}BlocSingleton; + } + + ${upperName}Bloc._internal(): super(Un${upperName}State(0)){ + on<${upperName}Event>((event, emit) { + return emit.forEach<${upperName}State>( + event.applyAsync(currentState: state, bloc: this), + onData: (state) => state, + onError: (error, stackTrace) { + developer.log('$error', name: '${upperName}Bloc', error: error, stackTrace: stackTrace); + return Error${upperName}State(0, error.toString()); + }, + ); + }); + } + + @override + Future close() async{ + // dispose objects + await super.close(); + } + + @override + ${upperName}State get initialState => Un${upperName}State(0); + +} diff --git a/templates/mutable/event.tmpl b/templates/mutable/event.tmpl new file mode 100644 index 00000000..cf45ca21 --- /dev/null +++ b/templates/mutable/event.tmpl @@ -0,0 +1,42 @@ +import 'dart:async'; +import 'dart:developer' as developer; + +import 'package:${appName}${relative}/index.dart'; +import 'package:meta/meta.dart'; + +@immutable +abstract class ${upperName}Event { + Stream<${upperName}State> applyAsync( + {${upperName}State currentState, ${upperName}Bloc bloc}); + final ${upperName}Repository _${privateName}Repository = ${upperName}Repository(); +} + +class Un${upperName}Event extends ${upperName}Event { + @override + Stream<${upperName}State> applyAsync({${upperName}State? currentState, ${upperName}Bloc? bloc}) async* { + yield Un${upperName}State(0); + } +} + +class Load${upperName}Event extends ${upperName}Event { + + final bool isError; + @override + String toString() => 'Load${upperName}Event'; + + Load${upperName}Event(this.isError); + + @override + Stream<${upperName}State> applyAsync( + {${upperName}State? currentState, ${upperName}Bloc? bloc}) async* { + try { + yield Un${upperName}State(0); + await Future.delayed(const Duration(seconds: 1)); + _${privateName}Repository.test(isError); + yield In${upperName}State(0, 'Hello world'); + } catch (_, stackTrace) { + developer.log('$_', name: 'Load${upperName}Event', error: _, stackTrace: stackTrace); + yield Error${upperName}State(0, _.toString()); + } + } +} diff --git a/templates/mutable/index.tmpl b/templates/mutable/index.tmpl new file mode 100644 index 00000000..e69de29b diff --git a/templates/mutable/model.tmpl b/templates/mutable/model.tmpl new file mode 100644 index 00000000..64cf5783 --- /dev/null +++ b/templates/mutable/model.tmpl @@ -0,0 +1,47 @@ +import 'package:equatable/equatable.dart'; + +/// generate by https://javiercbk.github.io/json_to_dart/ +class Autogenerated${upperName} { + final List<${upperName}Model> results; + + Autogenerated${upperName}({required this.results}); + + factory Autogenerated${upperName}.fromJson(Map json) { + var temp = []; + if (json['results'] != null) { + temp = <${upperName}Model>[]; + json['results'].forEach((v) { + temp.add(${upperName}Model.fromJson(v as Map)); + }); + } + return Autogenerated${upperName}(results: temp); + } + + Map toJson() { + final data = {}; + data['results'] = results.map((v) => v.toJson()).toList(); + return data; + } +} + +class ${upperName}Model extends Equatable { + final int id; + final String name; + + ${upperName}Model(this.id, this.name); + + @override + List get props => [id, name]; + + factory ${upperName}Model.fromJson(Map json) { + return ${upperName}Model(json['id'] as int, json['name'] as String); + } + + Map toJson() { + final data = {}; + data['id'] = id; + data['name'] = name; + return data; + } + +} diff --git a/templates/mutable/page.tmpl b/templates/mutable/page.tmpl new file mode 100644 index 00000000..5e152b8e --- /dev/null +++ b/templates/mutable/page.tmpl @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Page extends StatefulWidget { + static const String routeName = '/${privateName}'; + + @override + _${upperName}PageState createState() => _${upperName}PageState(); +} + +class _${upperName}PageState extends State<${upperName}Page> { + final _${privateName}Bloc = ${upperName}Bloc(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('${upperName}'), + ), + body: ${upperName}Screen(${privateName}Bloc: _${privateName}Bloc), + ); + } +} diff --git a/templates/mutable/provider.tmpl b/templates/mutable/provider.tmpl new file mode 100644 index 00000000..7fe9c15d --- /dev/null +++ b/templates/mutable/provider.tmpl @@ -0,0 +1,20 @@ +import 'dart:async'; + +class ${upperName}Provider { + Future loadAsync(String token) async { + /// write from keystore/keychain + await Future.delayed(Duration(seconds: 2)); + } + + Future saveAsync(String token) async { + /// write from keystore/keychain + await Future.delayed(Duration(seconds: 2)); + } + + void test(bool isError) { + if (isError == true){ + throw Exception('manual error'); + } + } +} + diff --git a/templates/mutable/repository.tmpl b/templates/mutable/repository.tmpl new file mode 100644 index 00000000..332f91dc --- /dev/null +++ b/templates/mutable/repository.tmpl @@ -0,0 +1,11 @@ +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Repository { + final ${upperName}Provider _${privateName}Provider = ${upperName}Provider(); + + ${upperName}Repository(); + + void test(bool isError) { + _${privateName}Provider.test(isError); + } +} \ No newline at end of file diff --git a/templates/mutable/screen.tmpl b/templates/mutable/screen.tmpl new file mode 100644 index 00000000..e126bf35 --- /dev/null +++ b/templates/mutable/screen.tmpl @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Screen extends StatefulWidget { + const ${upperName}Screen({ + required ${upperName}Bloc ${privateName}Bloc, + Key? key, + }) : _${privateName}Bloc = ${privateName}Bloc, + super(key: key); + + final ${upperName}Bloc _${privateName}Bloc; + + @override + ${upperName}ScreenState createState() { + return ${upperName}ScreenState(); + } +} + +class ${upperName}ScreenState extends State<${upperName}Screen> { + ${upperName}ScreenState(); + + @override + void initState() { + super.initState(); + _load(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder<${upperName}Bloc, ${upperName}State>( + bloc: widget._${privateName}Bloc, + builder: ( + BuildContext context, + ${upperName}State currentState, + ) { + if (currentState is Un${upperName}State) { + return Center( + child: CircularProgressIndicator(), + ); + } + if (currentState is Error${upperName}State) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(currentState.errorMessage), + Padding( + padding: const EdgeInsets.only(top: 32.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + ), + child: Text('reload'), + onPressed: _load, + ), + ), + ], + )); + } + if (currentState is In${upperName}State) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(currentState.hello), + const Text('Flutter files: done'), + Padding( + padding: const EdgeInsets.only(top: 32.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + ), + child: Text('throw error'), + onPressed: () => _load(true), + ), + ), + ], + ), + ); + } + return Center( + child: CircularProgressIndicator(), + ); + + }); + } + + void _load([bool isError = false]) { + widget._${privateName}Bloc.add(Load${upperName}Event(isError)); + } +} diff --git a/templates/mutable/state.tmpl b/templates/mutable/state.tmpl new file mode 100644 index 00000000..52c7ea72 --- /dev/null +++ b/templates/mutable/state.tmpl @@ -0,0 +1,83 @@ +import 'package:equatable/equatable.dart'; + +abstract class ${upperName}State extends Equatable { + ${upperName}State(this.version); + + /// notify change state without deep clone state + final int version; + + /// Copy object for use in action + /// if need use deep clone + ${upperName}State getStateCopy(); + + ${upperName}State getNewVersion(); + + @override + List get props => [version]; +} + +/// UnInitialized +class Un${upperName}State extends ${upperName}State { + + Un${upperName}State(int version) : super(version); + + @override + String toString() => 'Un${upperName}State'; + + @override + Un${upperName}State getStateCopy() { + return Un${upperName}State(0); + } + + @override + Un${upperName}State getNewVersion() { + return Un${upperName}State(version+1); + } +} + +/// Initialized +class In${upperName}State extends ${upperName}State { + + In${upperName}State(int version, this.hello) : super(version); + + final String hello; + + @override + String toString() => 'In${upperName}State $hello'; + + @override + In${upperName}State getStateCopy() { + return In${upperName}State(version, hello); + } + + @override + In${upperName}State getNewVersion() { + return In${upperName}State(version+1, hello); + } + + @override + List get props => [version, hello]; +} + +class Error${upperName}State extends ${upperName}State { + Error${upperName}State(int version, this.errorMessage): super(version); + + final String errorMessage; + + @override + String toString() => 'Error${upperName}State'; + + @override + Error${upperName}State getStateCopy() { + return Error${upperName}State(version, errorMessage); + } + + @override + Error${upperName}State getNewVersion() { + return Error${upperName}State(version+1, + errorMessage); + } + + @override + List get props => [version, errorMessage]; +} diff --git a/templates/navigate/navigate.tmpl b/templates/navigate/navigate.tmpl new file mode 100644 index 00000000..57b6748b --- /dev/null +++ b/templates/navigate/navigate.tmpl @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:navme/navme.dart'; +import 'package:navme/helpers.dart'; + +import 'index.dart'; + +class ${upperName}Navigate { + // base path + static String path = '${privateName}'; + + // config for configurate Router + static RouteConfig routeConfig = RouteConfig( + state: (Uri? uri) => RouteState(uri: path.toUri()), + // condition for using this page + isThisPage: (RouteState state) { + if (state?.firstPath == path) { + return true; + } + return false; + }, + // settigs from url + settings: (RouteState state) { + return null; + }, + // get Page for Router + page: ({RouteState? state}) { + return MaterialPage( + key: const ValueKey('${upperName}Page'), + child: ${upperName}Page(), + name: '${upperName}Page'); + }, + ); +} diff --git a/templates/simple/bloc.tmpl b/templates/simple/bloc.tmpl new file mode 100644 index 00000000..3556967f --- /dev/null +++ b/templates/simple/bloc.tmpl @@ -0,0 +1,21 @@ +import 'dart:async'; +import 'dart:developer' as developer; + +import 'package:bloc/bloc.dart'; +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Bloc extends Bloc<${upperName}Event, ${upperName}State> { + + ${upperName}Bloc(${upperName}State initialState) : super(initialState){ + on<${upperName}Event>((event, emit) { + return emit.forEach<${upperName}State>( + event.applyAsync(currentState: state, bloc: this), + onData: (state) => state, + onError: (error, stackTrace) { + developer.log('$error', name: '${upperName}Bloc', error: error, stackTrace: stackTrace); + return Error${upperName}State(error.toString()); + }, + ); + }); + } +} diff --git a/templates/simple/event.tmpl b/templates/simple/event.tmpl new file mode 100644 index 00000000..d21542a2 --- /dev/null +++ b/templates/simple/event.tmpl @@ -0,0 +1,34 @@ +import 'dart:async'; +import 'dart:developer' as developer; + +import 'package:${appName}${relative}/index.dart'; +import 'package:meta/meta.dart'; + +@immutable +abstract class ${upperName}Event { + Stream<${upperName}State> applyAsync( + {${upperName}State currentState, ${upperName}Bloc bloc}); +} + +class Un${upperName}Event extends ${upperName}Event { + @override + Stream<${upperName}State> applyAsync({${upperName}State? currentState, ${upperName}Bloc? bloc}) async* { + yield Un${upperName}State(); + } +} + +class Load${upperName}Event extends ${upperName}Event { + + @override + Stream<${upperName}State> applyAsync( + {${upperName}State? currentState, ${upperName}Bloc? bloc}) async* { + try { + yield Un${upperName}State(); + await Future.delayed(const Duration(seconds: 1)); + yield In${upperName}State('Hello world'); + } catch (_, stackTrace) { + developer.log('$_', name: 'Load${upperName}Event', error: _, stackTrace: stackTrace); + yield Error${upperName}State( _.toString()); + } + } +} diff --git a/templates/simple/index.tmpl b/templates/simple/index.tmpl new file mode 100644 index 00000000..e69de29b diff --git a/templates/simple/model.tmpl b/templates/simple/model.tmpl new file mode 100644 index 00000000..ede30505 --- /dev/null +++ b/templates/simple/model.tmpl @@ -0,0 +1,13 @@ +import 'package:equatable/equatable.dart'; + +/// use https://marketplace.visualstudio.com/items?itemName=BendixMa.dart-data-class-generator +class ${upperName}Model extends Equatable { + final int id; + final String name; + + ${upperName}Model(this.id, this.name); + + @override + List get props => [id, name]; + +} diff --git a/templates/simple/page.tmpl b/templates/simple/page.tmpl new file mode 100644 index 00000000..169680f2 --- /dev/null +++ b/templates/simple/page.tmpl @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Page extends StatefulWidget { + static const String routeName = '/${privateName}'; + + @override + _${upperName}PageState createState() => _${upperName}PageState(); +} + +class _${upperName}PageState extends State<${upperName}Page> { + final _${privateName}Bloc = ${upperName}Bloc(Un${upperName}State()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('${upperName}'), + ), + body: ${upperName}Screen(${privateName}Bloc: _${privateName}Bloc), + ); + } +} diff --git a/templates/simple/provider.tmpl b/templates/simple/provider.tmpl new file mode 100644 index 00000000..7fe9c15d --- /dev/null +++ b/templates/simple/provider.tmpl @@ -0,0 +1,20 @@ +import 'dart:async'; + +class ${upperName}Provider { + Future loadAsync(String token) async { + /// write from keystore/keychain + await Future.delayed(Duration(seconds: 2)); + } + + Future saveAsync(String token) async { + /// write from keystore/keychain + await Future.delayed(Duration(seconds: 2)); + } + + void test(bool isError) { + if (isError == true){ + throw Exception('manual error'); + } + } +} + diff --git a/templates/simple/repository.tmpl b/templates/simple/repository.tmpl new file mode 100644 index 00000000..332f91dc --- /dev/null +++ b/templates/simple/repository.tmpl @@ -0,0 +1,11 @@ +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Repository { + final ${upperName}Provider _${privateName}Provider = ${upperName}Provider(); + + ${upperName}Repository(); + + void test(bool isError) { + _${privateName}Provider.test(isError); + } +} \ No newline at end of file diff --git a/templates/simple/screen.tmpl b/templates/simple/screen.tmpl new file mode 100644 index 00000000..dc592fec --- /dev/null +++ b/templates/simple/screen.tmpl @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:${appName}${relative}/index.dart'; + +class ${upperName}Screen extends StatefulWidget { + const ${upperName}Screen({ + required ${upperName}Bloc ${privateName}Bloc, + Key? key, + }) : _${privateName}Bloc = ${privateName}Bloc, + super(key: key); + + final ${upperName}Bloc _${privateName}Bloc; + + @override + ${upperName}ScreenState createState() { + return ${upperName}ScreenState(); + } +} + +class ${upperName}ScreenState extends State<${upperName}Screen> { + ${upperName}ScreenState(); + + @override + void initState() { + super.initState(); + _load(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder<${upperName}Bloc, ${upperName}State>( + bloc: widget._${privateName}Bloc, + builder: ( + BuildContext context, + ${upperName}State currentState, + ) { + if (currentState is Un${upperName}State) { + return Center( + child: CircularProgressIndicator(), + ); + } + if (currentState is Error${upperName}State) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(currentState.errorMessage ), + Padding( + padding: const EdgeInsets.only(top: 32.0), + child: RaisedButton( + color: Colors.blue, + child: Text('reload'), + onPressed: _load, + ), + ), + ], + )); + } + if (currentState is In${upperName}State) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(currentState.hello), + ], + ), + ); + } + return Center( + child: CircularProgressIndicator(), + ); + + }); + } + + void _load() { + widget._${privateName}Bloc.add(Load${upperName}Event()); + } +} diff --git a/templates/simple/state.tmpl b/templates/simple/state.tmpl new file mode 100644 index 00000000..be0c2ac0 --- /dev/null +++ b/templates/simple/state.tmpl @@ -0,0 +1,42 @@ +import 'package:equatable/equatable.dart'; + +abstract class ${upperName}State extends Equatable { + ${upperName}State(); + + @override + List get props => []; +} + +/// UnInitialized +class Un${upperName}State extends ${upperName}State { + + Un${upperName}State(); + + @override + String toString() => 'Un${upperName}State'; +} + +/// Initialized +class In${upperName}State extends ${upperName}State { + In${upperName}State(this.hello); + + final String hello; + + @override + String toString() => 'In${upperName}State $hello'; + + @override + List get props => [hello]; +} + +class Error${upperName}State extends ${upperName}State { + Error${upperName}State(this.errorMessage); + + final String errorMessage; + + @override + String toString() => 'Error${upperName}State'; + + @override + List get props => [errorMessage]; +}