Portofolio

Fix:
- Sub bidang bisnis

### No Issu
This commit is contained in:
2025-09-02 18:29:28 +08:00
parent 6887f85e6a
commit 9a915c55d2
9 changed files with 201 additions and 76 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { StackCustom, ViewWrapper } from "@/components"; import { StackCustom, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
@@ -8,31 +9,40 @@ import TabSection from "@/screens/Home/tabSection";
import { tabsHome } from "@/screens/Home/tabsList"; import { tabsHome } from "@/screens/Home/tabsList";
import Home_FeatureSection from "@/screens/Home/topFeatureSection"; import Home_FeatureSection from "@/screens/Home/topFeatureSection";
import { apiUser } from "@/service/api-client/api-user"; import { apiUser } from "@/service/api-client/api-user";
import { apiVersion } from "@/service/api-config";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { Redirect, router, Stack } from "expo-router"; import { Redirect, router, Stack } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
export default function Application() { export default function Application() {
const { user } = useAuth(); const { token, user } = useAuth();
const [data, setData] = useState<any>({});
const [data, setData] = useState<any>();
useEffect(() => { useEffect(() => {
onLoadData(); onLoadData();
checkVersion();
}, []); }, []);
async function onLoadData() { async function onLoadData() {
const response = await apiUser(user?.id as string); const response = await apiUser(user?.id as string);
console.log("User >>", JSON.stringify(response.data.username, null, 2)); console.log("Response profile >>", JSON.stringify(response?.data?.Profile, null, 2));
console.log("Profile Check >>", JSON.stringify(response.data.Profile.id, null, 2));
setData(response.data); setData(response.data);
} }
const checkVersion = async () => {
const response = await apiVersion();
console.log("Version >>", JSON.stringify(response.data, null, 2));
};
if (data && data?.active === false) { if (data && data?.active === false) {
console.log("User is not active");
return <Redirect href={`/waiting-room`} />; return <Redirect href={`/waiting-room`} />;
} }
if (data && data?.Profile === null) { if (data && data?.Profile === null) {
console.log("Profile is null");
return <Redirect href={`/profile/create`} />; return <Redirect href={`/profile/create`} />;
} }
@@ -40,7 +50,7 @@ export default function Application() {
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "HIPMI", title: `HIPMI`,
headerLeft: () => ( headerLeft: () => (
<Ionicons <Ionicons
name="search" name="search"

View File

@@ -1,9 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import { import {
ActionIcon,
AvatarComp, AvatarComp,
BaseBox, BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
CenterCustom, CenterCustom,
Grid,
InformationBox, InformationBox,
SelectCustom, SelectCustom,
Spacing, Spacing,
@@ -13,7 +15,9 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio"; import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio";
import { import {
@@ -25,10 +29,12 @@ import {
IMasterSubBidangBisnis, IMasterSubBidangBisnis,
} from "@/types/Type-Master"; } from "@/types/Type-Master";
import pickImage from "@/utils/pickImage"; import pickImage from "@/utils/pickImage";
import { Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image"; import { Image } from "expo-image";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Text, View } from "react-native"; import { Text, TouchableOpacity, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number"; import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { Avatar } from "react-native-paper"; import { Avatar } from "react-native-paper";
@@ -46,12 +52,11 @@ export default function PortofolioCreate() {
const [imageUri, setImageUri] = useState<string | null>(null); const [imageUri, setImageUri] = useState<string | null>(null);
const [bidangBisnis, setBidangBisnis] = useState<IMasterBidangBisnis[]>([]); const [bidangBisnis, setBidangBisnis] = useState<IMasterBidangBisnis[]>([]);
const [selectBidangId, setSelectBidangId] = useState<string>("");
const [subBidangBisnis, setSubBidangBisnis] = useState< const [subBidangBisnis, setSubBidangBisnis] = useState<
IMasterSubBidangBisnis[] IMasterSubBidangBisnis[]
>([]); >([]);
// const [subBidangSelected, setSubBidangSelected] = useState<string[]>([]); const [selectedSubBidang, setSelectedSubBidang] = useState<string[]>([]);
const [listSubBidangSelected, setListSubBidangSelected] = useState([ const [listSubBidangSelected, setListSubBidangSelected] = useState([
{ {
id: "", id: "",
@@ -82,6 +87,7 @@ export default function PortofolioCreate() {
useEffect(() => { useEffect(() => {
onLoadMaster(); onLoadMaster();
onLoadMasterSubBidangBisnis();
}, []); }, []);
const onLoadMaster = async () => { const onLoadMaster = async () => {
@@ -94,15 +100,23 @@ export default function PortofolioCreate() {
} }
}; };
const onLoadMasterSubBidangBisnis = async ({ id }: { id: string }) => { const onLoadMasterSubBidangBisnis = async () => {
try { try {
const response = await apiMasterSubBidangBisnis({ id: id }); const response = await apiMasterSubBidangBisnis({});
setSubBidangBisnis(response.data); setSubBidangBisnis(response.data);
} catch (error) { } catch (error) {
setSubBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error); console.log("Error onLoadMasterBidangBisnis", error);
} }
}; };
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
const selectedList = subBidangBisnis?.filter(
(item) => (item?.masterBidangBisnisId as any) === id
);
setSelectedSubBidang(selectedList as any[]);
};
return ( return (
<ViewWrapper <ViewWrapper
footerComponent={ footerComponent={
@@ -136,45 +150,100 @@ export default function PortofolioCreate() {
}))} }))}
value={data.masterBidangBisnisId} value={data.masterBidangBisnisId}
onChange={(value) => { onChange={(value) => {
const isSameBidang = data.masterBidangBisnisId === value;
if (!isSameBidang) {
setListSubBidangSelected([{ id: "" }]);
}
setData({ ...(data as any), masterBidangBisnisId: value }); setData({ ...(data as any), masterBidangBisnisId: value });
setSelectBidangId(id as string); handlerSelectedSubBidang({ id: value as string });
onLoadMasterSubBidangBisnis({ id: value as string });
}} }}
/> />
{/* {listSubBidangSelected.map((item, index) => ( {listSubBidangSelected.map((item, index) => (
<Grid key={index}> <SelectCustom
<Grid.Col span={10}> key={index}
<SelectCustom disabled={data.masterBidangBisnisId === ""}
disabled={selectBidangId === ""} label="Sub Bidang Usaha"
label="Sub Bidang Usaha" required
required data={_.map(selectedSubBidang as any)
data={subBidangBisnis.map((item) => ({ .filter((option: any) => {
label: item.name, const selectedValues = listSubBidangSelected.map((s) => s.id);
value: item.id, return (
}))} option.id === item.id || // biarkan tetap muncul kalau ini valuenya sendiri
value={data.sub_bidang_usaha} !selectedValues.includes(option.id)
onChange={(value) => { );
setData({ ...(data as any), sub_bidang_usaha: value }); })
setListSubBidangSelected([ .map((e: any) => ({
...listSubBidangSelected, value: e.id,
{ id: value as string }, label: e.name,
]); }))}
}} value={item.id || null}
/> onChange={(value) => {
</Grid.Col> const list = _.clone(listSubBidangSelected);
<Grid.Col list[index].id = value as any;
span={2} setListSubBidangSelected(list);
style={{ alignItems: "center", justifyContent: "center" }} }}
> />
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid>
))} ))}
<ButtonCenteredOnly <CenterCustom>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<ActionIcon
disabled={
selectedSubBidang.length === listSubBidangSelected.length
}
onPress={() => {
setListSubBidangSelected([
...listSubBidangSelected,
{ id: "" },
]);
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={listSubBidangSelected.length <= 1}
onPress={() => {
const list = _.clone(listSubBidangSelected);
list.pop();
setListSubBidangSelected(list);
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
{/* <SelectCustom
label="Bidang Usaha"
required
data={bidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={null}
onChange={(value) => {
setData({ ...(data as any), masterBidangBisnisId: value });
handlerSelectedSubBidang({ id: value as string });
}}
/> */}
{/* <ButtonCenteredOnly
onPress={() => { onPress={() => {
setListSubBidangSelected([...listSubBidangSelected, { id: "" }]); setListSubBidangSelected([...listSubBidangSelected, { id: "" }]);
}} }}

View File

@@ -40,14 +40,14 @@ export default function Portofolio() {
const response = await apiGetOnePortofolio({ id: id }); const response = await apiGetOnePortofolio({ id: id });
console.log( console.log(
"Response portofolio >>", "Response portofolio >>",
JSON.stringify(response.data.namaBisnis, null, 2) JSON.stringify(response.data, null, 2)
); );
setData(response.data); setData(response.data);
} }
const userId = user?.id; const userId = user?.id;
const userLoginId = data?.Profile?.User?.id; const userLoginId = data?.Profile?.userId
console.log("User ID >>", userId); console.log("User ID >>", userId);
console.log("User Login ID >>", userLoginId); console.log("User Login ID >>", userLoginId);
@@ -75,7 +75,7 @@ export default function Portofolio() {
<ViewWrapper> <ViewWrapper>
{/* <PorfofolioSection setShowDeleteAlert={setDeleteAlert} data={data} /> */} {/* <PorfofolioSection setShowDeleteAlert={setDeleteAlert} data={data} /> */}
<StackCustom> <StackCustom>
<Portofolio_Data data={data} /> <Portofolio_Data data={data} listSubBidang={data?.Portofolio_BidangDanSubBidangBisnis as any []} />
<Portofolio_BusinessLocation /> <Portofolio_BusinessLocation />
<Portofolio_SocialMediaSection data={data?.Portofolio_MediaSosial} /> <Portofolio_SocialMediaSection data={data?.Portofolio_MediaSosial} />
<Portofolio_ButtonDelete <Portofolio_ButtonDelete

View File

@@ -9,11 +9,13 @@ export default function ActionIcon({
onPress, onPress,
icon, icon,
size = "md", size = "md",
disabled = false,
}: { }: {
href?: Href; href?: Href;
onPress?: () => void; onPress?: () => void;
icon: React.ReactNode; icon: React.ReactNode;
size?: SizeType; size?: SizeType;
disabled?: boolean;
}) { }) {
const sizeMap = { const sizeMap = {
xs: 22, xs: 22,
@@ -25,7 +27,7 @@ export default function ActionIcon({
const getSize = (size: SizeType): DimensionValue => { const getSize = (size: SizeType): DimensionValue => {
if (!size) return sizeMap.md; // Default to 'md' if size is undefined if (!size) return sizeMap.md; // Default to 'md' if size is undefined
if (typeof size === 'string' && size in sizeMap) { if (typeof size === "string" && size in sizeMap) {
return sizeMap[size as keyof typeof sizeMap]; return sizeMap[size as keyof typeof sizeMap];
} }
return size as DimensionValue; return size as DimensionValue;
@@ -35,9 +37,10 @@ export default function ActionIcon({
return ( return (
<TouchableOpacity <TouchableOpacity
disabled={disabled}
activeOpacity={0.7} activeOpacity={0.7}
style={{ style={{
backgroundColor: MainColor.yellow, backgroundColor: disabled ? MainColor.disabled : MainColor.yellow,
padding: 5, padding: 5,
borderRadius: 50, borderRadius: 50,
alignItems: "center", alignItems: "center",
@@ -46,6 +49,7 @@ export default function ActionIcon({
height: iconSize, height: iconSize,
}} }}
onPress={() => { onPress={() => {
if (disabled) return;
if (href) { if (href) {
router.push(href); router.push(href);
} else { } else {

View File

@@ -14,7 +14,6 @@ import Toast from "react-native-toast-message";
export default function RegisterView() { export default function RegisterView() {
const { nomor } = useLocalSearchParams(); const { nomor } = useLocalSearchParams();
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
// const [loading, setLoading] = useState(false);
const { registerUser, isLoading } = useAuth(); const { registerUser, isLoading } = useAuth();
@@ -22,16 +21,22 @@ export default function RegisterView() {
if (!nomor) { if (!nomor) {
Toast.show({ Toast.show({
type: "error", type: "error",
text1: "Gagal", text1: "Lengkapi nomor",
text2: "Nomor tidak ditemukan",
}); });
return false; return false;
} }
if (!username) { if (!username) {
Toast.show({ Toast.show({
type: "error", type: "error",
text1: "Gagal", text1: "Username tidak boleh kosong",
text2: "Username tidak boleh kosong", });
return false;
}
if (username.includes(" ")) {
Toast.show({
type: "info",
text1: "Username tidak boleh mengandung spasi",
}); });
return false; return false;
} }
@@ -42,9 +47,20 @@ export default function RegisterView() {
const isValid = validasiData(); const isValid = validasiData();
if (!isValid) return; if (!isValid) return;
if (username.length < 5) {
Toast.show({
type: "info",
text1: "Info",
text2: "Username minimal 5 karakter",
});
return;
}
const usernameLower = username.toLowerCase();
await registerUser({ await registerUser({
nomor: nomor as string, nomor: nomor as string,
username: username, username: usernameLower,
}); });
} }
@@ -73,6 +89,11 @@ export default function RegisterView() {
placeholder="Masukkan username" placeholder="Masukkan username"
value={username} value={username}
onChangeText={(text) => setUsername(text)} onChangeText={(text) => setUsername(text)}
error={
username.includes(" ")
? "Username tidak boleh mengandung spasi"
: ""
}
/> />
<ButtonCustom isLoading={isLoading} onPress={handleRegister}> <ButtonCustom isLoading={isLoading} onPress={handleRegister}>

View File

@@ -45,7 +45,9 @@ export default function Portofolio_ButtonCreate({
!data.masterBidangBisnisId || !data.masterBidangBisnisId ||
!data.alamatKantor || !data.alamatKantor ||
!data.tlpn || !data.tlpn ||
!data.deskripsi !data.deskripsi ||
subBidangSelected.map((item) => item.id).includes("") ||
subBidangSelected.map((item) => item.id).includes(null)
) { ) {
return false; return false;
} }
@@ -53,6 +55,10 @@ export default function Portofolio_ButtonCreate({
}; };
const handleCreatePortofolio = async () => { const handleCreatePortofolio = async () => {
console.log(
"Data sub bidang >>",
JSON.stringify(subBidangSelected, null, 2)
);
if (!validaasiData()) { if (!validaasiData()) {
Toast.show({ Toast.show({
type: "info", type: "info",
@@ -90,18 +96,18 @@ export default function Portofolio_ButtonCreate({
youtube: dataMedsos.youtube, youtube: dataMedsos.youtube,
subBidang: subBidangSelected, subBidang: subBidangSelected,
}; };
const response = await apiPortofolioCreate({
try { data: newData,
const response = await apiPortofolioCreate({ });
data: newData, console.log("Response >>", JSON.stringify(response, null, 2));
}); // return router.replace(`/maps/create`);
console.log("Response >>", JSON.stringify(response, null, 2)); // return router.push(`/maps/create`);
// return router.replace(`/maps/create`); Toast.show({
// return router.push(`/maps/create`); type: "success",
return router.back(); text1: "Sukses",
} catch (error) { text2: "Data berhasil disimpan",
throw error; });
} return router.back();
} catch (error) { } catch (error) {
Toast.show({ Toast.show({
type: "error", type: "error",

View File

@@ -17,7 +17,13 @@ import { FontAwesome, Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { View } from "react-native"; import { View } from "react-native";
export default function Portofolio_Data({ data }: { data: any }) { export default function Portofolio_Data({
data,
listSubBidang,
}: {
data: any;
listSubBidang: any[];
}) {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const listData = [ const listData = [
@@ -83,6 +89,8 @@ export default function Portofolio_Data({ data }: { data: any }) {
// }, // },
// ]; // ];
console.log("List Sub Bidang >>", JSON.stringify(listSubBidang, null, 2));
return ( return (
<> <>
<BaseBox> <BaseBox>
@@ -137,20 +145,24 @@ export default function Portofolio_Data({ data }: { data: any }) {
</Grid.Col> </Grid.Col>
</Grid> </Grid>
))} ))}
{/* <View style={{ paddingLeft: 10 }}> <View style={{ paddingLeft: 10 }}>
{listSubBidang.map((item, index) => ( {listSubBidang?.map((item, index) => (
<Grid key={index}> <Grid key={index}>
<Grid.Col span={2} style={{ alignItems: "center" }}> <Grid.Col span={2} style={{ alignItems: "center" }}>
{item.icon} <Ionicons
name="chevron-forward-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
</Grid.Col> </Grid.Col>
<Grid.Col span={10}> <Grid.Col span={10}>
<TextCustom style={{ paddingLeft: 5 }}> <TextCustom style={{ paddingLeft: 5 }}>
{item.label} {item?.MasterSubBidangBisnis?.name || "-"}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
))} ))}
</View> */} </View>
</View> </View>
<DividerCustom labelPosition="top" color={AccentColor.blue} /> <DividerCustom labelPosition="top" color={AccentColor.blue} />

View File

@@ -9,12 +9,14 @@ export async function apiMasterBidangBisnis() {
} }
} }
export async function apiMasterSubBidangBisnis({id}: {id: string}) { export async function apiMasterSubBidangBisnis({ id }: { id?: string }) {
try { try {
const response = await apiConfig.get(`/master/sub-bidang-bisnis/${id}`); const selectBidangId = id ? `/${id}` : "";
const response = await apiConfig.get(
`/master/sub-bidang-bisnis${selectBidangId}`
);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;
} }
} }

View File

@@ -14,4 +14,5 @@ export interface IMasterSubBidangBisnis {
isActive: boolean; isActive: boolean;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
masterBidangBisnisId: string;
} }