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_NewCreate from "@/app_modules/forum/create/new_create";
export default async function Page() {
return (
<>
<Forum_Create />
{/* <Forum_Create /> */}
<Forum_NewCreate/>
</>
);
}

View File

@@ -12,9 +12,14 @@ export default function ComponentGlobal_InputCountDown({
}) {
return (
<>
<Text fz={"xs"} fs={"italic"} color="gray">
{maxInput - lengthInput < 0 ? 0 : maxInput - lengthInput} /{" "}
<Text span inherit c={maxInput - lengthInput < 0 ? "red" : ""} style={{transition: "0.5s"}}>
<Text fz={"sm"} fs={"italic"} color="gray">
{maxInput - lengthInput} /
<Text
span
inherit
c={maxInput - lengthInput < 0 ? "red" : ""}
style={{ transition: " all0.5s" }}
>
{maxInput}
</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 { MAX_SIZE } from "../../lib";
import { PemberitahuanMaksimalFile } from "../../lib/max_size";
import { PemberitahuanMaksimalFile } from "../../lib/maximal_setting";
import { ComponentGlobal_NotifikasiPeringatan } from "../../notif_global";
import { funDeteleteFileById } from "../delete/fun_delete_file_by_id";
import { funUploadFileToStorage } from "./fun_upload_to_storage";

View File

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

View File

@@ -1,3 +1,8 @@
// Maksimal ukuran file dalam byte (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";
import { Button, Group, Paper, Stack } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { useRouter } from "next/navigation";
import "react-quill/dist/quill.bubble.css";
// import "react-quill/dist/quill.bubble.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(
() => {
return import("react-quill");
@@ -19,27 +9,108 @@ const ReactQuill = dynamic(
{ ssr: false }
);
import {
MainColor
} from "@/app_modules/_global/color/color_pallet";
import { Button, Group, Paper, ScrollArea, Stack } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
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 mqtt_client from "@/util/mqtt_client";
const maxLength = 500;
const maxLength = 100;
export default function Forum_Create() {
const [value, setValue] = useState("");
const [totalLength, setTotalLength] = useState(0);
const [data, setData] = useState("");
const [lengthData, setLengthData] = useState(0);
const [reload, setReload] = useState(false);
useShallowEffect(() => {
if (window && window.document) setReload(true);
}, []);
if (!reload) return <CustomSkeleton height={200} />;
// if (!reload) return <CustomSkeleton height={200} />;
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>
<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"}>
<ReactQuill
theme="bubble"
@@ -49,28 +120,35 @@ export default function Forum_Create() {
const input = val;
const text = input.replace(/<[^>]+>/g, "").trim(); // Remove HTML tags and trim whitespace
if (text.length === 0) {
setValue(""); // Set value to an empty string
setData(""); // Set value to an empty string
return;
}
setValue(val);
setData(val);
}}
/>
</Paper>
<Group position="right">
<ComponentGlobal_InputCountDown
maxInput={maxLength}
lengthInput={value.length}
lengthInput={lengthData}
/>
</Group>
<Group position="right">
<ButtonAction value={value} />
<ButtonAction value={data} lengthData={lengthData} />
</Group>
</Stack>
</Stack> */}
</>
);
}
function ButtonAction({ value }: { value: string }) {
function ButtonAction({
value,
lengthData,
}: {
value: string;
lengthData: number;
}) {
const router = useRouter();
const [loading, setLoading] = useState(false);
@@ -97,16 +175,16 @@ function ButtonAction({ value }: { value: string }) {
<>
<Button
style={{
transition: "0.5s",
transition: "all 0.5s",
}}
bg={MainColor.yellow}
color={"yellow"}
c="black"
disabled={
value === "<p><br></p>" || value === "" || value.length >= maxLength
value === "<p><br></p>" || value === "" || lengthData >= maxLength
}
radius={"xl"}
loading={loading ? true : false}
loading={loading}
loaderPosition="center"
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";
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 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_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 { 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 { apiGetOneForumById } from "../../component/api_fetch_forum";
import { forum_funEditPostingById } from "../../fun/edit/fun_edit_posting_by_id";
import { MODEL_FORUM_POSTING } from "../../model/interface";
const ReactQuill = dynamic(
() => {
return import("react-quill");
},
{ ssr: false }
);
const maxLength = 500;
export default function Forum_EditPosting() {
const param = useParams<{ id: string }>();
const [data, setData] = useState<MODEL_FORUM_POSTING | null>(null);
const [reload, setReload] = useState(false);
useShallowEffect(() => {
if (window && window.document) setReload(true);
}, []);
const [lengthData, setLengthData] = useState<number>(0);
useShallowEffect(() => {
handleLoadData();
@@ -53,12 +40,26 @@ export default function Forum_EditPosting() {
}
};
if (!reload || !data) return <CustomSkeleton height={200} />;
if (!data) return <CustomSkeleton height={200} />;
return (
<>
<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
theme="bubble"
placeholder="Apa yang sedang ingin dibahas ?"
@@ -82,18 +83,20 @@ export default function Forum_EditPosting() {
});
}}
/>
</Paper>
<Group position="right">
</Paper> */}
<Group position="apart">
<ComponentGlobal_InputCountDown
maxInput={maxLength}
lengthInput={data.diskusi.length}
maxInput={maxInputLength}
lengthInput={funReplaceHtml({ html: data.diskusi }).length}
/>
<ButtonAction
diskusi={data.diskusi as any}
postingId={data.id}
lengthData={lengthData}
/>
</Group>
<Group position="right">
<ButtonAction diskusi={data.diskusi as any} postingId={data.id} />
</Group>
</Stack>
{/* <div dangerouslySetInnerHTML={{ __html: value.diskusi }} /> */}
</>
);
}
@@ -101,16 +104,18 @@ export default function Forum_EditPosting() {
function ButtonAction({
postingId,
diskusi,
lengthData,
}: {
postingId: string;
diskusi: string;
lengthData: number;
}) {
const router = useRouter();
const [loading, setLoading] = useState(false);
async function onUpdate() {
if (diskusi === "<p><br></p>" || diskusi === "")
return ComponentGlobal_NotifikasiPeringatan("Masukan postingan anda");
// if (diskusi === "<p><br></p>" || diskusi === "")
// return ComponentGlobal_NotifikasiPeringatan("Masukan postingan anda");
try {
setLoading(true);
@@ -134,23 +139,13 @@ function ButtonAction({
<Button
style={{
transition: "0.5s",
backgroundColor:
diskusi === "<p><br></p>" ||
diskusi === "" ||
diskusi.length >= maxLength
? ""
: MainColor.yellow,
}}
disabled={
diskusi === "<p><br></p>" ||
diskusi === "" ||
diskusi.length >= maxLength
? true
: false
}
disabled={lengthData === 0 || lengthData > maxInputLength}
loaderPosition="center"
loading={loading}
radius={"xl"}
bg={MainColor.yellow}
color={"yellow"}
c={"black"}
onClick={() => {
onUpdate();

View File

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

View File

@@ -13,7 +13,7 @@ import {
funGlobal_UploadToStorage,
} from "@/app_modules/_global/fun";
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 { BIDANG_BISNIS_OLD } from "@/app_modules/model_global/portofolio";
import {

View File

@@ -35,7 +35,7 @@ import { ComponentMap_ButtonUpdateDataMap } from "../_component";
import { defaultMapZoom } from "../lib/default_lat_long";
import { MODEL_MAP } from "../lib/interface";
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({
mapboxToken,