fix forum

deskripsi:
- text editor di create & edit
- load pada beranda
This commit is contained in:
2025-04-22 15:36:22 +08:00
parent d30e821258
commit cf84daedba
13 changed files with 330 additions and 86 deletions

View File

@@ -1,9 +1,11 @@
import { Forum_Create } from "@/app_modules/forum"; import { Forum_Create } from "@/app_modules/forum";
import Forum_NewCreate from "@/app_modules/forum/create/new_create";
export default async function Page() { export default async function Page() {
return ( return (
<> <>
<Forum_Create /> {/* <Forum_Create /> */}
<Forum_NewCreate/>
</> </>
); );
} }

View File

@@ -12,9 +12,14 @@ export default function ComponentGlobal_InputCountDown({
}) { }) {
return ( return (
<> <>
<Text fz={"xs"} fs={"italic"} color="gray"> <Text fz={"sm"} fs={"italic"} color="gray">
{maxInput - lengthInput < 0 ? 0 : maxInput - lengthInput} /{" "} {maxInput - lengthInput} /
<Text span inherit c={maxInput - lengthInput < 0 ? "red" : ""} style={{transition: "0.5s"}}> <Text
span
inherit
c={maxInput - lengthInput < 0 ? "red" : ""}
style={{ transition: " all0.5s" }}
>
{maxInput} {maxInput}
</Text> </Text>
</Text> </Text>

View File

@@ -0,0 +1,69 @@
import { useState } from "react";
import "react-quill/dist/quill.snow.css";
import dynamic from "next/dynamic";
import { useShallowEffect } from "@mantine/hooks";
import CustomSkeleton from "@/app_modules/components/CustomSkeleton";
import { Paper, ScrollArea } from "@mantine/core";
import { MainColor } from "../../color";
import { maxInputLength } from "../../lib/maximal_setting";
const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });
export function ComponentTextEditor({
data,
onSetData,
// lengthData,
onSetLengthData,
}: {
data: string;
onSetData: (value: string) => void;
// lengthData: number;
onSetLengthData: (value: number) => void;
}) {
const [isReady, setIsReady] = useState<boolean>(false);
useShallowEffect(() => {
setIsReady(true); // Set ready on client-side mount
}, []);
const handleChange = (input: string) => {
const text = input.replace(/<[^>]+>/g, "").trim(); // Remove HTML tags and trim
// if (text.length <= maxInputLength) {
// }
onSetData(input);
onSetLengthData(text.length);
// Input diabaikan jika panjang > maxLength
};
return (
<>
{isReady ? (
<Paper p="sm" withBorder shadow="lg" mah={300} bg={MainColor.white} >
<ScrollArea h={280}>
<ReactQuill
placeholder="Apa yang sedang ingin dibahas ?"
style={{
color: "black",
backgroundColor: MainColor.white,
border: "none",
}}
modules={{
toolbar: [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
["bold", "italic", "underline", "link"],
[{ list: "ordered" }, { list: "bullet" }],
["clean"],
],
}}
theme="snow"
value={data}
onChange={handleChange}
/>
</ScrollArea>
</Paper>
) : (
<CustomSkeleton height={200} />
)}
</>
);
}

View File

@@ -0,0 +1,3 @@
export function funReplaceHtml({html}:{ html: string }) {
return html.replace(/<[^>]+>/g, "");
}

View File

@@ -1,6 +1,6 @@
import { clientLogger } from "@/util/clientLogger"; import { clientLogger } from "@/util/clientLogger";
import { MAX_SIZE } from "../../lib"; import { MAX_SIZE } from "../../lib";
import { PemberitahuanMaksimalFile } from "../../lib/max_size"; import { PemberitahuanMaksimalFile } from "../../lib/maximal_setting";
import { ComponentGlobal_NotifikasiPeringatan } from "../../notif_global"; import { ComponentGlobal_NotifikasiPeringatan } from "../../notif_global";
import { funDeteleteFileById } from "../delete/fun_delete_file_by_id"; import { funDeteleteFileById } from "../delete/fun_delete_file_by_id";
import { funUploadFileToStorage } from "./fun_upload_to_storage"; import { funUploadFileToStorage } from "./fun_upload_to_storage";

View File

@@ -1,5 +1,5 @@
import { globalStatusApp } from "./master_list_app"; import { globalStatusApp } from "./master_list_app";
import { MAX_SIZE } from "./max_size"; import { MAX_SIZE } from "./maximal_setting";
export { MAX_SIZE }; export { MAX_SIZE };
export { globalStatusApp }; export { globalStatusApp };

View File

@@ -1,3 +1,8 @@
// Maksimal ukuran file dalam byte (3 MB) // Maksimal ukuran file dalam byte (3 MB)
export const MAX_SIZE = 3 * 1024 * 1024; // 3 MB export const MAX_SIZE = 3 * 1024 * 1024; // 3 MB
export const PemberitahuanMaksimalFile = "Ukuran file terlalu besar. Maksimal 3 MB."; export const PemberitahuanMaksimalFile = "Ukuran file terlalu besar. Maksimal 3 MB.";
/**
* Maksimal panjang input 1000 karakter
*/
export const maxInputLength = 1000;

View File

@@ -1,17 +1,7 @@
"use client"; "use client";
import { Button, Group, Paper, Stack } from "@mantine/core"; // import "react-quill/dist/quill.bubble.css";
import { useShallowEffect } from "@mantine/hooks";
import { useRouter } from "next/navigation";
import "react-quill/dist/quill.bubble.css";
import "react-quill/dist/quill.snow.css"; import "react-quill/dist/quill.snow.css";
import ComponentGlobal_InputCountDown from "@/app_modules/_global/component/input_countdown";
import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global/notifikasi_berhasil";
import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/_global/notif_global/notifikasi_gagal";
import dynamic from "next/dynamic";
import { useState } from "react";
import { forum_funCreate } from "../fun/create/fun_create";
const ReactQuill = dynamic( const ReactQuill = dynamic(
() => { () => {
return import("react-quill"); return import("react-quill");
@@ -19,27 +9,108 @@ const ReactQuill = dynamic(
{ ssr: false } { ssr: false }
); );
import { import { Button, Group, Paper, ScrollArea, Stack } from "@mantine/core";
MainColor import { useShallowEffect } from "@mantine/hooks";
} from "@/app_modules/_global/color/color_pallet"; import { useRouter } from "next/navigation";
import ComponentGlobal_InputCountDown from "@/app_modules/_global/component/input_countdown";
import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global/notifikasi_berhasil";
import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/_global/notif_global/notifikasi_gagal";
import dynamic from "next/dynamic";
import { useState } from "react";
import { forum_funCreate } from "../fun/create/fun_create";
import { MainColor } from "@/app_modules/_global/color/color_pallet";
import CustomSkeleton from "@/app_modules/components/CustomSkeleton"; import CustomSkeleton from "@/app_modules/components/CustomSkeleton";
import mqtt_client from "@/util/mqtt_client"; import mqtt_client from "@/util/mqtt_client";
const maxLength = 500; const maxLength = 100;
export default function Forum_Create() { export default function Forum_Create() {
const [value, setValue] = useState(""); const [data, setData] = useState("");
const [totalLength, setTotalLength] = useState(0); const [lengthData, setLengthData] = useState(0);
const [reload, setReload] = useState(false); const [reload, setReload] = useState(false);
useShallowEffect(() => { useShallowEffect(() => {
if (window && window.document) setReload(true); if (window && window.document) setReload(true);
}, []); }, []);
if (!reload) return <CustomSkeleton height={200} />; // if (!reload) return <CustomSkeleton height={200} />;
return ( return (
<> <>
{/* <ReactQuill
theme="snow"
placeholder="Apa yang sedang ingin dibahas ?"
style={{
color: "black",
backgroundColor: "white",
height: 200,
}}
onChange={(input) => {
const longLength = input.replace(/<[^>]+>/g, "").trim(); // Remove HTML tags and trim whitespace
if (longLength.length === 0) {
setData(""); // Set value to an empty string
return;
}
setData(input);
setLengthData(longLength.length);
}}
modules={{
toolbar: [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
["bold", "italic", "underline", "link"],
// [{ align: [] }],
[{ list: "ordered" }, { list: "bullet" }],
["clean"],
],
}}
/> */}
<Stack> <Stack>
<Paper p={"sm"} withBorder shadow="lg" mah={200}>
<ScrollArea h={180}>
{!reload ? (
<CustomSkeleton height={100} />
) : (
<ReactQuill
placeholder={"Apa yang sedang ingin dibahas ?"}
style={{
color: "black",
backgroundColor: "white",
border: "none",
}}
modules={{
toolbar: [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
["bold", "italic", "underline", "link"],
// [{ align: [] }],
[{ list: "ordered" }, { list: "bullet" }],
["clean"],
],
}}
theme="snow"
onChange={(input) => {
const text = input.replace(/<[^>]+>/g, "").trim(); // Remove HTML tags and trim whitespace
if(text.length > 100){
}
setData(input);
setLengthData(text.length);
}}
/>
)}
</ScrollArea>
</Paper>
<ComponentGlobal_InputCountDown
maxInput={maxLength}
lengthInput={lengthData}
/>
</Stack>
{/* <Stack>
<Paper withBorder shadow="lg" p={"xs"}> <Paper withBorder shadow="lg" p={"xs"}>
<ReactQuill <ReactQuill
theme="bubble" theme="bubble"
@@ -49,28 +120,35 @@ export default function Forum_Create() {
const input = val; const input = val;
const text = input.replace(/<[^>]+>/g, "").trim(); // Remove HTML tags and trim whitespace const text = input.replace(/<[^>]+>/g, "").trim(); // Remove HTML tags and trim whitespace
if (text.length === 0) { if (text.length === 0) {
setValue(""); // Set value to an empty string setData(""); // Set value to an empty string
return; return;
} }
setValue(val); setData(val);
}} }}
/> />
</Paper> </Paper>
<Group position="right"> <Group position="right">
<ComponentGlobal_InputCountDown <ComponentGlobal_InputCountDown
maxInput={maxLength} maxInput={maxLength}
lengthInput={value.length} lengthInput={lengthData}
/> />
</Group> </Group>
<Group position="right"> <Group position="right">
<ButtonAction value={value} /> <ButtonAction value={data} lengthData={lengthData} />
</Group> </Group>
</Stack> </Stack> */}
</> </>
); );
} }
function ButtonAction({ value }: { value: string }) { function ButtonAction({
value,
lengthData,
}: {
value: string;
lengthData: number;
}) {
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -97,16 +175,16 @@ function ButtonAction({ value }: { value: string }) {
<> <>
<Button <Button
style={{ style={{
transition: "0.5s", transition: "all 0.5s",
}} }}
bg={MainColor.yellow} bg={MainColor.yellow}
color={"yellow"} color={"yellow"}
c="black" c="black"
disabled={ disabled={
value === "<p><br></p>" || value === "" || value.length >= maxLength value === "<p><br></p>" || value === "" || lengthData >= maxLength
} }
radius={"xl"} radius={"xl"}
loading={loading ? true : false} loading={loading}
loaderPosition="center" loaderPosition="center"
onClick={() => onCreate()} onClick={() => onCreate()}
> >

View File

@@ -0,0 +1,89 @@
"use client";
import { MainColor } from "@/app_modules/_global/color/color_pallet";
import ComponentGlobal_InputCountDown from "@/app_modules/_global/component/input_countdown";
import { ComponentTextEditor } from "@/app_modules/_global/component/new/new_text_editor";
import { maxInputLength } from "@/app_modules/_global/lib/maximal_setting";
import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global/notifikasi_berhasil";
import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/_global/notif_global/notifikasi_gagal";
import mqtt_client from "@/util/mqtt_client";
import { Button, Group, Stack } from "@mantine/core";
import { useRouter } from "next/navigation";
import { useState } from "react";
import "react-quill/dist/quill.snow.css";
import { forum_funCreate } from "../fun/create/fun_create";
export default function Forum_NewCreate() {
const [data, setData] = useState<string>("");
const [lengthData, setLengthData] = useState<number>(0);
return (
<Stack>
<ComponentTextEditor
data={data}
onSetData={(val) => {
setData(val);
}}
onSetLengthData={(val) => {
setLengthData(val);
}}
/>
<Group position="apart">
<ComponentGlobal_InputCountDown
maxInput={maxInputLength}
lengthInput={lengthData}
/>
<ButtonAction value={data} lengthData={lengthData} />
</Group>
</Stack>
);
}
interface ButtonActionProps {
value: string;
lengthData: number;
}
function ButtonAction({ value, lengthData }: ButtonActionProps) {
const router = useRouter();
const [loading, setLoading] = useState<boolean>(false);
async function onCreate() {
try {
setLoading(true);
const create = await forum_funCreate(value);
if (create.status === 201) {
ComponentGlobal_NotifikasiBerhasil(create.message);
router.back();
mqtt_client.publish(
"Forum_create_new",
JSON.stringify({ isNewPost: true, count: 1 })
);
} else {
ComponentGlobal_NotifikasiGagal(create.message);
}
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
}
return (
<Button
style={{ transition: "all 0.5s" }}
bg={MainColor.yellow}
color="yellow"
c="black"
disabled={lengthData === 0 || lengthData > maxInputLength}
radius="xl"
loading={loading}
loaderPosition="center"
onClick={onCreate}
>
Posting
</Button>
);
}

View File

@@ -1,39 +1,26 @@
"use client"; "use client";
import { Button, Group, Paper, Stack } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { useParams, useRouter } from "next/navigation";
import "react-quill/dist/quill.bubble.css";
import "react-quill/dist/quill.snow.css";
import { MainColor } from "@/app_modules/_global/color/color_pallet"; import { MainColor } from "@/app_modules/_global/color/color_pallet";
import ComponentGlobal_InputCountDown from "@/app_modules/_global/component/input_countdown"; import ComponentGlobal_InputCountDown from "@/app_modules/_global/component/input_countdown";
import { ComponentTextEditor } from "@/app_modules/_global/component/new/new_text_editor";
import { funReplaceHtml } from "@/app_modules/_global/fun/fun_replace_html";
import { maxInputLength } from "@/app_modules/_global/lib/maximal_setting";
import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global/notifikasi_berhasil"; import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global/notifikasi_berhasil";
import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/_global/notif_global/notifikasi_gagal"; import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/_global/notif_global/notifikasi_gagal";
import { ComponentGlobal_NotifikasiPeringatan } from "@/app_modules/_global/notif_global/notifikasi_peringatan";
import CustomSkeleton from "@/app_modules/components/CustomSkeleton"; import CustomSkeleton from "@/app_modules/components/CustomSkeleton";
import { clientLogger } from "@/util/clientLogger"; import { clientLogger } from "@/util/clientLogger";
import dynamic from "next/dynamic"; import { Button, Group, Stack } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { apiGetOneForumById } from "../../component/api_fetch_forum"; import { apiGetOneForumById } from "../../component/api_fetch_forum";
import { forum_funEditPostingById } from "../../fun/edit/fun_edit_posting_by_id"; import { forum_funEditPostingById } from "../../fun/edit/fun_edit_posting_by_id";
import { MODEL_FORUM_POSTING } from "../../model/interface"; import { MODEL_FORUM_POSTING } from "../../model/interface";
const ReactQuill = dynamic(
() => {
return import("react-quill");
},
{ ssr: false }
);
const maxLength = 500;
export default function Forum_EditPosting() { export default function Forum_EditPosting() {
const param = useParams<{ id: string }>(); const param = useParams<{ id: string }>();
const [data, setData] = useState<MODEL_FORUM_POSTING | null>(null); const [data, setData] = useState<MODEL_FORUM_POSTING | null>(null);
const [reload, setReload] = useState(false); const [lengthData, setLengthData] = useState<number>(0);
useShallowEffect(() => {
if (window && window.document) setReload(true);
}, []);
useShallowEffect(() => { useShallowEffect(() => {
handleLoadData(); handleLoadData();
@@ -53,12 +40,26 @@ export default function Forum_EditPosting() {
} }
}; };
if (!reload || !data) return <CustomSkeleton height={200} />; if (!data) return <CustomSkeleton height={200} />;
return ( return (
<> <>
<Stack> <Stack>
<Paper withBorder shadow="lg" p={"xs"}> <ComponentTextEditor
data={data.diskusi}
onSetData={(value) => {
setData({
...data,
diskusi: value,
});
}}
onSetLengthData={(value) => {
console.log(value);
setLengthData(value);
}}
/>
{/* <Paper withBorder shadow="lg" p={"xs"}>
<ReactQuill <ReactQuill
theme="bubble" theme="bubble"
placeholder="Apa yang sedang ingin dibahas ?" placeholder="Apa yang sedang ingin dibahas ?"
@@ -82,18 +83,20 @@ export default function Forum_EditPosting() {
}); });
}} }}
/> />
</Paper> </Paper> */}
<Group position="right">
<Group position="apart">
<ComponentGlobal_InputCountDown <ComponentGlobal_InputCountDown
maxInput={maxLength} maxInput={maxInputLength}
lengthInput={data.diskusi.length} lengthInput={funReplaceHtml({ html: data.diskusi }).length}
/>
<ButtonAction
diskusi={data.diskusi as any}
postingId={data.id}
lengthData={lengthData}
/> />
</Group> </Group>
<Group position="right">
<ButtonAction diskusi={data.diskusi as any} postingId={data.id} />
</Group>
</Stack> </Stack>
{/* <div dangerouslySetInnerHTML={{ __html: value.diskusi }} /> */}
</> </>
); );
} }
@@ -101,16 +104,18 @@ export default function Forum_EditPosting() {
function ButtonAction({ function ButtonAction({
postingId, postingId,
diskusi, diskusi,
lengthData,
}: { }: {
postingId: string; postingId: string;
diskusi: string; diskusi: string;
lengthData: number;
}) { }) {
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
async function onUpdate() { async function onUpdate() {
if (diskusi === "<p><br></p>" || diskusi === "") // if (diskusi === "<p><br></p>" || diskusi === "")
return ComponentGlobal_NotifikasiPeringatan("Masukan postingan anda"); // return ComponentGlobal_NotifikasiPeringatan("Masukan postingan anda");
try { try {
setLoading(true); setLoading(true);
@@ -134,23 +139,13 @@ function ButtonAction({
<Button <Button
style={{ style={{
transition: "0.5s", transition: "0.5s",
backgroundColor:
diskusi === "<p><br></p>" ||
diskusi === "" ||
diskusi.length >= maxLength
? ""
: MainColor.yellow,
}} }}
disabled={ disabled={lengthData === 0 || lengthData > maxInputLength}
diskusi === "<p><br></p>" ||
diskusi === "" ||
diskusi.length >= maxLength
? true
: false
}
loaderPosition="center" loaderPosition="center"
loading={loading} loading={loading}
radius={"xl"} radius={"xl"}
bg={MainColor.yellow}
color={"yellow"}
c={"black"} c={"black"}
onClick={() => { onClick={() => {
onUpdate(); onUpdate();

View File

@@ -3,6 +3,7 @@
import ComponentGlobal_CreateButton from "@/app_modules/_global/component/button_create"; import ComponentGlobal_CreateButton from "@/app_modules/_global/component/button_create";
import { RouterForum } from "@/lib/router_hipmi/router_forum"; import { RouterForum } from "@/lib/router_hipmi/router_forum";
import { clientLogger } from "@/util/clientLogger"; import { clientLogger } from "@/util/clientLogger";
import mqtt_client from "@/util/mqtt_client";
import { import {
Affix, Affix,
Box, Box,
@@ -22,8 +23,6 @@ import ComponentForum_BerandaCardView from "../component/main_component/card_vie
import { Forum_ComponentIsDataEmpty } from "../component/other_component"; import { Forum_ComponentIsDataEmpty } from "../component/other_component";
import { Forum_SkeletonCard } from "../component/skeleton_view"; import { Forum_SkeletonCard } from "../component/skeleton_view";
import { MODEL_FORUM_POSTING } from "../model/interface"; import { MODEL_FORUM_POSTING } from "../model/interface";
import mqtt_client from "@/util/mqtt_client";
import { AccentColor } from "@/app_modules/_global/color";
export default function Forum_Beranda({ export default function Forum_Beranda({
userLoginId, userLoginId,
@@ -174,14 +173,13 @@ export default function Forum_Beranda({
}} }}
/> />
{!data.length && isLoading ? ( {!data.length || isLoading ? (
<Forum_SkeletonCard /> <Forum_SkeletonCard />
) : _.isEmpty(data) ? ( ) : _.isEmpty(data) ? (
<Forum_ComponentIsDataEmpty /> <Forum_ComponentIsDataEmpty />
) : ( ) : (
// --- Main component --- // // --- Main component --- //
<Box <Box>
>
<ScrollOnly <ScrollOnly
height="80vh" height="80vh"
renderLoading={() => ( renderLoading={() => (

View File

@@ -13,7 +13,7 @@ import {
funGlobal_UploadToStorage, funGlobal_UploadToStorage,
} from "@/app_modules/_global/fun"; } from "@/app_modules/_global/fun";
import { MAX_SIZE } from "@/app_modules/_global/lib"; import { MAX_SIZE } from "@/app_modules/_global/lib";
import { PemberitahuanMaksimalFile } from "@/app_modules/_global/lib/max_size"; import { PemberitahuanMaksimalFile } from "@/app_modules/_global/lib/maximal_setting";
import { ComponentGlobal_NotifikasiPeringatan } from "@/app_modules/_global/notif_global"; import { ComponentGlobal_NotifikasiPeringatan } from "@/app_modules/_global/notif_global";
import { BIDANG_BISNIS_OLD } from "@/app_modules/model_global/portofolio"; import { BIDANG_BISNIS_OLD } from "@/app_modules/model_global/portofolio";
import { import {

View File

@@ -35,7 +35,7 @@ import { ComponentMap_ButtonUpdateDataMap } from "../_component";
import { defaultMapZoom } from "../lib/default_lat_long"; import { defaultMapZoom } from "../lib/default_lat_long";
import { MODEL_MAP } from "../lib/interface"; import { MODEL_MAP } from "../lib/interface";
import { MAX_SIZE } from "@/app_modules/_global/lib"; import { MAX_SIZE } from "@/app_modules/_global/lib";
import { PemberitahuanMaksimalFile } from "@/app_modules/_global/lib/max_size"; import { PemberitahuanMaksimalFile } from "@/app_modules/_global/lib/maximal_setting";
export function UiMap_EditMap({ export function UiMap_EditMap({
mapboxToken, mapboxToken,