Merge pull request #204 from bipproduction/lukman/6-september-2024

style: update user member
This commit is contained in:
Amalia
2024-09-06 13:58:19 +08:00
committed by GitHub
11 changed files with 643 additions and 113 deletions

View File

@@ -0,0 +1,71 @@
// GET MEMBER BY ID
import { prisma } from "@/module/_global";
import { funGetUserByCookies } from "@/module/auth";
import _ from "lodash";
import { NextResponse } from "next/server";
export async function GET(request: Request, context: { params: { id: string } }) {
try {
const { id } = context.params;
const { searchParams } = new URL(request.url);
const user = await funGetUserByCookies()
const name = searchParams.get('search')
if (user.id == undefined) {
return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 });
}
const data = await prisma.division.findUnique({
where: {
id: String(id),
isActive: true,
}
});
if (!data) {
return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan", }, { status: 404 });
}
const member = await prisma.divisionMember.findMany({
where: {
idDivision: String(id),
isActive: true,
User: {
name: {
contains: (name == undefined || name == null) ? "" : name,
mode: "insensitive",
}
}
},
select: {
id: true,
isAdmin: true,
isLeader: true,
idUser: true,
User: {
select: {
name: true,
img: true
}
}
},
orderBy: {
isAdmin: 'desc',
}
})
const fixMember = member.map((v: any) => ({
..._.omit(v, ["User"]),
name: v.User.name,
img: v.User.img
}))
return NextResponse.json({ success: true, data: fixMember })
} catch (error) {
return NextResponse.json({ success: false, message: "Gagal mendapatkan member, data tidak ditemukan", }, { status: 404 });
}
}

View File

