QC Voting

- # fix
- Beranda dan mandatori input
## No issuee
This commit is contained in:
2024-05-21 15:24:37 +08:00
parent dbeb740d1f
commit ce35aa8f51
32 changed files with 568 additions and 234 deletions

View File

@@ -23,6 +23,9 @@ export default function ComponentVote_HeaderTamplate({
bg?: any;
}) {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [isRightLoading, setRightLoading] = useState(false);
return (
<>
<Header
@@ -35,8 +38,10 @@ export default function ComponentVote_HeaderTamplate({
<ActionIcon variant="transparent" disabled></ActionIcon>
) : (
<ActionIcon
loading={isLoading ? true : false}
variant="transparent"
onClick={() => {
setIsLoading(true);
if (route === null || route === undefined) {
return router.back();
} else {
@@ -54,8 +59,13 @@ export default function ComponentVote_HeaderTamplate({
} else {
return (
<ActionIcon
loading={isRightLoading ? true : false}
variant="transparent"
onClick={() => router.push(route2)}
onClick={() => {
setRightLoading;
true;
router.push(route2);
}}
>
{icon}
</ActionIcon>

View File

@@ -5,7 +5,7 @@ import { Center } from "@mantine/core";
export default function ComponentVote_IsEmptyData({ text }: { text: string }) {
return (
<>
<Center h={"50vh"} fz={"sm"} fw={"bold"}>
<Center h={"50vh"} fz={"sm"} fw={"bold"} c={"gray"}>
{text}
</Center>
</>

View File

@@ -0,0 +1,22 @@
import { Center, Grid, Group, Paper, Text, Title } from "@mantine/core";
export default function ComponentVote_NotedBox({
informasi,
}: {
informasi: string;
}) {
return (
<>
<Paper bg={"blue.3"} p={10}>
<Group>
<Text fz={10} fs={"italic"}>
<Text span inherit c={"red"}>
Alasan:{" "}
</Text>
{informasi}
</Text>
</Group>
</Paper>
</>
);
}

View File

@@ -29,9 +29,11 @@ import { data } from "autoprefixer";
import { Vote_funCreate } from "../fun/create/create_vote";
import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/component_global/notif_global/notifikasi_gagal";
import { MODEL_VOTING } from "../model/interface";
import ComponentGlobal_InputCountDown from "@/app_modules/component_global/input_countdown";
export default function Vote_Create() {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [hotMenu, setHotMenu] = useAtom(gs_vote_hotMenu);
const [tabsStatus, setTabsStatus] = useAtom(gs_vote_status);
@@ -47,11 +49,11 @@ export default function Vote_Create() {
const [listVote, setListVote] = useState([
{
name: "Nama Voting",
name: "Nama Pilihan",
value: "",
},
{
name: "Nama Voting",
name: "Nama Pilihan",
value: "",
},
]);
@@ -64,6 +66,7 @@ export default function Vote_Create() {
label="Judul"
withAsterisk
placeholder="Masukan judul"
maxLength={100}
onChange={(val) => {
setData({
...data,
@@ -71,20 +74,27 @@ export default function Vote_Create() {
});
}}
/>
<Textarea
label="Deskripsi"
autosize
minRows={2}
maxRows={5}
withAsterisk
placeholder="Masukan deskripsi"
onChange={(val) => {
setData({
...data,
deskripsi: val.target.value,
});
}}
/>
<Stack spacing={5}>
<Textarea
label="Deskripsi"
autosize
minRows={2}
maxRows={5}
withAsterisk
maxLength={300}
placeholder="Masukan deskripsi"
onChange={(val) => {
setData({
...data,
deskripsi: val.target.value,
});
}}
/>
<ComponentGlobal_InputCountDown
maxInput={300}
lengthInput={data.deskripsi.length}
/>
</Stack>
<DatePickerInput
label="Jangka Waktu"
@@ -108,7 +118,7 @@ export default function Vote_Create() {
<Stack spacing={0}>
<Center>
<Text fw={"bold"} fz={"sm"}>
Daftar Voting
Daftar Pilihan
</Text>
</Center>
@@ -119,6 +129,7 @@ export default function Vote_Create() {
<TextInput
label={e.name}
withAsterisk
maxLength={100}
placeholder="Nama pilihan voting"
onChange={(v) => {
const val = _.clone(listVote);
@@ -131,60 +142,55 @@ export default function Vote_Create() {
</Stack>
<Group position="center">
{listVote.length >= 4 ? (
""
) : (
<Button
compact
w={100}
radius={"xl"}
leftIcon={<IconPlus size={15} />}
variant="outline"
onClick={() => {
// if (listVote.length >= 4)
// return ComponentGlobal_NotifikasiPeringatan(
// "Daftar Voting Maksimal 4"
// );
setListVote([
...listVote,
{ name: "Nama Voting", value: "" },
]);
}}
>
<Text fz={8}>Tambah List</Text>
</Button>
)}
<Button
disabled={listVote.length >= 4 ? true : false}
compact
w={100}
radius={"xl"}
leftIcon={<IconPlus size={15} />}
variant="outline"
onClick={() => {
setListVote([
...listVote,
{ name: "Nama Voting", value: "" },
]);
}}
>
<Text fz={8}>Tambah List</Text>
</Button>
{listVote.length <= 2 ? (
""
) : (
<Button
compact
w={100}
radius={"xl"}
leftIcon={<IconMinus size={15} />}
variant="outline"
onClick={() => {
if (listVote.length <= 2)
return ComponentGlobal_NotifikasiPeringatan(
"Daftar Voting Minimal 2"
);
setListVote([...listVote.slice(0, -1)]);
}}
>
<Text fz={8}>Kurangi List</Text>
</Button>
)}
<Button
disabled={listVote.length <= 2 ? true : false}
compact
w={100}
radius={"xl"}
leftIcon={<IconMinus size={15} />}
variant="outline"
onClick={() => {
setListVote([...listVote.slice(0, -1)]);
}}
>
<Text fz={8}>Kurangi List</Text>
</Button>
</Group>
</Stack>
</Stack>
<Button
// disabled
loaderPosition="center"
loading={isLoading ? true : false}
mt={"lg"}
radius={"xl"}
onClick={() => {
onSave(router, setHotMenu, setTabsStatus, data as any, listVote);
onSave(
router,
setHotMenu,
setTabsStatus,
data as any,
listVote,
setIsLoading
);
}}
>
Simpan
@@ -199,7 +205,8 @@ async function onSave(
setHotMenu: any,
setTabsStatus: any,
data: MODEL_VOTING,
listVote: any[]
listVote: any[],
setIsLoading: any
) {
if (_.values(data).includes(""))
return ComponentGlobal_NotifikasiPeringatan("Lengkapi Data");
@@ -213,7 +220,9 @@ async function onSave(
return ComponentGlobal_NotifikasiPeringatan("Lengkapi Tanggal");
if (_.values(listVote.map((e) => e.value)).includes(""))
return ComponentGlobal_NotifikasiPeringatan("Isi Semua Nama Voting");
return ComponentGlobal_NotifikasiPeringatan("Lengkapi Pilihan Voting");
// console.log("berhasil");
await Vote_funCreate(data, listVote).then((res) => {
if (res.status === 201) {
@@ -221,6 +230,7 @@ async function onSave(
setTabsStatus("Review");
router.replace(RouterVote.status);
ComponentGlobal_NotifikasiBerhasil(res.message);
setIsLoading(true);
} else {
ComponentGlobal_NotifikasiGagal(res.message);
}

View File

@@ -12,6 +12,7 @@ import { Vote_funEditStatusByStatusId } from "../../fun/edit/fun_edit_status_by_
import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/component_global/notif_global/notifikasi_gagal";
import { Vote_funDeleteById } from "../../fun/delete/fun_delete_by_id";
import { ComponentGlobal_NotifikasiPeringatan } from "@/app_modules/component_global/notif_global/notifikasi_peringatan";
import moment from "moment";
export default function Vote_DetailDraft({
dataVote,
@@ -41,7 +42,10 @@ function ButtonAction({
async function onUpdate() {
const hariIni = new Date();
if (awalVote < hariIni) return ComponentGlobal_NotifikasiPeringatan("Tanggal Voting Lewat");
const cekHari = moment(awalVote).diff(hariIni, "days");
if (cekHari < 0)
return ComponentGlobal_NotifikasiPeringatan("Tanggal Voting Lewat");
await Vote_funEditStatusByStatusId(voteId, "2").then((res) => {
if (res.status === 200) {

View File

@@ -19,6 +19,7 @@ import { MODEL_VOTING } from "../../model/interface";
import { Vote_funDeleteById } from "../../fun/delete/fun_delete_by_id";
import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/component_global/notif_global/notifikasi_gagal";
import { Vote_funEditStatusByStatusId } from "../../fun/edit/fun_edit_status_by_id";
import ComponentVote_NotedBox from "../../component/noted_box";
export default function Vote_DetailReject({
dataVote,
@@ -28,6 +29,7 @@ export default function Vote_DetailReject({
return (
<>
<Stack spacing={"xl"}>
<ComponentVote_NotedBox informasi={dataVote?.catatan} />
<ComponentVote_DetailDataSebelumPublish data={dataVote as any} />
<ButtonAction voteId={dataVote.id} />
</Stack>

View File

@@ -22,6 +22,7 @@ import ComponentVote_DetailDataSebelumPublish from "../../component/detail/detai
import { Vote_funEditStatusByStatusId } from "../../fun/edit/fun_edit_status_by_id";
import { MODEL_VOTING } from "../../model/interface";
import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/component_global/notif_global/notifikasi_gagal";
import { useState } from "react";
export default function Vote_DetailReview({
dataVote,
@@ -40,6 +41,7 @@ export default function Vote_DetailReview({
function ButtonAction({ voteId }: { voteId: string }) {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [tabsStatus, setTabsStatus] = useAtom(gs_vote_status);
async function onUpdate() {
@@ -48,6 +50,7 @@ function ButtonAction({ voteId }: { voteId: string }) {
setTabsStatus("Draft");
ComponentGlobal_NotifikasiBerhasil("Berhasil Batalkan Review", 2000);
router.back();
setIsLoading(true);
} else {
ComponentGlobal_NotifikasiGagal(res.message);
}
@@ -56,9 +59,11 @@ function ButtonAction({ voteId }: { voteId: string }) {
return (
<>
<Button
loaderPosition="center"
loading={isLoading ? true : false}
radius={"xl"}
color="red"
onClick={() => {
color="orange"
onClick={() => {8
onUpdate();
}}
>

View File

@@ -2,6 +2,7 @@
import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/component_global/notif_global/notifikasi_berhasil";
import {
ActionIcon,
Box,
Button,
Center,
@@ -15,7 +16,7 @@ import {
} from "@mantine/core";
import { DatePickerInput } from "@mantine/dates";
import { useCounter } from "@mantine/hooks";
import { IconHome, IconPlus } from "@tabler/icons-react";
import { IconHome, IconMinus, IconPlus, IconTrash } from "@tabler/icons-react";
import { useAtom } from "jotai";
import moment from "moment";
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
@@ -27,9 +28,12 @@ import {
MODEL_VOTING,
MODEL_VOTING_DAFTAR_NAMA_VOTE,
} from "../model/interface";
import _ from "lodash";
import _, { slice } from "lodash";
import { Vote_funEditById } from "../fun/edit/fun_edit_by_id";
import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/component_global/notif_global/notifikasi_gagal";
import ComponentGlobal_InputCountDown from "@/app_modules/component_global/input_countdown";
import ComponentGlobal_ErrorInput from "@/app_modules/component_global/error_input";
import { ComponentGlobal_NotifikasiPeringatan } from "@/app_modules/component_global/notif_global/notifikasi_peringatan";
export default function Vote_Edit({
dataVote,
@@ -39,7 +43,7 @@ export default function Vote_Edit({
listDaftarVote: MODEL_VOTING_DAFTAR_NAMA_VOTE[];
}) {
const [data, setData] = useState(dataVote);
const [listVoting, setListVoting] = useState(listDaftarVote);
const [pilihanNama, setPilihanNama] = useState(listDaftarVote);
return (
<>
@@ -47,8 +51,16 @@ export default function Vote_Edit({
<TextInput
label="Judul"
withAsterisk
placeholder="Masukan judul"
placeholder="Judul"
value={data.title}
maxLength={100}
error={
data.title === "" ? (
<ComponentGlobal_ErrorInput text="Masukan judul" />
) : (
""
)
}
onChange={(val) => {
setData({
...data,
@@ -56,21 +68,36 @@ export default function Vote_Edit({
});
}}
/>
<Textarea
label="Deskripsi"
autosize
minRows={2}
maxRows={5}
withAsterisk
placeholder="Masukan deskripsi"
value={data.deskripsi}
onChange={(val) => {
setData({
...data,
deskripsi: val.target.value,
});
}}
/>
<Stack spacing={5}>
<Textarea
label="Deskripsi"
autosize
minRows={2}
maxRows={5}
withAsterisk
placeholder="Deskripsi"
value={data.deskripsi}
maxLength={300}
error={
data.deskripsi === "" ? (
<ComponentGlobal_ErrorInput text="Masukan deskripsi" />
) : (
""
)
}
onChange={(val) => {
setData({
...data,
deskripsi: val.target.value,
});
}}
/>
<ComponentGlobal_InputCountDown
lengthInput={data.deskripsi.length}
maxInput={300}
/>
</Stack>
<DatePickerInput
label="Jangka Waktu"
@@ -82,45 +109,97 @@ export default function Vote_Edit({
return moment(date).diff(Date.now(), "days") < 0;
}}
value={[data.awalVote, data.akhirVote]}
onChange={(val: any) =>
error={
data.awalVote === null || data.akhirVote === null ? (
<ComponentGlobal_ErrorInput text="Invalid Date" />
) : (
""
)
}
onChange={(val: any) => {
setData({
...data,
awalVote: val[0],
akhirVote: val[1],
})
}
});
}}
/>
<Stack spacing={0}>
<Center>
<Text fw={"bold"} fz={"sm"}>
Daftar Voting
Daftar Pilihan
</Text>
</Center>
<Stack>
<Stack>
{listVoting.map((e, index) => (
<Box key={index}>
<TextInput
label={"Nama Voting"}
withAsterisk
placeholder="Nama pilihan voting"
value={e.value}
onChange={(v) => {
const cloneData = _.clone(listVoting);
cloneData[index].value = v.currentTarget.value;
setListVoting([...listVoting]);
}}
/>
</Box>
{pilihanNama.map((e, index) => (
<Grid key={index} h="100%" align="center">
<Grid.Col span={10}>
<Box>
<TextInput
label={"Nama Pilihan"}
withAsterisk
placeholder="Nama pilihan"
value={e.value}
maxLength={100}
error={
e.value === "" ? (
<ComponentGlobal_ErrorInput text="Masukan nama pilihan" />
) : (
""
)
}
onChange={(v) => {
const cloneData = _.clone(pilihanNama);
cloneData[index].value = v.currentTarget.value;
setPilihanNama([...pilihanNama]);
}}
/>
</Box>
</Grid.Col>
<Grid.Col span={2} mt={"md"}>
<ActionIcon
variant="transparent"
radius={"xl"}
disabled={pilihanNama.length < 3 ? true : false}
onClick={() => {
pilihanNama.splice(index, 1);
setPilihanNama([...pilihanNama]);
}}
>
<IconTrash
style={{
transition: "0.5s",
}}
color={pilihanNama.length < 3 ? "gray" : "red"}
/>
</ActionIcon>
</Grid.Col>
</Grid>
))}
</Stack>
<Group position="center">
<Button
disabled={pilihanNama.length >= 4 ? true : false}
compact
w={100}
radius={"xl"}
leftIcon={<IconPlus size={15} />}
variant="outline"
onClick={() => {
setPilihanNama([...(pilihanNama as any), { value: "" }]);
}}
>
<Text fz={8}>Tambah List</Text>
</Button>
</Group>
</Stack>
</Stack>
<ButtonAction data={data} listVoting={listVoting} />
<ButtonAction data={data} listVoting={pilihanNama} />
{/* <pre>{JSON.stringify(listDaftarVote, null, 2)}</pre> */}
</Stack>
@@ -136,16 +215,20 @@ function ButtonAction({
listVoting: MODEL_VOTING_DAFTAR_NAMA_VOTE[];
}) {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [hotMenu, setHotMenu] = useAtom(gs_vote_hotMenu);
const [tabsStatus, setTabsStatus] = useAtom(gs_vote_status);
async function onUpdate() {
// console.log(listVoting);
await Vote_funEditById(data, listVoting).then((res) => {
if (res.status === 200) {
ComponentGlobal_NotifikasiBerhasil("Berhasil Update");
// setHotMenu(1);
// setTabsStatus("Draft");
router.back();
setIsLoading(true);
} else {
ComponentGlobal_NotifikasiGagal(res.message);
}
@@ -154,7 +237,14 @@ function ButtonAction({
return (
<>
<Button mt={"lg"} radius={"xl"} color="green" onClick={() => onUpdate()}>
<Button
loaderPosition="center"
loading={isLoading ? true : false}
mt={"lg"}
radius={"xl"}
color="green"
onClick={() => onUpdate()}
>
Update
</Button>
</>

View File

@@ -6,6 +6,7 @@ import {
MODEL_VOTING_DAFTAR_NAMA_VOTE,
} from "../../model/interface";
import { revalidatePath } from "next/cache";
import _ from "lodash";
export async function Vote_funEditById(
data: MODEL_VOTING,
@@ -22,22 +23,34 @@ export async function Vote_funEditById(
awalVote: data.awalVote,
akhirVote: data.akhirVote,
},
select: {
Voting_DaftarNamaVote: {
where: {
isActive: true,
},
},
},
});
if (!updtVoting) return { status: 400, message: "Gagal Update" };
for (let e of listVoting) {
const updtListVoting = await prisma.voting_DaftarNamaVote.updateMany({
where: {
id: e.id,
},
const delPilihan = await prisma.voting_DaftarNamaVote.deleteMany({
where: {
votingId: data.id,
},
});
if (!delPilihan) return { status: 400, message: "Gagal Update Pilihan" };
for (let v of listVoting) {
const val = v.value;
const namaPilihan = await prisma.voting_DaftarNamaVote.create({
data: {
value: e.value,
value: val,
votingId: data.id,
},
});
if (!updtListVoting)
return { status: 400, message: "Gagal Update Daftar Vote" };
if (!namaPilihan) return { status: 400, message: "Gagal Membuat List" };
}
revalidatePath("/dev/vote/detail/draft");

View File

@@ -6,6 +6,7 @@ export async function Vote_getListDaftarNamaById(voteId: string) {
const data = await prisma.voting_DaftarNamaVote.findMany({
where: {
votingId: voteId,
isActive: true,
},
});

View File

@@ -21,15 +21,17 @@ export async function Vote_getOnebyId(voteId: string) {
voting_StatusId: true,
Voting_DaftarNamaVote: {
orderBy: {
createdAt: "asc"
}
createdAt: "asc",
},
where: {
isActive: true,
},
},
Author: {
select: {
Profile: true
}
}
Profile: true,
},
},
},
});

View File

@@ -19,7 +19,7 @@ import {
Title,
rem,
} from "@mantine/core";
import { IconCirclePlus } from "@tabler/icons-react";
import { IconCirclePlus, IconPencilPlus } from "@tabler/icons-react";
import moment from "moment";
import { useRouter } from "next/navigation";
import ComponentVote_CardViewPublish from "../component/card_view_publish";
@@ -27,6 +27,8 @@ import { MODEL_VOTING } from "../model/interface";
import ComponentGlobal_AuthorNameOnHeader from "@/app_modules/component_global/author_name_on_header";
import _ from "lodash";
import ComponentVote_IsEmptyData from "../component/is_empty_data";
import { useState } from "react";
import { useWindowScroll } from "@mantine/hooks";
export default function Vote_Beranda({
dataVote,
@@ -34,25 +36,33 @@ export default function Vote_Beranda({
dataVote: MODEL_VOTING[];
}) {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [scroll, scrollTo] = useWindowScroll();
return (
<>
<Affix position={{ bottom: rem(150), right: rem(30) }}>
<ActionIcon
loading={isLoading ? true : false}
opacity={scroll.y > 0 ? 0.5 : ""}
style={{
transition: "0.5s",
}}
size={"xl"}
radius={"xl"}
variant="transparent"
bg={"blue"}
onClick={() => {
setIsLoading(true);
router.push(RouterVote.create);
}}
>
<IconCirclePlus color="white" size={40} />
<IconPencilPlus color="white" />
</ActionIcon>
</Affix>
{_.isEmpty(dataVote) ? (
<ComponentVote_IsEmptyData text="Tidak ada data"/>
<ComponentVote_IsEmptyData text="Tidak ada data" />
) : (
<Stack>
{dataVote.map((e, i) => (