Refactor pagination implementation dan perbaikan UI
- Add default page parameter di apiAllUser - Refactor MainView_V2.tsx dengan separate render functions - Update pagination pageSize menjadi 10 di Forum - Fix iOS height constant dan tab styling - Rename Admin_ScreenPortofolioCreate ke ScreenPortofolioCreate - Add TASKS documentation folder Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
337
TASKS/crowdfunding-redesign.md
Normal file
337
TASKS/crowdfunding-redesign.md
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
# Crowdfunding Page Redesign - Modern UI Enhancement
|
||||||
|
|
||||||
|
## 📋 Ringkasan Task
|
||||||
|
Redesign halaman Crowdfunding (`app/(application)/(user)/crowdfunding/index.tsx`) untuk menciptakan tampilan yang lebih modern, menarik, dan user-friendly dengan visual hierarchy yang lebih baik.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Tujuan
|
||||||
|
1. Meningkatkan visual appeal dengan desain card yang modern
|
||||||
|
2. Memperbaiki spacing dan layout untuk readability yang lebih baik
|
||||||
|
3. Menambahkan elemen visual (icon, color accent) untuk navigasi yang lebih intuitif
|
||||||
|
4. Menciptakan konsistensi dengan design system aplikasi HIPMI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 File yang Terlibat
|
||||||
|
|
||||||
|
### File Utama
|
||||||
|
- **Target**: `app/(application)/(user)/crowdfunding/index.tsx`
|
||||||
|
|
||||||
|
### Komponen yang Mungkin Digunakan
|
||||||
|
- `ViewWrapper` - Wrapper utama
|
||||||
|
- `StackCustom` - Layout vertikal
|
||||||
|
- `Grid` & `Grid.Col` - Layout horizontal
|
||||||
|
- `TextCustom` - Typography
|
||||||
|
- `BaseBox` / `AdminBasicBox` - Card container
|
||||||
|
- `ClickableCustom` - Interactive element
|
||||||
|
- `Feather` / `Ionicons` - Icons
|
||||||
|
|
||||||
|
### Constants
|
||||||
|
- `MainColor` - Color palette
|
||||||
|
- `ICON_SIZE_SMALL`, `ICON_SIZE_BASE` - Icon sizing
|
||||||
|
|
||||||
|
### Assets
|
||||||
|
- `@/assets/images/constants/crowd-hipmi.png` - Header image
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Analisis Kondisi Saat Ini
|
||||||
|
|
||||||
|
### Kelemahan Design Sekarang
|
||||||
|
1. **Header Image**
|
||||||
|
- Plain image tanpa overlay atau title
|
||||||
|
- Tidak ada visual hierarchy
|
||||||
|
|
||||||
|
2. **Card List**
|
||||||
|
- BaseBox terlalu simple, kurang depth
|
||||||
|
- Tidak ada shadow atau elevation
|
||||||
|
- Border radius mungkin kurang smooth
|
||||||
|
- Spacing antar elemen kurang konsisten
|
||||||
|
|
||||||
|
3. **Typography**
|
||||||
|
- Judul dan deskripsi kurang kontras
|
||||||
|
- Tidak ada visual emphasis yang kuat
|
||||||
|
|
||||||
|
4. **Navigation**
|
||||||
|
- Chevron icon terlalu plain
|
||||||
|
- Tidak ada visual feedback saat hover/press
|
||||||
|
|
||||||
|
5. **Color Usage**
|
||||||
|
- Kurang color accent untuk membedakan sections
|
||||||
|
- Monoton dengan warna yang ada
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Rencana Desain
|
||||||
|
|
||||||
|
### 1. Header Section Enhancement
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ [Hero Image dengan Overlay] │
|
||||||
|
│ ┌─────────────────────────────┐ │
|
||||||
|
│ │ Crowdfunding │ │
|
||||||
|
│ │ Platform Investasi & Donasi │ │
|
||||||
|
│ └─────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementasi:**
|
||||||
|
- Image dengan overlay gradient untuk text readability
|
||||||
|
- Title "Crowdfunding" dengan subtitle
|
||||||
|
- Rounded corners dengan overflow hidden
|
||||||
|
- Height: ~180-200px
|
||||||
|
|
||||||
|
### 2. Card Design Modern
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ [Icon] Investasi → │
|
||||||
|
│ Deskripsi singkat... │
|
||||||
|
│ (2-3 baris max) │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementasi:**
|
||||||
|
- Card dengan:
|
||||||
|
- Background gradient atau solid color dengan tint
|
||||||
|
- Shadow/elevation untuk depth
|
||||||
|
- Border radius: 12-16px
|
||||||
|
- Padding: 16-20px
|
||||||
|
- Icon di kiri (Investasi & Donasi)
|
||||||
|
- Chevron di kanan dengan style yang lebih modern
|
||||||
|
|
||||||
|
### 3. Icon Integration
|
||||||
|
- **Investasi**: Icon grafik/trending (contoh: `trending-up`, `pie-chart`)
|
||||||
|
- **Donasi**: Icon hati/tangan (contoh: `heart`, `hand-heart`)
|
||||||
|
- Size: 40-48px untuk icon card
|
||||||
|
- Background icon: Circle dengan color accent
|
||||||
|
|
||||||
|
### 4. Color Scheme
|
||||||
|
```
|
||||||
|
Investasi:
|
||||||
|
- Primary: Blue/Teal gradient
|
||||||
|
- Accent: Soft blue background
|
||||||
|
- Icon: White on blue
|
||||||
|
|
||||||
|
Donasi:
|
||||||
|
- Primary: Orange/Red gradient
|
||||||
|
- Accent: Soft orange background
|
||||||
|
- Icon: White on orange
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Typography Hierarchy
|
||||||
|
```
|
||||||
|
Header Title: bold, x-large (20-22px)
|
||||||
|
Header Subtitle: regular, small (14-15px), gray
|
||||||
|
Card Title: bold, large (17-18px)
|
||||||
|
Card Description: regular, base (14px), gray
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Spacing & Layout
|
||||||
|
```
|
||||||
|
Container padding: 16px
|
||||||
|
Card margin bottom: 12-16px
|
||||||
|
Card internal padding: 16px
|
||||||
|
Gap between elements: 8-12px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Breakdown Task
|
||||||
|
|
||||||
|
### Task 1: Persiapan & Research
|
||||||
|
- [ ] Review komponen yang tersedia di `components/`
|
||||||
|
- [ ] Cek color palette di `constants/color-palet.ts`
|
||||||
|
- [ ] Identifikasi icon yang tersedia (Feather, Ionicons)
|
||||||
|
- [ ] Screenshot design sekarang untuk perbandingan
|
||||||
|
|
||||||
|
### Task 2: Setup Structure
|
||||||
|
- [ ] Buat constant untuk list menu (pindahkan dari component body)
|
||||||
|
- [ ] Tambahkan icon mapping untuk setiap menu item
|
||||||
|
- [ ] Setup color scheme untuk setiap card
|
||||||
|
|
||||||
|
### Task 3: Header Redesign
|
||||||
|
- [ ] Buat container dengan overflow hidden
|
||||||
|
- [ ] Tambahkan image dengan overlay gradient
|
||||||
|
- [ ] Tambahkan title "Crowdfunding" dengan text white
|
||||||
|
- [ ] Tambahkan subtitle (opsional)
|
||||||
|
- [ ] Test di berbagai ukuran layar
|
||||||
|
|
||||||
|
### Task 4: Card Component
|
||||||
|
- [ ] Buat custom card component atau modify BaseBox
|
||||||
|
- [ ] Tambahkan shadow/elevation
|
||||||
|
- [ ] Tambahkan border radius yang smooth
|
||||||
|
- [ ] Setup gradient background (opsional)
|
||||||
|
- [ ] Tambahkan visual feedback saat press
|
||||||
|
|
||||||
|
### Task 5: Icon Integration
|
||||||
|
- [ ] Pilih icon yang sesuai untuk setiap menu
|
||||||
|
- [ ] Buat icon container dengan background color
|
||||||
|
- [ ] Setup icon size dan positioning
|
||||||
|
- [ ] Test visibility di berbagai device
|
||||||
|
|
||||||
|
### Task 6: Typography & Content
|
||||||
|
- [ ] Apply text hierarchy (title, subtitle, desc)
|
||||||
|
- [ ] Truncate description jika terlalu panjang (max 2-3 baris)
|
||||||
|
- [ ] Ensure text contrast yang baik
|
||||||
|
- [ ] Test dengan text panjang
|
||||||
|
|
||||||
|
### Task 7: Polish & Refinement
|
||||||
|
- [ ] Adjust spacing dan padding
|
||||||
|
- [ ] Test di light/dark mode (jika applicable)
|
||||||
|
- [ ] Test di berbagai ukuran layar (responsive)
|
||||||
|
- [ ] Add smooth transitions/animations
|
||||||
|
|
||||||
|
### Task 8: Testing
|
||||||
|
- [ ] Test navigation ke setiap halaman
|
||||||
|
- [ ] Test di iOS simulator
|
||||||
|
- [ ] Test di Android emulator
|
||||||
|
- [ ] Test di device fisik (jika memungkinkan)
|
||||||
|
- [ ] Check performance (no lag saat scroll)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Implementation Guidelines
|
||||||
|
|
||||||
|
### Code Structure Example
|
||||||
|
```typescript
|
||||||
|
// Constants
|
||||||
|
const CROWDFUNDING_MENU = [
|
||||||
|
{
|
||||||
|
title: "Investasi",
|
||||||
|
desc: "Buat investasi dan jual beli saham lebih mudah dengan pengguna lain.",
|
||||||
|
path: "investment/(tabs)",
|
||||||
|
icon: "trending-up",
|
||||||
|
color: MainColor.blue,
|
||||||
|
gradient: ["#667eea", "#764ba2"],
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
];
|
||||||
|
|
||||||
|
// Component
|
||||||
|
export default function Crowdfunding() {
|
||||||
|
const renderHeader = () => (
|
||||||
|
<View style={styles.headerContainer}>
|
||||||
|
<Image source={...} style={styles.headerImage} />
|
||||||
|
<View style={styles.headerOverlay}>
|
||||||
|
<TextCustom bold size="x-large">Crowdfunding</TextCustom>
|
||||||
|
<TextCustom>Platform Investasi & Donasi</TextCustom>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderCard = (item: CrowdfundingMenuItem) => (
|
||||||
|
<ClickableCustom onPress={...} style={styles.card}>
|
||||||
|
<View style={[styles.iconContainer, { backgroundColor: item.color }]}>
|
||||||
|
<Feather name={item.icon} size={24} color={MainColor.white} />
|
||||||
|
</View>
|
||||||
|
<Grid.Col span={8}>
|
||||||
|
<TextCustom bold size="large">{item.title}</TextCustom>
|
||||||
|
<TextCustom numberOfLines={2}>{item.desc}</TextCustom>
|
||||||
|
</Grid.Col>
|
||||||
|
<Feather name="chevron-right" size={ICON_SIZE_SMALL} />
|
||||||
|
</ClickableCustom>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewWrapper>
|
||||||
|
<StackCustom>
|
||||||
|
{renderHeader()}
|
||||||
|
{CROWDFUNDING_MENU.map(renderCard)}
|
||||||
|
</StackCustom>
|
||||||
|
</ViewWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Style Guidelines
|
||||||
|
```typescript
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
headerContainer: {
|
||||||
|
position: 'relative',
|
||||||
|
borderRadius: 16,
|
||||||
|
overflow: 'hidden',
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
headerOverlay: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
padding: 16,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.6)',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: MainColor.white,
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 12,
|
||||||
|
elevation: 4,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 4,
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 24,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Acceptance Criteria
|
||||||
|
|
||||||
|
### Visual
|
||||||
|
- [ ] Header dengan overlay text yang readable
|
||||||
|
- [ ] Card dengan shadow/elevation yang jelas
|
||||||
|
- [ ] Icon untuk setiap menu item
|
||||||
|
- [ ] Color accent yang berbeda untuk Investasi & Donasi
|
||||||
|
- [ ] Typography hierarchy yang jelas
|
||||||
|
|
||||||
|
### Functional
|
||||||
|
- [ ] Navigation ke halaman tujuan berfungsi
|
||||||
|
- [ ] Responsive di berbagai ukuran layar
|
||||||
|
- [ ] No console errors atau warnings
|
||||||
|
- [ ] Smooth scroll (60fps)
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- [ ] Code terorganisir dengan baik
|
||||||
|
- [ ] Components terpisah untuk reusability
|
||||||
|
- [ ] Constants untuk data statis
|
||||||
|
- [ ] Comments untuk logic yang kompleks
|
||||||
|
- [ ] TypeScript types yang proper
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Estimated Effort
|
||||||
|
- **Complexity**: Low-Medium
|
||||||
|
- **Time Estimate**: 2-3 jam
|
||||||
|
- **Risk Level**: Low (tidak ada breaking changes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 References
|
||||||
|
- Design Reference: `screens/Forum/ViewBeranda3.tsx` (untuk pagination pattern)
|
||||||
|
- Color Palette: `constants/color-palet.ts`
|
||||||
|
- Icons: Feather Icons, Ionicons
|
||||||
|
- Components: `components/_ShareComponent/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Notes
|
||||||
|
- Pastikan backward compatibility (tidak breaking existing features)
|
||||||
|
- Test di device dengan screen size berbeda
|
||||||
|
- Consider accessibility (text contrast, touch target size)
|
||||||
|
- Jika ada time, tambahkan micro-interactions (scale on press, dll)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Created**: 2026-03-25
|
||||||
|
**Status**: Pending
|
||||||
|
**Priority**: Medium
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { BasicWrapper, StackCustom, ViewWrapper } from "@/components";
|
import { BasicWrapper, NewWrapper, StackCustom, ViewWrapper } from "@/components";
|
||||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
@@ -148,7 +148,7 @@ export default function Application() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ViewWrapper
|
<NewWrapper
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl
|
<RefreshControl
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
@@ -166,18 +166,19 @@ export default function Application() {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<View style={GStyles.tabBar}>
|
null
|
||||||
<View style={[GStyles.tabContainer, { paddingTop: 10 }]}>
|
// <View style={GStyles.tabBar}>
|
||||||
{Array.from({ length: 4 }).map((e, index) => (
|
// <View style={[GStyles.tabContainer, { paddingTop: 10 }]}>
|
||||||
<CustomSkeleton
|
// {Array.from({ length: 4 }).map((e, index) => (
|
||||||
key={index}
|
// <CustomSkeleton
|
||||||
height={40}
|
// key={index}
|
||||||
width={40}
|
// height={40}
|
||||||
radius={100}
|
// width={40}
|
||||||
/>
|
// radius={100}
|
||||||
))}
|
// />
|
||||||
</View>
|
// ))}
|
||||||
</View>
|
// </View>
|
||||||
|
// </View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -201,10 +202,10 @@ export default function Application() {
|
|||||||
{data ? (
|
{data ? (
|
||||||
<Home_BottomFeatureSection listData={listData} />
|
<Home_BottomFeatureSection listData={listData} />
|
||||||
) : (
|
) : (
|
||||||
<CustomSkeleton height={200} />
|
<CustomSkeleton height={150} />
|
||||||
)}
|
)}
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
</ViewWrapper>
|
</NewWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Admin_ScreenPortofolioCreate } from "@/screens/Portofolio/ScreenPortofolioCreate";
|
import { ScreenPortofolioCreate } from "@/screens/Portofolio/ScreenPortofolioCreate";
|
||||||
|
|
||||||
export default function PortofolioCreate() {
|
export default function PortofolioCreate() {
|
||||||
return <Admin_ScreenPortofolioCreate />;
|
return <ScreenPortofolioCreate />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
} from "react-native-safe-area-context";
|
} from "react-native-safe-area-context";
|
||||||
import type { ScrollViewProps, FlatListProps } from "react-native";
|
import type { ScrollViewProps, FlatListProps } from "react-native";
|
||||||
|
import Spacing from "./Spacing";
|
||||||
|
|
||||||
// --- ✅ Tambahkan refreshControl ke BaseProps ---
|
// --- ✅ Tambahkan refreshControl ke BaseProps ---
|
||||||
interface BaseProps {
|
interface BaseProps {
|
||||||
@@ -111,7 +112,6 @@ const NewWrapper = (props: NewWrapperProps) => {
|
|||||||
return `${String(item.id)}-${index}`;
|
return `${String(item.id)}-${index}`;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshControl={refreshControl} // ✅ dari BaseProps
|
refreshControl={refreshControl} // ✅ dari BaseProps
|
||||||
onEndReached={listProps.onEndReached}
|
onEndReached={listProps.onEndReached}
|
||||||
onEndReachedThreshold={0.5}
|
onEndReachedThreshold={0.5}
|
||||||
@@ -156,15 +156,27 @@ const NewWrapper = (props: NewWrapperProps) => {
|
|||||||
<View style={GStyles.stickyHeader}>{headerComponent}</View>
|
<View style={GStyles.stickyHeader}>{headerComponent}</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ScrollView
|
<View style={{ flex: 0 }} collapsable={false}>
|
||||||
contentContainerStyle={{ flexGrow: 1 }}
|
<ScrollView
|
||||||
|
contentContainerStyle={{ flexGrow: 1 }}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
refreshControl={refreshControl} // ✅ sekarang valid
|
||||||
|
>
|
||||||
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
|
{renderContainer(staticProps.children)}
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* <ScrollView
|
||||||
|
contentContainerStyle={{ flexGrow: 0 }}
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
refreshControl={refreshControl} // ✅ sekarang valid
|
refreshControl={refreshControl} // ✅ sekarang valid
|
||||||
>
|
>
|
||||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
{renderContainer(staticProps.children)}
|
{renderContainer(staticProps.children)}
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
</ScrollView>
|
</ScrollView> */}
|
||||||
|
|
||||||
{footerComponent ? (
|
{footerComponent ? (
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export {
|
|||||||
|
|
||||||
// OS Height
|
// OS Height
|
||||||
const OS_ANDROID_HEIGHT = 115
|
const OS_ANDROID_HEIGHT = 115
|
||||||
const OS_IOS_HEIGHT = 90
|
const OS_IOS_HEIGHT = 80
|
||||||
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
|
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
|
||||||
|
|
||||||
// Text Size
|
// Text Size
|
||||||
|
|||||||
@@ -197,6 +197,7 @@
|
|||||||
D5CA1D54CFF74AB4B8B5B583 /* Remove signature files (Xcode workaround) */,
|
D5CA1D54CFF74AB4B8B5B583 /* Remove signature files (Xcode workaround) */,
|
||||||
97C01196E2194AF5A13C7773 /* Remove signature files (Xcode workaround) */,
|
97C01196E2194AF5A13C7773 /* Remove signature files (Xcode workaround) */,
|
||||||
EB19F4C53C8B434CBAD50897 /* Remove signature files (Xcode workaround) */,
|
EB19F4C53C8B434CBAD50897 /* Remove signature files (Xcode workaround) */,
|
||||||
|
95ABFC1FE48F4F2ABAF407D8 /* Remove signature files (Xcode workaround) */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -1247,6 +1248,23 @@
|
|||||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||||
";
|
";
|
||||||
};
|
};
|
||||||
|
95ABFC1FE48F4F2ABAF407D8 /* Remove signature files (Xcode workaround) */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
name = "Remove signature files (Xcode workaround)";
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "
|
||||||
|
echo \"Remove signature files (Xcode workaround)\";
|
||||||
|
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||||
|
";
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NewWrapper } from "@/components";
|
import { NewWrapper, ViewWrapper } from "@/components";
|
||||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||||
import ModalReactNative from "@/components/Modal/ModalReactNative";
|
import ModalReactNative from "@/components/Modal/ModalReactNative";
|
||||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||||
@@ -128,7 +128,7 @@ export default function LoginView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewWrapper
|
<ViewWrapper
|
||||||
withBackground
|
withBackground
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
|
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
|
||||||
@@ -205,6 +205,6 @@ export default function LoginView() {
|
|||||||
setLoadingTerm={setLoadingTerm}
|
setLoadingTerm={setLoadingTerm}
|
||||||
/>
|
/>
|
||||||
</ModalReactNative>
|
</ModalReactNative>
|
||||||
</NewWrapper>
|
</ViewWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import _ from "lodash";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { RefreshControl, TouchableOpacity, View } from "react-native";
|
import { RefreshControl, TouchableOpacity, View } from "react-native";
|
||||||
|
|
||||||
const PAGE_SIZE = 5;
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
export default function Forum_ViewBeranda3() {
|
export default function Forum_ViewBeranda3() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const stylesHome = StyleSheet.create({
|
|||||||
jobVacancyHeader: {
|
jobVacancyHeader: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginBottom: 16,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
jobVacancyTitle: {
|
jobVacancyTitle: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { router } from "expo-router";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Text, TouchableOpacity, View } from "react-native";
|
import { Text, TouchableOpacity, View } from "react-native";
|
||||||
|
|
||||||
|
|
||||||
const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
|
const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[GStyles.tabItem, isActive && GStyles.activeTab]}
|
style={[GStyles.tabItem, isActive && GStyles.activeTab]}
|
||||||
@@ -17,7 +16,7 @@ const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
|
|||||||
>
|
>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name={icon as any}
|
name={icon as any}
|
||||||
size={20}
|
size={18}
|
||||||
color={isActive ? "#fff" : "#666"}
|
color={isActive ? "#fff" : "#666"}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -30,8 +29,8 @@ const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
|
|||||||
export default function TabSection({ tabs }: { tabs: ITabs[] }) {
|
export default function TabSection({ tabs }: { tabs: ITabs[] }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={GStyles.tabBar}>
|
<View style={GStyles.tabBar} pointerEvents="box-none">
|
||||||
<View style={GStyles.tabContainer}>
|
<View style={GStyles.tabContainer} pointerEvents="box-none">
|
||||||
{tabs.map((e) => (
|
{tabs.map((e) => (
|
||||||
<CustomTab
|
<CustomTab
|
||||||
key={e.id}
|
key={e.id}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import { Text, 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";
|
||||||
|
|
||||||
export function Admin_ScreenPortofolioCreate() {
|
export function ScreenPortofolioCreate() {
|
||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
||||||
const [inputValue, setInputValue] = useState<string>("");
|
const [inputValue, setInputValue] = useState<string>("");
|
||||||
@@ -72,7 +72,7 @@ export function Admin_ScreenPortofolioCreate() {
|
|||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
onLoadMaster();
|
onLoadMaster();
|
||||||
onLoadMasterSubBidangBisnis();
|
onLoadMasterSubBidangBisnis();
|
||||||
}, [])
|
}, []),
|
||||||
);
|
);
|
||||||
|
|
||||||
const onLoadMaster = async () => {
|
const onLoadMaster = async () => {
|
||||||
@@ -97,7 +97,7 @@ export function Admin_ScreenPortofolioCreate() {
|
|||||||
|
|
||||||
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
|
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
|
||||||
const selectedList = subBidangBisnis?.filter(
|
const selectedList = subBidangBisnis?.filter(
|
||||||
(item) => (item?.masterBidangBisnisId as any) === id
|
(item) => (item?.masterBidangBisnisId as any) === id,
|
||||||
);
|
);
|
||||||
setSelectedSubBidang(selectedList as any[]);
|
setSelectedSubBidang(selectedList as any[]);
|
||||||
};
|
};
|
||||||
@@ -168,8 +168,7 @@ export function Admin_ScreenPortofolioCreate() {
|
|||||||
.filter((option: any) => {
|
.filter((option: any) => {
|
||||||
const selectedValues = listSubBidangSelected.map((s) => s.id);
|
const selectedValues = listSubBidangSelected.map((s) => s.id);
|
||||||
return (
|
return (
|
||||||
option.id === item.id ||
|
option.id === item.id || !selectedValues.includes(option.id)
|
||||||
!selectedValues.includes(option.id)
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((e: any) => ({
|
.map((e: any) => ({
|
||||||
@@ -188,7 +187,9 @@ export function Admin_ScreenPortofolioCreate() {
|
|||||||
<CenterCustom>
|
<CenterCustom>
|
||||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
disabled={selectedSubBidang.length === listSubBidangSelected.length}
|
disabled={
|
||||||
|
selectedSubBidang.length === listSubBidangSelected.length
|
||||||
|
}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setListSubBidangSelected([
|
setListSubBidangSelected([
|
||||||
...listSubBidangSelected,
|
...listSubBidangSelected,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
ICON_SIZE_SMALL,
|
ICON_SIZE_SMALL,
|
||||||
PAGINATION_DEFAULT_TAKE,
|
PAGINATION_DEFAULT_TAKE,
|
||||||
} from "@/constants/constans-value";
|
} from "@/constants/constans-value";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
import { usePagination } from "@/hooks/use-pagination";
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
import { apiAllUser } from "@/service/api-client/api-user";
|
import { apiAllUser } from "@/service/api-client/api-user";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
@@ -19,21 +20,89 @@ import { router, useFocusEffect } from "expo-router";
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { useCallback, useRef, useState } from "react";
|
import { useCallback, useRef, useState } from "react";
|
||||||
import { RefreshControl, View } from "react-native";
|
import { RefreshControl, View } from "react-native";
|
||||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
|
||||||
|
const PAGE_SIZE = PAGINATION_DEFAULT_TAKE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render header dengan search input
|
||||||
|
*/
|
||||||
|
const renderHeader = (search: string, setSearch: (text: string) => void) => (
|
||||||
|
<TextInputCustom
|
||||||
|
value={search}
|
||||||
|
onChangeText={setSearch}
|
||||||
|
iconLeft={
|
||||||
|
<Ionicons
|
||||||
|
name="search"
|
||||||
|
size={ICON_SIZE_SMALL}
|
||||||
|
color={MainColor.placeholder}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placeholder="Cari Pengguna"
|
||||||
|
borderRadius={50}
|
||||||
|
containerStyle={{ marginBottom: 0 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render item user
|
||||||
|
*/
|
||||||
|
const renderItem = ({ item }: { item: any }) => (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
backgroundColor: MainColor.soft_darkblue,
|
||||||
|
borderRadius: 8,
|
||||||
|
padding: 12,
|
||||||
|
marginBottom: 10,
|
||||||
|
elevation: 2,
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClickableCustom
|
||||||
|
onPress={() => {
|
||||||
|
router.push(`/profile/${item?.Profile?.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span={2}>
|
||||||
|
<AvatarComp fileId={item?.Profile?.imageId} size="base" />
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={9}>
|
||||||
|
<StackCustom gap={"sm"}>
|
||||||
|
<TextCustom size="large">{item?.username}</TextCustom>
|
||||||
|
<TextCustom size="small">+{item?.nomor}</TextCustom>
|
||||||
|
{item?.Profile?.businessField && (
|
||||||
|
<TextCustom size="small">
|
||||||
|
{item?.Profile?.businessField}
|
||||||
|
</TextCustom>
|
||||||
|
)}
|
||||||
|
</StackCustom>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col
|
||||||
|
span={1}
|
||||||
|
style={{
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="chevron-forward"
|
||||||
|
size={ICON_SIZE_SMALL}
|
||||||
|
color={MainColor.placeholder}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</ClickableCustom>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
export default function UserSearchMainView_V2() {
|
export default function UserSearchMainView_V2() {
|
||||||
const isInitialMount = useRef(true);
|
const isInitialMount = useRef(true);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
const {
|
const pagination = usePagination({
|
||||||
listData,
|
|
||||||
loading,
|
|
||||||
refreshing,
|
|
||||||
hasMore,
|
|
||||||
onRefresh,
|
|
||||||
loadMore,
|
|
||||||
isInitialLoad,
|
|
||||||
} = usePagination({
|
|
||||||
fetchFunction: async (page, searchQuery) => {
|
fetchFunction: async (page, searchQuery) => {
|
||||||
const response = await apiAllUser({
|
const response = await apiAllUser({
|
||||||
page: String(page),
|
page: String(page),
|
||||||
@@ -41,7 +110,7 @@ export default function UserSearchMainView_V2() {
|
|||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
pageSize: PAGE_SIZE,
|
||||||
searchQuery: search,
|
searchQuery: search,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,119 +118,42 @@ export default function UserSearchMainView_V2() {
|
|||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
if (isInitialMount.current) {
|
if (isInitialMount.current) {
|
||||||
// Skip saat pertama kali mount
|
|
||||||
isInitialMount.current = false;
|
isInitialMount.current = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Hanya refresh saat kembali dari screen lain
|
pagination.onRefresh();
|
||||||
onRefresh();
|
}, [pagination.onRefresh]),
|
||||||
}, [onRefresh]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderHeader = () => (
|
|
||||||
<>
|
|
||||||
<TextInputCustom
|
|
||||||
value={search}
|
|
||||||
onChangeText={setSearch}
|
|
||||||
iconLeft={
|
|
||||||
<Ionicons
|
|
||||||
name="search"
|
|
||||||
size={ICON_SIZE_SMALL}
|
|
||||||
color={MainColor.placeholder}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
placeholder="Cari Pengguna"
|
|
||||||
borderRadius={50}
|
|
||||||
containerStyle={{ marginBottom: 0 }}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: any }) => (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: MainColor.soft_darkblue,
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 12,
|
|
||||||
marginBottom: 10,
|
|
||||||
elevation: 2,
|
|
||||||
shadowColor: "#000",
|
|
||||||
shadowOffset: { width: 0, height: 1 },
|
|
||||||
shadowOpacity: 0.2,
|
|
||||||
shadowRadius: 2,
|
|
||||||
// height: 100
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ClickableCustom
|
|
||||||
onPress={() => {
|
|
||||||
console.log("Ke Profile");
|
|
||||||
router.push(`/profile/${item?.Profile?.id}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Grid>
|
|
||||||
<Grid.Col span={2}>
|
|
||||||
<AvatarComp fileId={item?.Profile?.imageId} size="base" />
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col span={9}>
|
|
||||||
<StackCustom gap={"sm"}>
|
|
||||||
<TextCustom size="large">{item?.username}</TextCustom>
|
|
||||||
<TextCustom size="small">+{item?.nomor}</TextCustom>
|
|
||||||
{item?.Profile?.businessField && (
|
|
||||||
<TextCustom size="small">
|
|
||||||
{item?.Profile?.businessField}
|
|
||||||
</TextCustom>
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col
|
|
||||||
span={1}
|
|
||||||
style={{
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "flex-end",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Ionicons
|
|
||||||
name="chevron-forward"
|
|
||||||
size={ICON_SIZE_SMALL}
|
|
||||||
color={MainColor.placeholder}
|
|
||||||
/>
|
|
||||||
</Grid.Col>
|
|
||||||
</Grid>
|
|
||||||
</ClickableCustom>
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { ListEmptyComponent, ListFooterComponent } =
|
const { ListEmptyComponent, ListFooterComponent } =
|
||||||
createPaginationComponents({
|
createPaginationComponents({
|
||||||
loading,
|
loading: pagination.loading,
|
||||||
refreshing,
|
refreshing: pagination.refreshing,
|
||||||
listData,
|
listData: pagination.listData,
|
||||||
searchQuery: search,
|
searchQuery: search,
|
||||||
emptyMessage: "Tidak ada pengguna ditemukan",
|
emptyMessage: "Tidak ada pengguna ditemukan",
|
||||||
emptySearchMessage: "Tidak ada hasil pencarian",
|
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||||
skeletonHeight: 100,
|
skeletonHeight: 100,
|
||||||
loadingFooterText: "Memuat lebih banyak pengguna...",
|
loadingFooterText: "Memuat lebih banyak pengguna...",
|
||||||
isInitialLoad,
|
isInitialLoad: pagination.isInitialLoad,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<NewWrapper
|
||||||
<NewWrapper
|
headerComponent={renderHeader(search, setSearch)}
|
||||||
headerComponent={renderHeader()}
|
listData={pagination.listData}
|
||||||
listData={listData}
|
renderItem={renderItem}
|
||||||
renderItem={renderItem}
|
onEndReached={pagination.loadMore}
|
||||||
onEndReached={loadMore}
|
refreshControl={
|
||||||
refreshControl={
|
<RefreshControl
|
||||||
<RefreshControl
|
progressBackgroundColor={MainColor.yellow}
|
||||||
progressBackgroundColor={MainColor.yellow}
|
refreshing={pagination.refreshing}
|
||||||
refreshing={refreshing}
|
onRefresh={pagination.onRefresh}
|
||||||
onRefresh={onRefresh}
|
/>
|
||||||
/>
|
}
|
||||||
}
|
ListFooterComponent={ListFooterComponent}
|
||||||
ListFooterComponent={ListFooterComponent}
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
ListEmptyComponent={ListEmptyComponent}
|
/>
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ export async function apiUser(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function apiAllUser({
|
export async function apiAllUser({
|
||||||
page,
|
page = "1",
|
||||||
search,
|
search,
|
||||||
}: {
|
}: {
|
||||||
page?: string;
|
page?: string;
|
||||||
search?: string;
|
search?: string;
|
||||||
}) {
|
}) {
|
||||||
const pageQuery = page ? `?page=${page}` : "";
|
const pageQuery = `?page=${page}`;
|
||||||
const searchQuery = search ? `&search=${search}` : "";
|
const searchQuery = search ? `&search=${search}` : "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export const GStyles = StyleSheet.create({
|
|||||||
transform: [{ scale: 1.05 }],
|
transform: [{ scale: 1.05 }],
|
||||||
},
|
},
|
||||||
iconContainer: {
|
iconContainer: {
|
||||||
padding: 8,
|
padding: 5,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
// marginBottom: 4,
|
// marginBottom: 4,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const TabsStyles: BottomTabNavigationOptions = {
|
|||||||
tabBarStyle: Platform.select({
|
tabBarStyle: Platform.select({
|
||||||
ios: {
|
ios: {
|
||||||
borderTopWidth: 0,
|
borderTopWidth: 0,
|
||||||
paddingTop: 5,
|
paddingTop: 12,
|
||||||
height: OS_IOS_HEIGHT,
|
height: OS_IOS_HEIGHT,
|
||||||
},
|
},
|
||||||
android: {
|
android: {
|
||||||
|
|||||||
Reference in New Issue
Block a user