@@ -27,7 +27,7 @@ export async function GET(request: Request, context: { params: { id: string } })
const member = await prisma.divisionMember.findMany({ const member = await prisma.divisionMember.findMany({
where: { where: {
idDivision: String(id), idDivision: String(id),
isActive: true isActive: true,
}, },
select: { select: {
id: true, id: true,

View File

@@ -32,7 +32,7 @@ export async function POST(request: Request, context: { params: { id: string } }
if (member.length > 0) { if (member.length > 0) {
const dataMember = member.map((v: any) => ({ const dataMember = member.map((v: any) => ({
..._.omit(v, ["idUser", "name"]), ..._.omit(v, ["idUser", "name", "img"]),
idDivision: idDivision, idDivision: idDivision,
idProject: id, idProject: id,
idUser: v.idUser, idUser: v.idUser,

View File

@@ -1,13 +1,16 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { globalCalender } from '../lib/val_calender'; import { globalCalender } from '../lib/val_calender';
import { useParams, useRouter } from 'next/navigation'; import { useParams, useRouter } from 'next/navigation';
import { funGetDivisionById, IDataMemberDivision } from '@/module/division_new'; import { funGetDivisionById, funGetSearchMemberDivision, IDataMemberDivision } from '@/module/division_new';
import { useHookstate } from '@hookstate/core'; import { useHookstate } from '@hookstate/core';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { LayoutNavbarNew, SkeletonSingle, WARNA } from '@/module/_global'; import { LayoutNavbarNew, SkeletonSingle, WARNA } from '@/module/_global';
import { Avatar, Box, Button, Divider, Flex, Grid, Group, rem, Text } from '@mantine/core'; import { ActionIcon, Avatar, Box, Button, Center, Divider, Flex, Grid, Group, Indicator, rem, Text, TextInput } from '@mantine/core';
import { FaCheck } from 'react-icons/fa6'; import { FaCheck } from 'react-icons/fa6';
import { HiMagnifyingGlass } from 'react-icons/hi2';
import { IoArrowBackOutline, IoClose } from 'react-icons/io5';
import { Carousel } from '@mantine/carousel';
export default function CreateUserCalender({ onClose }: { onClose: (val: any) => void }) { export default function CreateUserCalender({ onClose }: { onClose: (val: any) => void }) {
const router = useRouter() const router = useRouter()
@@ -17,13 +20,15 @@ export default function CreateUserCalender({ onClose }: { onClose: (val: any) =>
const member = useHookstate(globalCalender) const member = useHookstate(globalCalender)
const [selectAll, setSelectAll] = useState(false) const [selectAll, setSelectAll] = useState(false)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [onClickSearch, setOnClickSearch] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
async function getData() { async function getData() {
try { try {
setLoading(true) setLoading(true)
const response = await funGetDivisionById(param.id) const response = await funGetSearchMemberDivision("?search=", param.id)
if (response.success) { if (response.success) {
setData(response.data.member) setData(response.data)
if (member.length > 0) { if (member.length > 0) {
setSelectedFiles(JSON.parse(JSON.stringify(member.get()))) setSelectedFiles(JSON.parse(JSON.stringify(member.get())))
} }
@@ -81,15 +86,122 @@ export default function CreateUserCalender({ onClose }: { onClose: (val: any) =>
onClose(true) onClose(true)
} }
const handleSearchClick = () => {
setOnClickSearch(true);
};
const handleClose = () => {
setOnClickSearch(false);
};
function handleXMember(id: number) {
setSelectedFiles(selectedFiles.filter((i: any) => i.idUser != id))
}
async function fetchGetMember(val: string) {
setSearchQuery(val)
try {
const res = await funGetSearchMemberDivision('?search=' + val, param.id);
if (res.success) {
setData(res.data)
} else {
toast.error(res.message);
}
} catch (error) {
console.error(error);
}
}
return ( return (
<Box> <Box>
<LayoutNavbarNew <LayoutNavbarNew
// back="" // back=""
title="Pilih Anggota" title="Pilih Anggota"
menu menu={<ActionIcon onClick={handleSearchClick} variant="light" bg={WARNA.bgIcon} size="lg" radius="lg" aria-label="search">
<HiMagnifyingGlass size={20} color='white' />
</ActionIcon>}
/> />
{/* SEARCH */}
{onClickSearch
? (
<Box
pos={'fixed'} top={0} p={rem(20)} w={"100%"} style={{
maxWidth: rem(550),
zIndex: 9999,
backgroundColor: `${WARNA.biruTua}`,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
}}>
<Grid justify='center' align='center' gutter={'lg'}>
<Grid.Col span={1}>
<ActionIcon onClick={handleClose} variant="subtle" color='white' size="lg" mt={5} radius="lg" aria-label="search">
<IoArrowBackOutline size={30} />
</ActionIcon>
</Grid.Col>
<Grid.Col span={11}>
<TextInput
styles={{
input: {
color: "white",
borderRadius: '#A3A3A3',
borderColor: `${WARNA.biruTua}`,
backgroundColor: `${WARNA.biruTua}`,
},
}}
size="md"
radius={30}
placeholder="Pencarian"
onChange={(e) => fetchGetMember(e.currentTarget.value)}
/>
</Grid.Col>
</Grid>
</Box>
)
: null
}
{/* Close User */}
<Box pos={'fixed'} top={80} pl={rem(20)} pr={rem(20)} pt={rem(20)} pb={rem(5)} w={"100%"} style={{
maxWidth: rem(550),
zIndex: 100,
backgroundColor: `${WARNA.bgWhite}`,
borderBottom: `1px solid ${"#E0DFDF"}`
}}>
{selectedFiles.length > 0 ? (
<Carousel dragFree slideGap={"xs"} align="start" slideSize={"xs"} withIndicators withControls={false}>
{selectedFiles.map((v: any, i: any) => {
return (
<Carousel.Slide key={i}>
<Box w={{
base: 70,
xl: 70
}}
onClick={() => { handleXMember(v.idUser) }}
>
<Center>
<Indicator inline size={25} offset={7} position="bottom-end" color="red" withBorder label={<IoClose />}>
<Avatar style={{
border: `2px solid ${WARNA.biruTua}`
}} src={`/api/file/img?jenis=image&cat=user&file=${v.img}`} alt="it's me" size="lg" />
</Indicator>
</Center>
<Text ta={"center"} lineClamp={1}>{v.name}</Text>
</Box>
</Carousel.Slide>
)
})}
</Carousel>
) : (
<Box h={rem(81)}>
<Flex justify={"center"} align={'center'} h={"100%"}>
<Text ta={'center'} fz={14}>Tidak ada anggota yang dipilih</Text>
</Flex>
</Box>
)}
</Box>
<Box p={20}> <Box p={20}>
<Group justify="space-between" mt={20} onClick={handleSelectAll}> <Group justify="space-between" mt={100} onClick={handleSelectAll}>
<Text c={WARNA.biruTua} fw={"bold"}> <Text c={WARNA.biruTua} fw={"bold"}>
Pilih Semua Anggota Pilih Semua Anggota
</Text> </Text>

View File

@@ -1,8 +1,8 @@
"use client" "use client"
import { LayoutNavbarNew, SkeletonSingle, WARNA } from '@/module/_global'; import { LayoutNavbarNew, SkeletonSingle, WARNA } from '@/module/_global';
import { funGetDivisionById, IDataMemberDivision } from '@/module/division_new'; import { funGetDivisionById, funGetSearchMemberDivision, IDataMemberDivision } from '@/module/division_new';
import { useHookstate } from '@hookstate/core'; import { useHookstate } from '@hookstate/core';
import { Avatar, Box, Button, Center, Divider, Flex, Grid, Group, rem, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { ActionIcon, Avatar, Box, Button, Center, Divider, Flex, Grid, Group, Indicator, rem, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { useParams, useRouter } from 'next/navigation'; import { useParams, useRouter } from 'next/navigation';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { HiMagnifyingGlass } from 'react-icons/hi2'; import { HiMagnifyingGlass } from 'react-icons/hi2';
@@ -10,6 +10,8 @@ import { globalCalender } from '../lib/val_calender';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { FaCheck } from 'react-icons/fa6'; import { FaCheck } from 'react-icons/fa6';
import { IoArrowBackOutline, IoClose } from 'react-icons/io5';
import { Carousel } from '@mantine/carousel';
export default function UpdateListUsers({ onClose }: { onClose: (val: any) => void }) { export default function UpdateListUsers({ onClose }: { onClose: (val: any) => void }) {
@@ -20,13 +22,15 @@ export default function UpdateListUsers({ onClose }: { onClose: (val: any) => vo
const member = useHookstate(globalCalender) const member = useHookstate(globalCalender)
const [selectAll, setSelectAll] = useState(false) const [selectAll, setSelectAll] = useState(false)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [onClickSearch, setOnClickSearch] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
async function getData() { async function getData() {
try { try {
setLoading(true) setLoading(true)
const response = await funGetDivisionById(param.id) const response = await funGetSearchMemberDivision("?search=", param.id)
if (response.success) { if (response.success) {
setData(response.data.member) setData(response.data)
if (member.length > 0) { if (member.length > 0) {
setSelectedFiles(JSON.parse(JSON.stringify(member.get()))) setSelectedFiles(JSON.parse(JSON.stringify(member.get())))
} }
@@ -83,15 +87,122 @@ export default function UpdateListUsers({ onClose }: { onClose: (val: any) => vo
onClose(true) onClose(true)
} }
const handleSearchClick = () => {
setOnClickSearch(true);
};
const handleClose = () => {
setOnClickSearch(false);
};
function handleXMember(id: number) {
setSelectedFiles(selectedFiles.filter((i: any) => i.idUser != id))
}
async function fetchGetMember(val: string) {
setSearchQuery(val)
try {
const res = await funGetSearchMemberDivision('?search=' + val, param.id);
if (res.success) {
setData(res.data)
} else {
toast.error(res.message);
}
} catch (error) {
console.error(error);
}
}
return ( return (
<Box> <Box>
<LayoutNavbarNew <LayoutNavbarNew
// back="" // back=""
title="Pilih Anggota" title="Pilih Anggota"
menu menu={<ActionIcon onClick={handleSearchClick} variant="light" bg={WARNA.bgIcon} size="lg" radius="lg" aria-label="search">
<HiMagnifyingGlass size={20} color='white' />
</ActionIcon>}
/> />
{/* SEARCH */}
{onClickSearch
? (
<Box
pos={'fixed'} top={0} p={rem(20)} w={"100%"} style={{
maxWidth: rem(550),
zIndex: 9999,
backgroundColor: `${WARNA.biruTua}`,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
}}>
<Grid justify='center' align='center' gutter={'lg'}>
<Grid.Col span={1}>
<ActionIcon onClick={handleClose} variant="subtle" color='white' size="lg" mt={5} radius="lg" aria-label="search">
<IoArrowBackOutline size={30} />
</ActionIcon>
</Grid.Col>
<Grid.Col span={11}>
<TextInput
styles={{
input: {
color: "white",
borderRadius: '#A3A3A3',
borderColor: `${WARNA.biruTua}`,
backgroundColor: `${WARNA.biruTua}`,
},
}}
size="md"
radius={30}
placeholder="Pencarian"
onChange={(e) => fetchGetMember(e.currentTarget.value)}
/>
</Grid.Col>
</Grid>
</Box>
)
: null
}
{/* Close User */}
<Box pos={'fixed'} top={80} pl={rem(20)} pr={rem(20)} pt={rem(20)} pb={rem(5)} w={"100%"} style={{
maxWidth: rem(550),
zIndex: 100,
backgroundColor: `${WARNA.bgWhite}`,
borderBottom: `1px solid ${"#E0DFDF"}`
}}>
{selectedFiles.length > 0 ? (
<Carousel dragFree slideGap={"xs"} align="start" slideSize={"xs"} withIndicators withControls={false}>
{selectedFiles.map((v: any, i: any) => {
return (
<Carousel.Slide key={i}>
<Box w={{
base: 70,
xl: 70
}}
onClick={() => { handleXMember(v.idUser) }}
>
<Center>
<Indicator inline size={25} offset={7} position="bottom-end" color="red" withBorder label={<IoClose />}>
<Avatar style={{
border: `2px solid ${WARNA.biruTua}`
}} src={`/api/file/img?jenis=image&cat=user&file=${v.img}`} alt="it's me" size="lg" />
</Indicator>
</Center>
<Text ta={"center"} lineClamp={1}>{v.name}</Text>
</Box>
</Carousel.Slide>
)
})}
</Carousel>
) : (
<Box h={rem(81)}>
<Flex justify={"center"} align={'center'} h={"100%"}>
<Text ta={'center'} fz={14}>Tidak ada anggota yang dipilih</Text>
</Flex>
</Box>
)}
</Box>
<Box p={20}> <Box p={20}>
<Group justify="space-between" mt={20} onClick={handleSelectAll}> <Group justify="space-between" mt={100} onClick={handleSelectAll}>
<Text c={WARNA.biruTua} fw={"bold"}> <Text c={WARNA.biruTua} fw={"bold"}>
Pilih Semua Anggota Pilih Semua Anggota
</Text> </Text>

View File

@@ -14,7 +14,7 @@ import CreateAnggotaDivision from './ui/create_anggota_division';
import EditDivision from './ui/edit_division'; import EditDivision from './ui/edit_division';
import CreateReport from './ui/create_report'; import CreateReport from './ui/create_report';
import ReportDivisionId from './ui/report_division_id'; import ReportDivisionId from './ui/report_division_id';
import { funGetDivisionById, funGetListDivisionByIdDivision } from './lib/api_division'; import { funGetDivisionById, funGetListDivisionByIdDivision, funGetSearchMemberDivision } from './lib/api_division';
export { CreateUsers }; export { CreateUsers };
export { CreateAdminDivision }; export { CreateAdminDivision };
@@ -34,3 +34,4 @@ export { CreateReport }
export { ReportDivisionId } export { ReportDivisionId }
export { funGetDivisionById } export { funGetDivisionById }
export { funGetListDivisionByIdDivision } export { funGetListDivisionByIdDivision }
export { funGetSearchMemberDivision }

View File

@@ -83,3 +83,8 @@ export const funGetReportDivision = async (path?: string) => {
const response = await fetch(`/api/division/report${(path) ? path : ''}`, { next: { tags: ['discussion'] } }); const response = await fetch(`/api/division/report${(path) ? path : ''}`, { next: { tags: ['discussion'] } });
return await response.json().catch(() => null); return await response.json().catch(() => null);
} }
export const funGetSearchMemberDivision = async (path: string, id: string) => {
const response = await fetch(`/api/division/${id}/member/${(path) ? path : ''}`, { next: { tags: ['division'] } });
return await response.json().catch(() => null);
}

View File

@@ -145,41 +145,22 @@ export default function CreateAnggotaDivision() {
backgroundColor: `${WARNA.bgWhite}`, backgroundColor: `${WARNA.bgWhite}`,
borderBottom: `1px solid ${"#E0DFDF"}` borderBottom: `1px solid ${"#E0DFDF"}`
}}> }}>
{/* <Carousel dragFree slideGap={"xs"} align="start" slideSize={"xs"} withIndicators withControls={false}>
{selectedFiles.map((v: any, i: any) => {
return (
<Carousel.Slide key={i}>
<Box w={{
base: 60,
xl: 60
}}
onClick={() => { handleXMember(v.idUser) }}
>
<Center>
<Indicator inline size={25} offset={7} position="bottom-end" color="red" withBorder label={<IoClose />}>
<Avatar src={`/api/file/img?jenis=image&cat=user&file=${v.img}`} alt="it's me" size="lg" />
</Indicator>
</Center>
<Text ta={"center"} lineClamp={1}>{v.name}</Text>
</Box>
</Carousel.Slide>
)
})}
</Carousel> */}
{selectedFiles.length > 0 ? ( {selectedFiles.length > 0 ? (
<Carousel dragFree slideGap={"xs"} align="start" slideSize={"xs"} withIndicators withControls={false}> <Carousel dragFree slideGap={"xs"} align="start" slideSize={"xs"} withIndicators withControls={false}>
{selectedFiles.map((v: any, i: any) => { {selectedFiles.map((v: any, i: any) => {
return ( return (
<Carousel.Slide key={i}> <Carousel.Slide key={i}>
<Box w={{ <Box w={{
base: 60, base: 70,
xl: 60 xl: 70
}} }}
onClick={() => { handleXMember(v.idUser) }} onClick={() => { handleXMember(v.idUser) }}
> >
<Center> <Center>
<Indicator inline size={25} offset={7} position="bottom-end" color="red" withBorder label={<IoClose />}> <Indicator inline size={25} offset={7} position="bottom-end" color="red" withBorder label={<IoClose />}>
<Avatar src={`/api/file/img?jenis=image&cat=user&file=${v.img}`} alt="it's me" size="lg" /> <Avatar style={{
border: `2px solid ${WARNA.biruTua}`
}} src={`/api/file/img?jenis=image&cat=user&file=${v.img}`} alt="it's me" size="lg" />
</Indicator> </Indicator>
</Center> </Center>
<Text ta={"center"} lineClamp={1}>{v.name}</Text> <Text ta={"center"} lineClamp={1}>{v.name}</Text>
@@ -189,8 +170,10 @@ export default function CreateAnggotaDivision() {
})} })}
</Carousel> </Carousel>
) : ( ) : (
<Box> <Box h={rem(81)}>
<Flex justify={"center"} align={'center'} h={"100%"}>
<Text ta={'center'} fz={14}>Tidak ada anggota yang dipilih</Text> <Text ta={'center'} fz={14}>Tidak ada anggota yang dipilih</Text>
</Flex>
</Box> </Box>
)} )}
</Box> </Box>

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import { globalRole, WARNA } from '@/module/_global'; import { globalRole, WARNA } from '@/module/_global';
import { ActionIcon, Avatar, Badge, Box, Card, Center, Divider, Flex, Grid, Group, Text, TextInput, Title } from '@mantine/core'; import { ActionIcon, Avatar, Badge, Box, Card, Center, Divider, Flex, Grid, Group, Skeleton, Text, TextInput, Title } from '@mantine/core';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { HiMagnifyingGlass, HiMiniPresentationChartBar, HiOutlineListBullet, HiSquares2X2 } from 'react-icons/hi2'; import { HiMagnifyingGlass, HiMiniPresentationChartBar, HiOutlineListBullet, HiSquares2X2 } from 'react-icons/hi2';
@@ -12,6 +12,7 @@ import { useShallowEffect } from '@mantine/hooks';
import { IDataProject } from '../lib/type_project'; import { IDataProject } from '../lib/type_project';
import { funGetAllGroup, IDataGroup } from '@/module/group'; import { funGetAllGroup, IDataGroup } from '@/module/group';
import { useHookstate } from '@hookstate/core'; import { useHookstate } from '@hookstate/core';
import _ from 'lodash';
export default function ListProject() { export default function ListProject() {
const [isList, setIsList] = useState(false) const [isList, setIsList] = useState(false)
@@ -85,12 +86,18 @@ export default function ListProject() {
</Grid> </Grid>
<Box pt={20}> <Box pt={20}>
{roleLogin.get() == 'supadmin' && <Text>Filter by: {nameGroup}</Text>} {roleLogin.get() == 'supadmin' && <Text>Filter by: {nameGroup}</Text>}
{loading ?
<Box>
<Skeleton width={"100%"} height={100} radius={"md"} />
</Box>
:
<Box bg={"#DCEED8"} p={10} style={{ borderRadius: 10 }}> <Box bg={"#DCEED8"} p={10} style={{ borderRadius: 10 }}>
<Text fw={'bold'} c={WARNA.biruTua}>Total Kegiatan</Text> <Text fw={'bold'} c={WARNA.biruTua}>Total Kegiatan</Text>
<Flex justify={'center'} align={'center'} h={'100%'}> <Flex justify={'center'} align={'center'} h={'100%'}>
<Text fz={40} fw={'bold'} c={WARNA.biruTua}>{isData.length}</Text> <Text fz={40} fw={'bold'} c={WARNA.biruTua}>{isData.length}</Text>
</Flex> </Flex>
</Box> </Box>
}
{isList ? ( {isList ? (
<Box pt={20}> <Box pt={20}>
{isData.map((v, i) => { {isData.map((v, i) => {
@@ -152,7 +159,22 @@ export default function ListProject() {
</Box> </Box>
) : ( ) : (
<Box pt={20}> <Box pt={20}>
{isData.map((v, i) => { {loading ?
Array(3)
.fill(null)
.map((_, i) => (
<Box key={i} mb={20}>
<Skeleton width={"100%"} height={200} radius={"md"} />
</Box>
))
:
_.isEmpty(isData)
?
<Box style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '50vh' }}>
<Text c="dimmed" ta={"center"} fs={"italic"}>Tidak ada Kegiatan</Text>
</Box>
:
isData.map((v, i) => {
return ( return (
<Box key={i} mb={20}> <Box key={i} mb={20}>
<Card shadow="sm" padding="md" component="a" radius={10} onClick={() => router.push(`/project/${v.id}`)}> <Card shadow="sm" padding="md" component="a" radius={10} onClick={() => router.push(`/project/${v.id}`)}>
@@ -183,18 +205,15 @@ export default function ListProject() {
<Avatar> <Avatar>
<MdAccountCircle size={32} color={WARNA.biruTua} /> <MdAccountCircle size={32} color={WARNA.biruTua} />
</Avatar> </Avatar>
<Avatar> <Avatar>+{v.member - 1}</Avatar>
{
v.member > 0 ? '+' + (v.member - 1) : "0"
}
</Avatar>
</Avatar.Group> </Avatar.Group>
</Group> </Group>
</Box> </Box>
</Card> </Card>
</Box> </Box>
); );
})} })
}
</Box> </Box>
)} )}
</Box> </Box>

View File

@@ -1,16 +1,19 @@
"use client" "use client"
import { LayoutNavbarNew, SkeletonSingle, WARNA } from "@/module/_global"; import { LayoutNavbarNew, SkeletonSingle, WARNA } from "@/module/_global";
import { funGetDivisionById, IDataMemberDivision } from "@/module/division_new"; import { funGetDivisionById, funGetSearchMemberDivision, IDataMemberDivision } from "@/module/division_new";
import { import {
ActionIcon,
Anchor, Anchor,
Avatar, Avatar,
Box, Box,
Button, Button,
Center,
Checkbox, Checkbox,
Divider, Divider,
Flex, Flex,
Grid, Grid,
Group, Group,
Indicator,
rem, rem,
Stack, Stack,
Text, Text,
@@ -24,6 +27,9 @@ import { FaCheck } from "react-icons/fa6";
import { funAddMemberTask, funGetTaskDivisionById } from "../lib/api_task"; import { funAddMemberTask, funGetTaskDivisionById } from "../lib/api_task";
import { IDataMemberTaskDivision } from "../lib/type_task"; import { IDataMemberTaskDivision } from "../lib/type_task";
import LayoutModal from "@/module/_global/layout/layout_modal"; import LayoutModal from "@/module/_global/layout/layout_modal";
import { HiMagnifyingGlass } from "react-icons/hi2";
import { IoArrowBackOutline, IoClose } from "react-icons/io5";
import { Carousel } from "@mantine/carousel";
export default function AddMemberDetailTask() { export default function AddMemberDetailTask() {
const router = useRouter() const router = useRouter()
@@ -34,14 +40,16 @@ export default function AddMemberDetailTask() {
const [selectAll, setSelectAll] = useState(false) const [selectAll, setSelectAll] = useState(false)
const [openModal, setOpenModal] = useState(false) const [openModal, setOpenModal] = useState(false)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [onClickSearch, setOnClickSearch] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
async function getData() { async function getData() {
try { try {
setLoading(true) setLoading(true)
const response = await funGetDivisionById(param.id) const response = await funGetSearchMemberDivision("?search=", param.id)
if (response.success) { if (response.success) {
setData(response.data.member) setData(response.data)
} else { } else {
toast.error(response.message) toast.error(response.message)
} }
@@ -71,7 +79,7 @@ export default function AddMemberDetailTask() {
if (selectedFiles.some((i: any) => i.idUser == isData[index].idUser)) { if (selectedFiles.some((i: any) => i.idUser == isData[index].idUser)) {
setSelectedFiles(selectedFiles.filter((i: any) => i.idUser != isData[index].idUser)) setSelectedFiles(selectedFiles.filter((i: any) => i.idUser != isData[index].idUser))
} else { } else {
setSelectedFiles([...selectedFiles, { idUser: isData[index].idUser, name: isData[index].name }]) setSelectedFiles([...selectedFiles, { idUser: isData[index].idUser, name: isData[index].name, img: isData[index].img }])
} }
}; };
@@ -84,7 +92,7 @@ export default function AddMemberDetailTask() {
if (!isDataMember.some((i: any) => i.idUser == isData[index].idUser)) { if (!isDataMember.some((i: any) => i.idUser == isData[index].idUser)) {
if (!selectedFiles.some((i: any) => i.idUser == isData[index].idUser)) { if (!selectedFiles.some((i: any) => i.idUser == isData[index].idUser)) {
const newArr = { const newArr = {
idUser: isData[index].idUser, name: isData[index].name idUser: isData[index].idUser, name: isData[index].name, img: isData[index].img
} }
setSelectedFiles((selectedFiles: any) => [...selectedFiles, newArr]) setSelectedFiles((selectedFiles: any) => [...selectedFiles, newArr])
} }
@@ -120,16 +128,122 @@ export default function AddMemberDetailTask() {
} }
} }
const handleSearchClick = () => {
setOnClickSearch(true);
};
const handleClose = () => {
setOnClickSearch(false);
};
function handleXMember(id: number) {
setSelectedFiles(selectedFiles.filter((i: any) => i.idUser != id))
}
async function fetchGetMember(val: string) {
setSearchQuery(val)
try {
const res = await funGetSearchMemberDivision('?search=' + val, param.id);
if (res.success) {
setData(res.data)
} else {
toast.error(res.message);
}
} catch (error) {
console.error(error);
}
}
return ( return (
<Box> <Box>
<LayoutNavbarNew <LayoutNavbarNew
back="" back=""
title="Pilih Anggota" title="Pilih Anggota"
menu menu={<ActionIcon onClick={handleSearchClick} variant="light" bg={WARNA.bgIcon} size="lg" radius="lg" aria-label="search">
<HiMagnifyingGlass size={20} color='white' />
</ActionIcon>}
/> />
{/* SEARCH */}
{onClickSearch
? (
<Box
pos={'fixed'} top={0} p={rem(20)} w={"100%"} style={{
maxWidth: rem(550),
zIndex: 9999,
backgroundColor: `${WARNA.biruTua}`,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
}}>
<Grid justify='center' align='center' gutter={'lg'}>
<Grid.Col span={1}>
<ActionIcon onClick={handleClose} variant="subtle" color='white' size="lg" mt={5} radius="lg" aria-label="search">
<IoArrowBackOutline size={30} />
</ActionIcon>
</Grid.Col>
<Grid.Col span={11}>
<TextInput
styles={{
input: {
color: "white",
borderRadius: '#A3A3A3',
borderColor: `${WARNA.biruTua}`,
backgroundColor: `${WARNA.biruTua}`,
},
}}
size="md"
radius={30}
placeholder="Pencarian"
onChange={(e) => fetchGetMember(e.currentTarget.value)}
/>
</Grid.Col>
</Grid>
</Box>
)
: null
}
{/* Close User */}
<Box pos={'fixed'} top={80} pl={rem(20)} pr={rem(20)} pt={rem(20)} pb={rem(5)} w={"100%"} style={{
maxWidth: rem(550),
zIndex: 100,
backgroundColor: `${WARNA.bgWhite}`,
borderBottom: `1px solid ${"#E0DFDF"}`
}}>
{selectedFiles.length > 0 ? (
<Carousel dragFree slideGap={"xs"} align="start" slideSize={"xs"} withIndicators withControls={false}>
{selectedFiles.map((v: any, i: any) => {
return (
<Carousel.Slide key={i}>
<Box w={{
base: 70,
xl: 70
}}
onClick={() => { handleXMember(v.idUser) }}
>
<Center>
<Indicator inline size={25} offset={7} position="bottom-end" color="red" withBorder label={<IoClose />}>
<Avatar style={{
border: `2px solid ${WARNA.biruTua}`
}} src={`/api/file/img?jenis=image&cat=user&file=${v.img}`} alt="it's me" size="lg" />
</Indicator>
</Center>
<Text ta={"center"} lineClamp={1}>{v.name}</Text>
</Box>
</Carousel.Slide>
)
})}
</Carousel>
) : (
<Box h={rem(81)}>
<Flex justify={"center"} align={'center'} h={"100%"}>
<Text ta={'center'} fz={14}>Tidak ada anggota yang dipilih</Text>
</Flex>
</Box>
)}
</Box>
<Box p={20}> <Box p={20}>
<Group justify="space-between" mt={20} onClick={handleSelectAll}> <Group justify="space-between" mt={100} onClick={handleSelectAll}>
<Text c={WARNA.biruTua} fw={"bold"}> <Text c={WARNA.biruTua} fw={"bold"}>
Pilih Semua Anggota Pilih Semua Anggota
</Text> </Text>

View File

@@ -1,19 +1,22 @@
"use client" "use client"
import { LayoutNavbarNew, SkeletonSingle, WARNA } from "@/module/_global"; import { LayoutNavbarNew, SkeletonSingle, WARNA } from "@/module/_global";
import { funGetDivisionById, IDataMemberDivision } from "@/module/division_new"; import { funGetDivisionById, funGetSearchMemberDivision, IDataMemberDivision } from "@/module/division_new";
import { useHookstate } from "@hookstate/core"; import { useHookstate } from "@hookstate/core";
import { import {
ActionIcon, ActionIcon,
Avatar, Avatar,
Box, Box,
Button, Button,
Center,
Divider, Divider,
Flex, Flex,
Grid, Grid,
Group, Group,
Indicator,
rem, rem,
Skeleton, Skeleton,
Text, Text,
TextInput,
} from "@mantine/core"; } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks"; import { useShallowEffect } from "@mantine/hooks";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@@ -23,6 +26,9 @@ import { globalMemberTask } from "../lib/val_task";
import { FaCheck } from "react-icons/fa6"; import { FaCheck } from "react-icons/fa6";
import { RiListCheck } from "react-icons/ri"; import { RiListCheck } from "react-icons/ri";
import { BsListCheck } from "react-icons/bs"; import { BsListCheck } from "react-icons/bs";
import { HiMagnifyingGlass } from "react-icons/hi2";
import { IoArrowBackOutline, IoClose } from "react-icons/io5";
import { Carousel } from "@mantine/carousel";
export default function CreateUsersProject({ onClose }: { onClose: (val: any) => void }) { export default function CreateUsersProject({ onClose }: { onClose: (val: any) => void }) {
const router = useRouter() const router = useRouter()
@@ -32,14 +38,16 @@ export default function CreateUsersProject({ onClose }: { onClose: (val: any) =>
const member = useHookstate(globalMemberTask) const member = useHookstate(globalMemberTask)
const [selectAll, setSelectAll] = useState(false) const [selectAll, setSelectAll] = useState(false)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [onClickSearch, setOnClickSearch] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
async function getData() { async function getData() {
try { try {
setLoading(true) setLoading(true)
const response = await funGetDivisionById(param.id) const response = await funGetSearchMemberDivision("?search=", param.id)
if (response.success) { if (response.success) {
setData(response.data.member) setData(response.data)
if (member.length > 0) { if (member.length > 0) {
setSelectedFiles(JSON.parse(JSON.stringify(member.get()))) setSelectedFiles(JSON.parse(JSON.stringify(member.get())))
} }
@@ -96,19 +104,125 @@ export default function CreateUsersProject({ onClose }: { onClose: (val: any) =>
onClose(true) onClose(true)
} }
const handleSearchClick = () => {
setOnClickSearch(true);
};
const handleClose = () => {
setOnClickSearch(false);
};
function handleXMember(id: number) {
setSelectedFiles(selectedFiles.filter((i: any) => i.idUser != id))
}
async function fetchGetMember(val: string) {
setSearchQuery(val)
try {
const res = await funGetSearchMemberDivision('?search=' + val, param.id);
if (res.success) {
setData(res.data)
} else {
toast.error(res.message);
}
} catch (error) {
console.error(error);
}
}
return ( return (
<Box> <Box>
<LayoutNavbarNew <LayoutNavbarNew
// back="" // back=""
title="Pilih Anggota" title="Pilih Anggota"
menu menu={<ActionIcon onClick={handleSearchClick} variant="light" bg={WARNA.bgIcon} size="lg" radius="lg" aria-label="search">
<HiMagnifyingGlass size={20} color='white' />
</ActionIcon>}
/> />
{/* SEARCH */}
{onClickSearch
? (
<Box
pos={'fixed'} top={0} p={rem(20)} w={"100%"} style={{
maxWidth: rem(550),
zIndex: 9999,
backgroundColor: `${WARNA.biruTua}`,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
}}>
<Grid justify='center' align='center' gutter={'lg'}>
<Grid.Col span={1}>
<ActionIcon onClick={handleClose} variant="subtle" color='white' size="lg" mt={5} radius="lg" aria-label="search">
<IoArrowBackOutline size={30} />
</ActionIcon>
</Grid.Col>
<Grid.Col span={11}>
<TextInput
styles={{
input: {
color: "white",
borderRadius: '#A3A3A3',
borderColor: `${WARNA.biruTua}`,
backgroundColor: `${WARNA.biruTua}`,
},
}}
size="md"
radius={30}
placeholder="Pencarian"
onChange={(e) => fetchGetMember(e.currentTarget.value)}
/>
</Grid.Col>
</Grid>
</Box>
)
: null
}
{/* Close User */}
<Box pos={'fixed'} top={80} pl={rem(20)} pr={rem(20)} pt={rem(20)} pb={rem(5)} w={"100%"} style={{
maxWidth: rem(550),
zIndex: 100,
backgroundColor: `${WARNA.bgWhite}`,
borderBottom: `1px solid ${"#E0DFDF"}`
}}>
{selectedFiles.length > 0 ? (
<Carousel dragFree slideGap={"xs"} align="start" slideSize={"xs"} withIndicators withControls={false}>
{selectedFiles.map((v: any, i: any) => {
return (
<Carousel.Slide key={i}>
<Box w={{
base: 70,
xl: 70
}}
onClick={() => { handleXMember(v.idUser) }}
>
<Center>
<Indicator inline size={25} offset={7} position="bottom-end" color="red" withBorder label={<IoClose />}>
<Avatar style={{
border: `2px solid ${WARNA.biruTua}`
}} src={`/api/file/img?jenis=image&cat=user&file=${v.img}`} alt="it's me" size="lg" />
</Indicator>
</Center>
<Text ta={"center"} lineClamp={1}>{v.name}</Text>
</Box>
</Carousel.Slide>
)
})}
</Carousel>
) : (
<Box h={rem(81)}>
<Flex justify={"center"} align={'center'} h={"100%"}>
<Text ta={'center'} fz={14}>Tidak ada anggota yang dipilih</Text>
</Flex>
</Box>
)}
</Box>
<Box p={20}> <Box p={20}>
{loading ? {loading ?
<Skeleton height={20} width={"100%"} mt={20} /> <Skeleton height={20} width={"100%"} mt={20} />
: :
<Group justify="space-between" mt={20} onClick={handleSelectAll}> <Group justify="space-between" mt={100} onClick={handleSelectAll}>
<Text c={WARNA.biruTua} fw={"bold"}> <Text c={WARNA.biruTua} fw={"bold"}>
Pilih Semua Anggota Pilih Semua Anggota
</Text> </Text>