Compare commits

...

88 Commits

Author SHA1 Message Date
bd82b7c427 Merge pull request 'amalia/26-sept-25' (#43) from amalia/26-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#43
2025-09-26 17:42:28 +08:00
a6c96105d2 upd: text input komentar
Deskripsi:
- android input komentar pada android

No Issues
2025-09-26 16:52:35 +08:00
14e9bf15c7 upd: dokumen divisi
Deskripsi:
- update akses role pada dokumen divisi

No Issues
2025-09-26 15:08:59 +08:00
907b56feaf upd: text input
Deskripsi:
- text input pada android

No Issues
2025-09-26 12:22:12 +08:00
2341a46992 upd: task divisi
Deskripsi:
- user role akses

No Issues
2025-09-26 11:03:55 +08:00
43a91c6481 Merge pull request 'amalia/25-sept-25' (#42) from amalia/25-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#42
2025-09-26 10:22:11 +08:00
ecc41c905f upd 2025-09-25 17:13:44 +08:00
65d53951c3 upd: caraousel kegiatan home
Deskripsi:
- loop false pada home caraousel kegiatan

No Issues
2025-09-25 11:27:51 +08:00
c2597b25bf upd: push notification
Deskripsi:
- update push notification warning

No Issues
2025-09-25 11:22:39 +08:00
f1b3eecbbe Merge pull request 'amalia/24-sept-25' (#41) from amalia/24-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#41
2025-09-24 17:39:23 +08:00
f042e32d98 upd: push notification ios
Deskripsi:
- push notification foreground dan background pada ios

No Issues
2025-09-24 17:37:00 +08:00
93c492ac71 fix: edit profile
Deskripsi:
- update keyboard avoiding pada edit profile

No Issues
2025-09-24 16:19:33 +08:00
6cca0a3d08 fix : multiline text input
Deskripsi:
- multiline pada text input komentar diskusi umum dan diskusi divisi

No Issues
2025-09-24 16:11:57 +08:00
bbb25a30d2 fix : create divisi
Deskripsi:
- router delete pada saat setelah tambah divisi

No Issues
2025-09-24 14:42:54 +08:00
46e269b45f fix: drawer bottom
Deskripsi:
- scrooll data pada drawer bottom

No Issues
2025-09-24 14:08:04 +08:00
4725d27f74 fix: list project
Deskripsi:
- filter pada setiap user role
- fitur filter disetiap user role

No Issues
2025-09-24 11:01:18 +08:00
ae74791a1c Merge pull request 'amalia/23-sept-25' (#40) from amalia/23-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#40
2025-09-23 17:47:13 +08:00
ba453ad027 upd: ios update 2025-09-23 13:55:50 +08:00
187e9dd19e upd: version code 2025-09-23 12:15:58 +08:00
040cab4f5e Merge pull request 'amalia/22-sept-25' (#39) from amalia/22-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#39
2025-09-22 17:44:20 +08:00
7442d01551 upd: text error login 2025-09-22 17:43:07 +08:00
180fbeede9 fix: project
Deskripsi:
- fix fungsi tambah project saat user role selain developer dan supadmin

No Issues
2025-09-22 17:33:24 +08:00
d0d40cb1a7 fix : tampilan
Deskripsi:
- update tampilan diskusi item > judul melebihi container

No Issues
2025-09-22 17:22:32 +08:00
8a25c2f672 Merge pull request 'amalia/12-sept-25' (#38) from amalia/12-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#38
2025-09-12 18:23:44 +08:00
d31c3677c9 upd: icon gambar 2025-09-12 16:35:56 +08:00
19b02ffc01 upd: easignore 2025-09-11 14:43:10 +08:00
b9b615636b fix: calendar
Deskripsi:
- tinggi scroll pada tambah data
- tinggi scroll pada edit tambah anggota
- checked anggota pada edit tambah anggota
- on press disable saat user telah menjadi anggota pada edit tambah anggota

No Issues
2025-09-11 11:44:34 +08:00
d3e7ef9623 Merge pull request 'amalia/09-sept-25' (#37) from amalia/09-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#37
2025-09-09 17:26:53 +08:00
d52453c530 upd: ios 2025-09-09 16:40:26 +08:00
1853cb573c upd : modal filter
Deskripsi:
- update refresh modal filter saat ada perubahan data grup

No Issues
2025-09-09 14:35:45 +08:00
83291676d3 upd : scroll view
Deskripsi:
- horizontal view hide
- vertical view hide
- scroll view height

No Issues
2025-09-09 12:30:40 +08:00
1509d1b702 upd: version app 2025-09-09 12:05:24 +08:00
8d6a0d3981 fixx :api tambah pengumuman 2025-09-09 12:05:04 +08:00
d20307fc0b upd: refresh division detail home
Deskripsi:
- refresh load pada halaman home detail division

No Issues
2025-09-09 11:57:33 +08:00
060f96e7b2 upd : home page
Deskripsi:
- load refresh pada halaman home

No Issues
2025-09-09 11:49:13 +08:00
a15724756e fix: diskusi divisi
Deskripsi:
- multiline input deskripsi pada tambah dan edit diskusi divisi

No Issues
2025-09-09 11:15:49 +08:00
c8de5d185a fix : notification page
Deskripsi:
- load refresh notification page

No Issues
2025-09-09 11:09:19 +08:00
5fa364be24 fix: pencarian
Deskripsi:
- scroll height
- tanpa wrap
- pake scroll map

No Issues
2025-09-09 11:05:37 +08:00
4768007df3 fix:tinggi scroll profile 2025-09-09 10:47:43 +08:00
89bf659598 Merge pull request 'upd: kode verification' (#36) from amalia/04-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#36
2025-09-04 18:39:14 +08:00
9793794ff2 upd: kode verification
Deskripsi:
- update logo pda page kode otp
- update text wa

No Issues
2025-09-04 18:38:19 +08:00
c435eb1503 Merge pull request 'amalia/04-sept-25' (#35) from amalia/04-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#35
2025-09-04 18:19:35 +08:00
d0aaa5561c upd: ios 2025-09-04 18:17:41 +08:00
90042d13dd upd: android dan ios 2025-09-04 17:46:30 +08:00
596565ba8e upd: gitignore 2025-09-04 17:33:16 +08:00
22663acaae upd: icon small 2025-09-04 17:30:02 +08:00
d3354e3e74 upd: android dan ios 2025-09-04 17:29:45 +08:00
c3ab4d05ae upd
: update icon

Deskirpsi:
- ganti logo dan icon dan nama aplikasi

Mo Issues
2025-09-04 16:46:57 +08:00
75c95b5c92 Merge pull request 'amalia/03-sept-25' (#34) from amalia/03-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#34
2025-09-03 17:37:36 +08:00
270001aa4f upd : build version
Deskripsi;
- build version otomatis

No Issues
2025-09-03 16:49:40 +08:00
1122e51047 upd: color status bar
Deskripsi:
- status bar pada login dan halaman konfirmasi otp

No Issues
2025-09-02 11:48:38 +08:00
265656413d upd: login tanpa verifikasi otp
Deskripsi:
- dibuat untuk pengecekan aplikasi oleh tim google play store

No Issues
2025-09-02 11:08:58 +08:00
db0f0ecd6c upd: version app 2025-09-02 11:07:06 +08:00
bc590b8cb5 Merge pull request 'amalia/01-sept-25' (#33) from amalia/01-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#33
2025-09-01 17:30:53 +08:00
24e1ace521 fix : login
Deskripsi :
- login api

NO Issues
2025-09-01 17:05:51 +08:00
019c0a5e33 upd : coba rollout google playstore
Deskripsi :
- internal track

No Issues
2025-09-01 15:38:29 +08:00
4250ca3057 Merge pull request 'amalia/29-agustus-25' (#32) from amalia/29-agustus-25 into join
Reviewed-on: bip/mobile-darmasaba#32
2025-08-29 17:04:10 +08:00
c775b06dc3 upd: load dokumen divisi
Deskripsi:
- ilangin skeleton loading pada path load data

No Issues
2025-08-29 16:56:09 +08:00
77cd07ad7a upd: upd new folder
Deskripsi:
- bisa membuat folder baru saat salin atau pindah file pada fitur dokumen divisi

No Issues
2025-08-29 16:52:08 +08:00
c35e2e65bd upd: tambah jabatan
Deskripsi:
- pisah class modal form tambah jabatan supaya bisa double modal

No Issues
2025-08-29 15:23:08 +08:00
6a24b95cdd upd: loading button
Deskripsi:
- tambah folder bar
- rename file pada dokumen divisi

No Issues
2025-08-29 14:58:16 +08:00
92c58524f6 upd : salin link
Deskripsi:
- detail kalender event divisi > dapat di copy

No Issues
2025-08-29 12:04:58 +08:00
7d5ec511f5 upd: input date
Deskripsi:
- on submit value pada ios
- on cancel value pada android

No Issues'
2025-08-29 11:18:38 +08:00
3de8e628b6 upd: tampilan
Deskripsi:
- update tampilan select anggota

No Issues
2025-08-29 10:42:11 +08:00
e9f1b14bd6 Merge pull request 'amalia/28-agustus-25' (#31) from amalia/28-agustus-25 into join
Reviewed-on: bip/mobile-darmasaba#31
2025-08-28 17:41:58 +08:00
9607774056 fix : create division
Deskripsi:
- fix error form create division

No Issues
2025-08-28 16:39:03 +08:00
07caea8ae5 fix : tampilan
Deskripsi:
- tag text warna hitam pada info division page

No Issues
2025-08-28 15:47:10 +08:00
94c48889c6 upd: validasi create pengumuman
Deskripsi :.
- disable button form pada saat blm memilih divisi

No Issues
2025-08-28 14:49:56 +08:00
d0849143f2 upd: tampilan
Deskripsi:
- menghilangkan new line dan tag html pada list pengumuman dan list diskusi umum
- update api

NO Issues
2025-08-28 14:29:31 +08:00
a7aeb3d3f9 upd
: validasi no telp

Deskripsi:
- validasi nomor telepon >= 9 dan <=16
- tambah anggota, edit anggota, edit profile

No Issues
2025-08-28 12:00:46 +08:00
e755273ab1 Merge pull request 'amalia/27-agustus-25' (#30) from amalia/27-agustus-25 into join
Reviewed-on: bip/mobile-darmasaba#30
2025-08-27 17:34:22 +08:00
d460653408 fix: jabatan
Deskripsi:
- loading button on form edit dan tambah data
- toast error api edit jabatan

No Issues
2025-08-27 17:06:43 +08:00
bb242c9be8 upd: tampilan android
Deskripsi:
- padding input pada tampilan android

No Issues
2025-08-27 15:50:32 +08:00
c01a1885c2 fix : toast
Deskripsi:
- bisa custom posisi toast alert

No Issues
2025-08-27 11:39:46 +08:00
2651e4bd18 upd : position
Deskripsi:
- validasi button form tambah jabatan
- validasi disable button form edit jabatan

No Issues
2025-08-27 11:25:16 +08:00
171c5f0eeb upd: ketika link api berubah
Deskripsi:
- jika pengembalian error maka otomatis signout pada halaman home

NO Issues
2025-08-27 10:30:23 +08:00
abeb26e565 Merge pull request 'fix : tampilan' (#29) from amalia/26-agustus-25 into join
Reviewed-on: bip/mobile-darmasaba#29
2025-08-26 17:23:42 +08:00
191699af04 fix : tampilan
Deskripsi:
- text panjang pada pengumuman
- text panjang pada list banner
- text align pada detail member
- text panjang pada section item tanggal tugas
- text panjang pada select form
- text panjang pada detail event calendar divisi
- keyboard avoiding pada edit event calendar divisi

No Issues
2025-08-26 16:35:25 +08:00
5cb81856de Merge pull request 'amalia/22-agustus-25' (#28) from amalia/22-agustus-25 into join
Reviewed-on: bip/mobile-darmasaba#28
2025-08-22 17:42:40 +08:00
37fda41dc1 upd: tampilan
Deskripsi:
- jarak setelah input text pencarian

No Issues
2025-08-22 16:40:31 +08:00
298113488c upd: task divisi
Deskripsi:
- update role akses task divisi

No Issues'
2025-08-22 16:29:52 +08:00
d88c332b03 upd: tampilan
Deskripsi:
- vertical center icon list dan grid pada list project dan divisi
- mb list lembaga desa dan search page

No Issues
2025-08-22 12:15:45 +08:00
f5c29e86fa upd : banner
Deskripsi:
- home > ketika tidak ada data banner
- banner list > ketika tidak ada data banner

No Issues
2025-08-22 12:00:23 +08:00
1dc69b64d2 Merge pull request 'amalia/21-agustus-25' (#27) from amalia/21-agustus-25 into join
Reviewed-on: bip/mobile-darmasaba#27
2025-08-21 17:37:39 +08:00
a409bfef2f upd: input datetime
Deskripsi:
- update tombol konfirmasi pada input date time ios
- dan konfigurasi pada android

No Issues
2025-08-21 17:19:39 +08:00
99c81f6f0d upd: env
Deskripsi;
- env storage
- env db firebase url

No Issues
2025-08-21 12:16:39 +08:00
4e6b27bbcc upd: env pass encrypt
Deskripsi:
- ganti env pass encripsi
- pengaplikasian env

No Issues
2025-08-21 11:35:25 +08:00
6a97ae76fc fix: date type
Deskripsi:
- fix tanggal range type data

No Issues
2025-08-21 11:08:41 +08:00
165 changed files with 1936 additions and 1321 deletions

View File

@@ -39,5 +39,7 @@ app-example
x.ts
x.sh
/android
/ios
.env
android/

3
.gitignore vendored
View File

@@ -44,4 +44,5 @@ x.ts
x.sh
google-services.json
mobile-darmasaba-firebase-adminsdk-fbsvc-f5abb292b5.json
service-account.json

View File

@@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

View File

@@ -92,8 +92,8 @@ android {
applicationId 'mobiledarmasaba.app'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"
versionCode 6
versionName "1.0.2"
}
signingConfigs {
debug {

View File

@@ -1,6 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -1,5 +1,5 @@
<resources>
<string name="app_name">mobile-darmasaba</string>
<string name="app_name">Desa+</string>
<string name="expo_system_ui_user_interface_style" translatable="false">automatic</string>
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>

View File

@@ -31,7 +31,7 @@ extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
}
expoAutolinking.useExpoModules()
rootProject.name = 'mobile-darmasaba'
rootProject.name = 'Desa+'
expoAutolinking.useExpoVersionCatalog()

View File

@@ -2,30 +2,40 @@ import 'dotenv/config';
export default {
expo: {
name: "mobile-darmasaba",
name: "Desa+",
slug: "mobile-darmasaba",
version: "1.0.0",
version: "1.0.5", // Versi aplikasi (App Store)
jsEngine: "jsc",
orientation: "portrait",
icon: "./assets/images/icon.png",
icon: "./assets/images/logo-icon-small.png",
scheme: "myapp",
userInterfaceStyle: "automatic",
newArchEnabled: false,
ios: {
supportsTablet: true,
bundleIdentifier: "mobiledarmasaba.app",
buildNumber: "2",
infoPlist: {
ITSAppUsesNonExemptEncryption: false
ITSAppUsesNonExemptEncryption: false,
CFBundleDisplayName: "Desa+"
},
googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist"
googleServicesFile: process.env.IOS_GOOGLE_SERVICES_FILE
},
android: {
package: "mobiledarmasaba.app",
versionCode: 9,
adaptiveIcon: {
foregroundImage: "./assets/images/splash-icon.png",
foregroundImage: "./assets/images/logo-icon-small.png",
backgroundColor: "#ffffff"
},
googleServicesFile: "./google-services.json"
googleServicesFile: "./google-services.json",
permissions: [
"READ_EXTERNAL_STORAGE",
"WRITE_EXTERNAL_STORAGE",
"READ_MEDIA_IMAGES", // Android 13+
"READ_MEDIA_VIDEO", // Android 13+
"READ_MEDIA_AUDIO" // Android 13+
]
},
web: {
bundler: "metro",
@@ -37,7 +47,7 @@ export default {
[
"expo-splash-screen",
{
image: "./assets/images/splash-icon.png",
image: "./assets/images/logo-icon-small.png",
imageWidth: 200,
resizeMode: "contain",
backgroundColor: "#ffffff"
@@ -50,7 +60,7 @@ export default {
"@react-native-firebase/app",
{
ios: {
googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist"
googleServicesFile: process.env.IOS_GOOGLE_SERVICES_FILE
}
}
]
@@ -65,8 +75,9 @@ export default {
},
URL_API: process.env.URL_API,
URL_OTP: process.env.URL_OTP,
URL_STORAGE : process.env.URL_STORAGE,
URL_FIREBASE_DB : process.env.URL_FIREBASE_DB
URL_STORAGE: process.env.URL_STORAGE,
URL_FIREBASE_DB: process.env.URL_FIREBASE_DB,
PASS_ENC: process.env.PASS_ENC
}
}
};

View File

@@ -148,7 +148,7 @@ export default function RootLayout() {
}}
/>
</Stack>
<StatusBar style="light" translucent={false} backgroundColor="black" />
<StatusBar style="inverted" translucent={false} backgroundColor="black" />
<ToastCustom />
</Provider>
)

View File

@@ -8,7 +8,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
import { Entypo, MaterialIcons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import React, { useEffect, useState } from "react";
import { Dimensions, SafeAreaView, ScrollView, View } from "react-native";
import { Dimensions, RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
import RenderHTML from 'react-native-render-html';
import { useSelector } from "react-redux";
@@ -28,6 +28,7 @@ export default function DetailAnnouncement() {
const contentWidth = Dimensions.get('window').width
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 2 }, (_, index) => index)
const [refreshing, setRefreshing] = useState(false)
async function handleLoad(loading: boolean) {
try {
@@ -56,6 +57,13 @@ export default function DetailAnnouncement() {
return htmlRegex.test(text);
};
const handleRefresh = async () => {
setRefreshing(true)
handleLoad(false)
await new Promise(resolve => setTimeout(resolve, 2000));
setRefreshing(false)
};
return (
<SafeAreaView>
<Stack.Screen
@@ -66,7 +74,16 @@ export default function DetailAnnouncement() {
headerRight: () => entityUser.role != 'user' && entityUser.role != 'coadmin' ? <HeaderRightAnnouncementDetail id={id} /> : <></>,
}}
/>
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
style={[Styles.h100]}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={() => handleRefresh()}
/>
}
>
<View style={[Styles.p15]}>
<View style={[Styles.wrapPaper]}>
{
@@ -84,9 +101,9 @@ export default function DetailAnnouncement() {
</View>
:
<>
<View style={Styles.rowItemsCenter}>
<View style={[Styles.rowItemsCenter, { alignItems: 'flex-start' }]}>
<MaterialIcons name="campaign" size={30} color="black" style={Styles.mr05} />
<Text style={[Styles.textDefaultSemiBold]}>{data?.title}</Text>
<Text style={[Styles.textDefaultSemiBold, Styles.w90]}>{data?.title}</Text>
</View>
<View style={[Styles.mt10]}>
{

View File

@@ -99,7 +99,7 @@ export default function CreateAnnouncement() {
headerTitleAlign: "center",
headerRight: () => (
<ButtonSaveHeader
disable={disableBtn || loading ? true : false}
disable={disableBtn || divisionMember.length == 0 || loading ? true : false}
category="create"
onPress={() => {
divisionMember.length == 0
@@ -110,8 +110,11 @@ export default function CreateAnnouncement() {
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<ScrollView
showsVerticalScrollIndicator={false}
style={[Styles.h100]}
>
<View style={[Styles.p15]}>
<InputForm
label="Judul"
type="default"

View File

@@ -153,8 +153,11 @@ export default function EditAnnouncement() {
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<ScrollView
showsVerticalScrollIndicator={false}
style={[Styles.h100]}
>
<View style={[Styles.p15]}>
<InputForm
label="Judul"
type="default"

View File

@@ -87,7 +87,7 @@ export default function Announcement() {
<View>
<InputSearch onChange={setSearch} />
</View>
<View style={[{ flex: 2 }]}>
<View style={[{ flex: 2 }, Styles.mt05]}>
{
loading ?
arrSkeleton.map((item, index) => {
@@ -114,7 +114,7 @@ export default function Announcement() {
</View>
}
title={item.title}
desc={item.desc.replace(/<[^>]*>?/gm, '')}
desc={item.desc.replace(/<[^>]*>?/gm, '').replace(/\r?\n|\r/g, ' ')}
rightTopInfo={item.createdAt}
/>
)

View File

@@ -2,6 +2,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiEditBanner, apiGetBanner, apiGetBannerOne } from "@/lib/api";
import { setEntities } from "@/lib/bannerSlice";
@@ -50,7 +51,7 @@ export default function EditBanner() {
const hasil = await decryptToken(String(token?.current));
const data = await apiGetBannerOne({ user: hasil, id });
setSelectedImage({
uri: `https://wibu-storage.wibudev.com/api/files/${data.data.image}`,
uri: `${ConstEnv.url_storage}/files/${data.data.image}`,
});
setTitle(data.data.title);
};
@@ -129,7 +130,7 @@ export default function EditBanner() {
category="update" />,
}}
/>
<ScrollView>
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
<View style={[Styles.p15, Styles.mb100]}>
<View style={[Styles.mb15]}>
{selectedImage != undefined ? (

View File

@@ -117,8 +117,8 @@ export default function CreateBanner() {
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
<View style={[Styles.p15]}>
<View style={[Styles.mb15]}>
{selectedImage != undefined ? (
<Pressable onPress={pickImageAsync}>

View File

@@ -5,6 +5,8 @@ import ButtonBackHeader from "@/components/buttonBackHeader"
import DrawerBottom from "@/components/drawerBottom"
import MenuItemRow from "@/components/menuItemRow"
import ModalLoading from "@/components/modalLoading"
import Text from "@/components/Text"
import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles"
import { apiDeleteBanner, apiGetBanner } from "@/lib/api"
import { setEntities } from "@/lib/bannerSlice"
@@ -20,7 +22,6 @@ import * as mime from 'react-native-mime-types'
import Toast from "react-native-toast-message"
import { useDispatch, useSelector } from "react-redux"
type Props = {
id: string
title: string
@@ -71,7 +72,7 @@ export default function BannerList() {
const openFile = () => {
setModal(false)
setLoadingOpen(true)
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + selectFile?.image;
let remoteUrl = ConstEnv.url_storage + '/files/' + selectFile?.image;
const fileName = selectFile?.title + '.' + selectFile?.extension;
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName)
@@ -112,7 +113,7 @@ export default function BannerList() {
}}
/>
<ModalLoading isVisible={loadingOpen} setVisible={setLoadingOpen} />
<ScrollView
<ScrollView
refreshControl={
<RefreshControl
refreshing={refreshing}
@@ -121,26 +122,37 @@ export default function BannerList() {
}
style={[Styles.h100]}
>
<View style={[Styles.p15, Styles.mb100]}>
{entities.map((index: any, key: number) => (
<BorderBottomItem
key={key}
onPress={() => {
setDataId(index.id)
setSelectFile(index)
setModal(true)
}}
borderType="all"
icon={
<Image
source={{ uri: `https://wibu-storage.wibudev.com/api/files/${index.image}` }}
style={[Styles.imgListBanner]}
{
entities.length > 0
?
<View style={[Styles.p15, Styles.mb100]}>
{entities.map((index: any, key: number) => (
<BorderBottomItem
key={key}
onPress={() => {
setDataId(index.id)
setSelectFile(index)
setModal(true)
}}
borderType="all"
icon={
<Image
source={{ uri: `${ConstEnv.url_storage}/files/${index.image}` }}
style={[Styles.imgListBanner]}
/>
}
title={index.title}
width={65}
/>
}
title={index.title}
/>
))}
</View>
))}
</View>
:
<View style={[Styles.p15, Styles.mb100]}>
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
</View>
}
</ScrollView>
<DrawerBottom animation="slide" isVisible={isModal} setVisible={() => setModal(false)} title="Menu">

View File

@@ -8,6 +8,7 @@ import Skeleton from "@/components/skeleton";
import SkeletonContent from "@/components/skeletonContent";
import Text from '@/components/Text';
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetDiscussionGeneralOne, apiSendDiscussionGeneralCommentar } from "@/lib/api";
import { getDB } from "@/lib/firebaseDatabase";
@@ -138,7 +139,7 @@ export default function DetailDiscussionGeneral() {
}}
/>
<View style={{ flex: 1 }}>
<ScrollView>
<ScrollView showsVerticalScrollIndicator={false}>
<View style={[Styles.p15, Styles.mb100]}>
{
loading ?
@@ -152,14 +153,13 @@ export default function DetailDiscussionGeneral() {
<MaterialIcons name="chat" size={25} color={'#384288'} />
</View>
}
title={data?.title}
title={data?.title + " " + data?.createdAt}
subtitle={
!data?.isActive ?
<LabelStatus category='warning' text='ARSIP' size="small" />
:
<LabelStatus category={data.status == 1 ? 'success' : 'error'} text={data.status == 1 ? 'BUKA' : 'TUTUP'} size="small" />
}
rightTopInfo={data?.createdAt}
desc={data?.desc}
leftBottomInfo={
<View style={[Styles.rowItemsCenter]}>
@@ -167,6 +167,11 @@ export default function DetailDiscussionGeneral() {
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>{dataKomentar.length} Komentar</Text>
</View>
}
rightBottomInfo={
<View style={[Styles.rowItemsCenter]}>
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>{data?.createdAt}</Text>
</View>
}
/>
}
<View style={[Styles.p15]}>
@@ -184,7 +189,7 @@ export default function DetailDiscussionGeneral() {
key={i}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="xs" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="xs" />
}
title={item.username}
rightTopInfo={item.createdAt}
@@ -213,11 +218,16 @@ export default function DetailDiscussionGeneral() {
bg="white"
onChange={setKomentar}
value={komentar}
multiline
itemRight={
<Pressable onPress={() => {
(komentar != '' && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin")))
&& handleKomentar()
}}>
}}
style={[
Platform.OS == 'android' && Styles.mb15,
]}
>
<MaterialIcons name="send" size={25} style={(komentar == '' || data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin"))) ? Styles.cGray : Styles.cDefault} />
</Pressable>
}

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from '@/components/Text';
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberDiscussionGeneral, apiGetDiscussionGeneralOne, apiGetUser } from "@/lib/api";
import { setUpdateDiscussionGeneralDetail } from "@/lib/discussionGeneralDetail";
@@ -115,13 +116,13 @@ export default function AddMemberDiscussionDetail() {
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -132,7 +133,9 @@ export default function AddMemberDiscussionDetail() {
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
>
{
data.length > 0 ?
@@ -147,7 +150,7 @@ export default function AddMemberDiscussionDetail() {
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]}>{item.name}</Text>
{
@@ -156,7 +159,7 @@ export default function AddMemberDiscussionDetail() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -7,6 +7,7 @@ import { InputForm } from "@/components/inputForm";
import ModalSelect from "@/components/modalSelect";
import SelectForm from "@/components/selectForm";
import Text from '@/components/Text';
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateDiscussionGeneral } from "@/lib/api";
import { setUpdateDiscussionGeneralDetail } from "@/lib/discussionGeneralDetail";
@@ -141,7 +142,7 @@ export default function CreateDiscussionGeneral() {
),
}}
/>
<ScrollView>
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
<View style={[Styles.p15, Styles.mb100]}>
{
(entityUser.role == "supadmin" ||
@@ -215,7 +216,7 @@ export default function CreateDiscussionGeneral() {
key={index}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}
title={item.name}
/>

View File

@@ -119,7 +119,7 @@ export default function EditDiscussionGeneral() {
),
}}
/>
<ScrollView>
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
<View style={[Styles.p15]}>
<InputForm
label="Judul"

View File

@@ -98,22 +98,26 @@ export default function Discussion() {
return (
<View style={[Styles.p15, { flex: 1 }]}>
<View>
<View style={[Styles.wrapBtnTab]}>
<ButtonTab
active={status == "false" ? "false" : "true"}
value="true"
onPress={() => { setStatus("true") }}
label="Aktif"
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
n={2} />
<ButtonTab
active={status == "false" ? "false" : "true"}
value="false"
onPress={() => { setStatus("false") }}
label="Arsip"
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
n={2} />
</View>
{
entityUser.role != "user" && entityUser.role != "coadmin" &&
<View style={[Styles.wrapBtnTab]}>
<ButtonTab
active={status == "false" ? "false" : "true"}
value="true"
onPress={() => { setStatus("true") }}
label="Aktif"
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
n={2} />
<ButtonTab
active={status == "false" ? "false" : "true"}
value="false"
onPress={() => { setStatus("false") }}
label="Arsip"
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
n={2} />
</View>
}
<InputSearch onChange={setSearch} />
{
(entityUser.role == "supadmin" || entityUser.role == "developer") &&
@@ -122,7 +126,7 @@ export default function Discussion() {
</View>
}
</View>
<View style={[{ flex: 2 }]}>
<View style={[{ flex: 2 }, Styles.mt05]}>
{
loading ?
arrSkeleton.map((item: any, i: number) => {
@@ -153,7 +157,7 @@ export default function Discussion() {
status != "false" && <LabelStatus category={item.status === 1 ? "success" : "error"} text={item.status === 1 ? "BUKA" : "TUTUP"} size="small" />
}
rightTopInfo={item.createdAt}
desc={item.desc}
desc={item.desc.replace(/<[^>]*>?/gm, ' ').replace(/\r?\n|\r/g, ' ')}
leftBottomInfo={
<View style={[Styles.rowItemsCenter]}>
<Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
@@ -176,34 +180,6 @@ export default function Discussion() {
/>
}
/>
// data.map((item: any, i: number) => {
// return (
// <BorderBottomItem
// key={i}
// onPress={() => { router.push(`/discussion/${item.id}`) }}
// borderType="bottom"
// icon={
// <View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
// <MaterialIcons name="chat" size={25} color={'#384288'} />
// </View>
// }
// title={item.title}
// subtitle={
// status != "false" && <LabelStatus category={item.status === 1 ? "success" : "error"} text={item.status === 1 ? "BUKA" : "TUTUP"} size="small" />
// }
// rightTopInfo={item.createdAt}
// desc={item.desc}
// leftBottomInfo={
// <View style={[Styles.rowItemsCenter]}>
// <Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
// <Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>Diskusikan</Text>
// </View>
// }
// rightBottomInfo={`${item.total_komentar} Komentar`}
// />
// )
// })
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
}

View File

@@ -7,6 +7,7 @@ import MenuItemRow from "@/components/menuItemRow";
import SkeletonTwoItem from "@/components/skeletonTwoItem";
import Text from '@/components/Text';
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiDeleteMemberDiscussionGeneral, apiGetDiscussionGeneralOne } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
@@ -109,7 +110,7 @@ export default function MemberDiscussionDetail() {
key={index}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}
title={item.name}
onPress={() => {
@@ -134,22 +135,25 @@ export default function MemberDiscussionDetail() {
router.push(`/member/${chooseUser.idUser}`)
}}
/>
{
entityUser.role != "user" && entityUser.role != "coadmin" &&
<MenuItemRow
icon={<MaterialCommunityIcons name="account-remove" color="black" size={25} />}
title="Keluarkan"
onPress={() => {
setModal(false)
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah Anda yakin ingin mengeluarkan anggota?',
onPress: () => {
handleDeleteUser()
}
})
<MenuItemRow
icon={<MaterialCommunityIcons name="account-remove" color="black" size={25} />}
title="Keluarkan"
onPress={() => {
setModal(false)
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah Anda yakin ingin mengeluarkan anggota?',
onPress: () => {
handleDeleteUser()
}
})
}}
/>
}
}}
/>
</View>
</DrawerBottom>
</SafeAreaView>

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberCalendar, apiGetCalendarOne, apiGetDivisionMember } from "@/lib/api";
import { setUpdateCalendar } from "@/lib/calendarUpdate";
@@ -122,13 +123,13 @@ export default function AddMemberCalendarEvent() {
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -139,7 +140,10 @@ export default function AddMemberCalendarEvent() {
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
style={[Styles.h100]}
>
{
data.length > 0 ?
data.map((item: any, index: any) => {
@@ -150,11 +154,10 @@ export default function AddMemberCalendarEvent() {
style={[Styles.itemSelectModal]}
onPress={() => {
!found && onChoose(item.idUser, item.name, item.img)
onChoose(item.idUser, item.name, item.img)
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10, { width: '80%' }]}>
<Text numberOfLines={1} ellipsizeMode="tail" style={[Styles.textDefault]}>{item.name}</Text>
{
@@ -163,7 +166,7 @@ export default function AddMemberCalendarEvent() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -9,10 +9,11 @@ import { valueTypeEventRepeat } from "@/constants/TypeEventRepeat"
import { apiGetCalendarOne, apiUpdateCalendar } from "@/lib/api"
import { stringToDateTime } from "@/lib/fun_stringToDate"
import { useAuthSession } from "@/providers/AuthProvider"
import { useHeaderHeight } from "@react-navigation/elements"
import { Stack, router, useLocalSearchParams } from "expo-router"
import moment from "moment"
import { useEffect, useState } from "react"
import { SafeAreaView, ScrollView, View } from "react-native"
import { KeyboardAvoidingView, Platform, SafeAreaView, ScrollView, View } from "react-native"
import Toast from "react-native-toast-message"
export default function EditEventCalendar() {
@@ -22,6 +23,7 @@ export default function EditEventCalendar() {
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>()
const [idCalendar, setIdCalendar] = useState('')
const [loading, setLoading] = useState(false)
const headerHeight = useHeaderHeight()
const [error, setError] = useState({
title: false,
@@ -176,94 +178,100 @@ export default function EditEventCalendar() {
/>
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<InputForm
label="Nama Acara"
type="default"
placeholder="Nama Acara"
required
bg="white"
value={data.title}
onChange={(val) => validationForm("title", val)}
error={error.title}
errorText="Nama acara tidak boleh kosong"
/>
<InputDate
onChange={(val) => validationForm("dateStart", val)}
mode="date"
value={data.dateStart}
label="Tanggal Acara"
required
error={error.dateStart}
errorText="Tanggal acara tidak boleh kosong"
placeholder="Pilih Tanggal Acara"
/>
<View style={[Styles.rowSpaceBetween, Styles.mv10]}>
<View style={[{ width: "48%" }]}>
<InputDate
onChange={(val) => validationForm("timeStart", val)}
mode="time"
value={data.timeStart}
label="Waktu Awal"
required
error={error.timeStart}
errorText="Waktu awal tidak valid"
placeholder="--:--"
/>
</View>
<View style={[{ width: "48%" }]}>
<InputDate
onChange={(val) => validationForm("timeEnd", val)}
mode="time"
value={data.timeEnd}
label="Waktu Akhir"
required
error={error.timeEnd}
errorText="Waktu akhir tidak valid"
placeholder="--:--"
/>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={headerHeight}
>
<ScrollView>
<View style={[Styles.p15]}>
<InputForm
label="Nama Acara"
type="default"
placeholder="Nama Acara"
required
bg="white"
value={data.title}
onChange={(val) => validationForm("title", val)}
error={error.title}
errorText="Nama acara tidak boleh kosong"
/>
<InputDate
onChange={(val) => validationForm("dateStart", val)}
mode="date"
value={data.dateStart}
label="Tanggal Acara"
required
error={error.dateStart}
errorText="Tanggal acara tidak boleh kosong"
placeholder="Pilih Tanggal Acara"
/>
<View style={[Styles.rowSpaceBetween, Styles.mv10]}>
<View style={[{ width: "48%" }]}>
<InputDate
onChange={(val) => validationForm("timeStart", val)}
mode="time"
value={data.timeStart}
label="Waktu Awal"
required
error={error.timeStart}
errorText="Waktu awal tidak valid"
placeholder="--:--"
/>
</View>
<View style={[{ width: "48%" }]}>
<InputDate
onChange={(val) => validationForm("timeEnd", val)}
mode="time"
value={data.timeEnd}
label="Waktu Akhir"
required
error={error.timeEnd}
errorText="Waktu akhir tidak valid"
placeholder="--:--"
/>
</View>
</View>
<InputForm
label="Link Meet"
type="default"
placeholder="Link Meet"
bg="white"
value={data.linkMeet}
onChange={(val) => validationForm("linkMeet", val)}
/>
<SelectForm
bg="white"
label="Ulangi Acara"
placeholder="Ulangi Acara"
value={choose.label}
required
onPress={() => { setSelect(true) }}
/>
<InputForm
label="Jumlah Pengulangan"
type="numeric"
placeholder="Jumlah Pengulangan"
required
bg="white"
value={String(data.repeatValue)}
onChange={(val) => validationForm("repeatValue", val)}
error={error.repeatValue}
errorText="Jumlah pengulangan tidak valid"
disable={choose.val == "once"}
/>
<InputForm
label="Deskripsi"
type="default"
placeholder="Deskripsi"
bg="white"
value={data.desc}
onChange={(val) => validationForm("desc", val)}
multiline
/>
</View>
<InputForm
label="Link Meet"
type="default"
placeholder="Link Meet"
bg="white"
value={data.linkMeet}
onChange={(val) => validationForm("linkMeet", val)}
/>
<SelectForm
bg="white"
label="Ulangi Acara"
placeholder="Ulangi Acara"
value={choose.label}
required
onPress={() => { setSelect(true) }}
/>
<InputForm
label="Jumlah Pengulangan"
type="numeric"
placeholder="Jumlah Pengulangan"
required
bg="white"
value={String(data.repeatValue)}
onChange={(val) => validationForm("repeatValue", val)}
error={error.repeatValue}
errorText="Jumlah pengulangan tidak valid"
disable={choose.val == "once"}
/>
<InputForm
label="Deskripsi"
type="default"
placeholder="Deskripsi"
bg="white"
value={data.desc}
onChange={(val) => validationForm("desc", val)}
multiline
/>
</View>
</ScrollView>
</ScrollView>
</KeyboardAvoidingView>
<ModalSelect
category={"type-event-repeat"}

View File

@@ -7,14 +7,16 @@ import ImageUser from "@/components/imageNew"
import MenuItemRow from "@/components/menuItemRow"
import Skeleton from "@/components/skeleton"
import Text from "@/components/Text"
import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles"
import { apiDeleteCalendarMember, apiGetCalendarOne, apiGetDivisionOneFeature } from "@/lib/api"
import { setUpdateCalendar } from "@/lib/calendarUpdate"
import { useAuthSession } from "@/providers/AuthProvider"
import { MaterialCommunityIcons } from "@expo/vector-icons"
import Clipboard from "@react-native-clipboard/clipboard"
import { router, Stack, useLocalSearchParams } from "expo-router"
import { useEffect, useState } from "react"
import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
import Toast from "react-native-toast-message"
import { useDispatch, useSelector } from "react-redux"
@@ -70,7 +72,7 @@ export default function DetailEventCalendar() {
}
}
async function handleLoad(loading:boolean) {
async function handleLoad(loading: boolean) {
try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current));
@@ -114,6 +116,11 @@ export default function DetailEventCalendar() {
handleLoadMember();
}, [update.member]);
const handleCopy = (text: string) => {
Clipboard.setString(text);
Toast.show({ type: 'small', text1: 'Berhasil menyalin link', })
};
async function handleDeleteUser() {
try {
const hasil = await decryptToken(String(token?.current));
@@ -164,12 +171,12 @@ export default function DetailEventCalendar() {
>
<View style={[Styles.p15]}>
<View style={[Styles.wrapPaper, Styles.mb15]}>
<View style={Styles.rowItemsCenter}>
<View style={[Styles.rowItemsCenter, { alignItems: 'flex-start' }]}>
<MaterialCommunityIcons name="calendar-text" size={30} color="black" style={Styles.mr10} />
{
loading ?
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" />
: <Text style={[Styles.textDefault]}>{data?.title}</Text>
: <Text style={[Styles.textDefault, Styles.w90]}>{data?.title}</Text>
}
</View>
@@ -215,16 +222,20 @@ export default function DetailEventCalendar() {
loading ?
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" />
:
<Text style={[Styles.textDefault]}>{data?.linkMeet ? data.linkMeet : '-'}</Text>
data?.linkMeet ?
<Pressable onPress={() => { handleCopy(data.linkMeet) }}>
<Text style={[Styles.textDefault]}>{data.linkMeet}</Text>
</Pressable>
: <Text style={[Styles.textDefault]}>-</Text>
}
</View>
<View style={[Styles.rowItemsCenter, Styles.mt10]}>
<View style={[Styles.rowItemsCenter, Styles.mt10, { alignItems: 'flex-start' }]}>
<MaterialCommunityIcons name="card-text-outline" size={30} color="black" style={Styles.mr10} />
{
loading ?
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" />
:
<Text style={[Styles.textDefault]}>{data?.desc}</Text>
<Text style={[Styles.textDefault, Styles.w90]}>{data?.desc}</Text>
}
</View>
</View>
@@ -241,7 +252,7 @@ export default function DetailEventCalendar() {
<BorderBottomItem
key={index}
borderType="bottom"
icon={<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />}
icon={<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />}
title={item.name}
subtitle={item.email}
onPress={() => {

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateCalendar, apiGetDivisionMember } from "@/lib/api";
import { setFormCreateCalendar } from "@/lib/calendarCreate";
@@ -111,13 +112,13 @@ export default function CreateCalendarAddMember() {
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -128,7 +129,10 @@ export default function CreateCalendarAddMember() {
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
style={[Styles.h100]}
>
{
data.length > 0 ?
@@ -140,13 +144,13 @@ export default function CreateCalendarAddMember() {
onPress={() => { onChoose(item.idUser, item.name, item.img) }}
>
<View style={[Styles.rowItemsCenter, Styles.w70]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -93,6 +93,7 @@ export default function DiscussionDivisionEdit() {
required
value={data}
onChange={setData}
multiline
/>
</View>
</ScrollView>

View File

@@ -7,6 +7,7 @@ import LabelStatus from "@/components/labelStatus";
import Skeleton from "@/components/skeleton";
import SkeletonContent from "@/components/skeletonContent";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import {
apiGetDiscussionOne,
@@ -220,7 +221,7 @@ export default function DiscussionDetail() {
borderType="bottom"
icon={
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${data?.user_img}`}
src={`${ConstEnv.url_storage}/files/${data?.user_img}`}
size="sm"
/>
}
@@ -267,7 +268,7 @@ export default function DiscussionDetail() {
borderType="bottom"
icon={
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
size="xs"
/>
}
@@ -303,6 +304,7 @@ export default function DiscussionDetail() {
bg="white"
type="default"
round
multiline
placeholder="Kirim Komentar"
onChange={setKomentar}
value={komentar}
@@ -322,12 +324,15 @@ export default function DiscussionDetail() {
entityUser.role == "cosupadmin") &&
handleKomentar();
}}
style={[
Platform.OS == 'android' && Styles.mb15,
]}
>
<MaterialIcons
name="send"
size={25}
style={
komentar == "" ||
[komentar == "" ||
loadingSend ||
data?.status == 2 ||
data?.isActive == false ||
@@ -335,7 +340,8 @@ export default function DiscussionDetail() {
entityUser.role == "coadmin") &&
!isMemberDivision)
? Styles.cGray
: Styles.cDefault
: Styles.cDefault,
]
}
/>
</Pressable>

View File

@@ -56,7 +56,14 @@ export default function CreateDiscussionDivision() {
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<InputForm label="Diskusi" type="default" placeholder="Hal yang didiskusikan" required onChange={setDesc} />
<InputForm
label="Diskusi"
type="default"
placeholder="Hal yang didiskusikan"
required
onChange={setDesc}
multiline
/>
</View>
</ScrollView>
</SafeAreaView>

View File

@@ -5,8 +5,9 @@ import InputSearch from "@/components/inputSearch";
import LabelStatus from "@/components/labelStatus";
import SkeletonContent from "@/components/skeletonContent";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetDiscussion } from "@/lib/api";
import { apiGetDiscussion, apiGetDivisionOneFeature } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign, Feather, Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
@@ -40,6 +41,30 @@ export default function DiscussionDivision() {
const [waiting, setWaiting] = useState(false)
const [status, setStatus] = useState<'true' | 'false'>('true')
const [refreshing, setRefreshing] = useState(false)
const [isMemberDivision, setIsMemberDivision] = useState(false)
const [isAdminDivision, setIsAdminDivision] = useState(false)
const entityUser = useSelector((state: any) => state.user)
async function handleCheckMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-member",
});
const response2 = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-admin",
});
setIsMemberDivision(response.data);
setIsAdminDivision(response2.data);
} catch (error) {
console.error(error);
}
}
async function handleLoad(loading: boolean, thisPage: number) {
try {
@@ -79,6 +104,10 @@ export default function DiscussionDivision() {
}, 1000);
}
useEffect(() => {
handleCheckMember()
}, [])
const handleRefresh = async () => {
setRefreshing(true)
handleLoad(false, 1)
@@ -100,27 +129,31 @@ export default function DiscussionDivision() {
return (
<View style={[Styles.p15, { flex: 1 }]}>
<View>
<View style={[Styles.wrapBtnTab]}>
<ButtonTab
active={status == "false" ? "false" : "true"}
value="true"
onPress={() => { setStatus("true") }}
label="Aktif"
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
n={2} />
<ButtonTab
active={status == "false" ? "false" : "true"}
value="false"
onPress={() => { setStatus("false") }}
label="Arsip"
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
n={2} />
{
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) &&
<View>
<View style={[Styles.wrapBtnTab]}>
<ButtonTab
active={status == "false" ? "false" : "true"}
value="true"
onPress={() => { setStatus("true") }}
label="Aktif"
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
n={2} />
<ButtonTab
active={status == "false" ? "false" : "true"}
value="false"
onPress={() => { setStatus("false") }}
label="Arsip"
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
n={2} />
</View>
<InputSearch onChange={setSearch} />
</View>
<InputSearch onChange={setSearch} />
</View>
}
<View style={[{ flex: 2 }]}>
<View style={[{ flex: 2 }, Styles.mt05]}>
{
loading ?
arrSkeleton.map((item: any, i: number) => {
@@ -141,7 +174,7 @@ export default function DiscussionDivision() {
onPress={() => { router.push(`./discussion/${item.id}`) }}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}
title={item.user_name}
subtitle={
@@ -170,33 +203,8 @@ export default function DiscussionDivision() {
/>
}
/>
// data.map((item, index) => (
// <BorderBottomItem
// key={index}
// onPress={() => { router.push(`./discussion/${item.id}`) }}
// borderType="bottom"
// icon={
// <ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
// }
// title={item.user_name}
// subtitle={
// active == "true" ? item.status == 1 ? <LabelStatus category='success' text='BUKA' size="small" /> : <LabelStatus category='error' text='TUTUP' size="small" /> : <></>
// }
// rightTopInfo={item.createdAt}
// desc={item.desc}
// leftBottomInfo={
// <View style={[Styles.rowItemsCenter]}>
// <Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
// <Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>Diskusikan</Text>
// </View>
// }
// rightBottomInfo={item.total_komentar + ' Komentar'}
// />
// ))
:
(
<Text style={[Styles.textDefault, Styles.cGray, Styles.mv10, { textAlign: "center" }]}>Tidak ada diskusi</Text>
)
(<Text style={[Styles.textDefault, Styles.cGray, Styles.mv10, { textAlign: "center" }]}>Tidak ada diskusi</Text>)
}
</View>
</View>

View File

@@ -13,10 +13,12 @@ import ModalSelectMultiple from "@/components/modalSelectMultiple";
import Skeleton from "@/components/skeleton";
import Text from "@/components/Text";
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import {
apiDocumentDelete,
apiDocumentRename,
apiGetDivisionOneFeature,
apiGetDocument,
apiShareDocument,
} from "@/lib/api";
@@ -64,6 +66,7 @@ type PropsPath = {
};
export default function DocumentDivision() {
const [loadingRename, setLoadingRename] = useState(false)
const [isShare, setShare] = useState(false)
const { token, decryptToken } = useAuthSession()
const { id } = useLocalSearchParams<{ id: string }>()
@@ -83,6 +86,8 @@ export default function DocumentDivision() {
const update = useSelector((state: any) => state.dokumenUpdate)
const [refreshing, setRefreshing] = useState(false)
const [loadingOpen, setLoadingOpen] = useState(false)
const [isMemberDivision, setIsMemberDivision] = useState(false)
const entityUser = useSelector((state: any) => state.user)
const [bodyRename, setBodyRename] = useState({
id: "",
name: "",
@@ -91,6 +96,24 @@ export default function DocumentDivision() {
extension: "",
});
async function handleCheckMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-member",
});
setIsMemberDivision(response.data);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleCheckMember()
}, [id])
async function handleLoad(loading: boolean) {
try {
setLoading(loading)
@@ -200,6 +223,7 @@ export default function DocumentDivision() {
async function handleRename() {
try {
setLoadingRename(true)
const hasil = await decryptToken(String(token?.current));
const response = await apiDocumentRename({ user: hasil, ...bodyRename });
if (response.success) {
@@ -213,7 +237,8 @@ export default function DocumentDivision() {
console.error(error);
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
} finally {
setRename(false);
setLoadingRename(false)
setRename(false)
}
}
@@ -268,7 +293,7 @@ export default function DocumentDivision() {
const openFile = (item: Props) => {
if (Platform.OS == 'android') setLoadingOpen(true)
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + item.idStorage;
let remoteUrl = ConstEnv.url_storage + '/files/' + item.idStorage;
const fileName = item.name + '.' + item.extension;
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName)
@@ -343,7 +368,7 @@ export default function DocumentDivision() {
}}
/>
) : (
<HeaderRightDocument path={path} />
<HeaderRightDocument path={path} isMember={isMemberDivision} />
),
}}
/>
@@ -359,25 +384,20 @@ export default function DocumentDivision() {
<View style={[Styles.p15, Styles.mb100]}>
<View style={[Styles.rowItemsCenter]}>
{
loading ?
arrSkeleton.map((item, index) => (
<Skeleton key={index} width={60} height={10} borderRadius={10} style={[Styles.mr05]} />
))
:
dataJalur.map((item, index) => (
<Pressable
key={index}
style={[Styles.rowItemsCenter]}
onPress={() => {
setPath(item.id);
}}
>
{item.id != "home" && (
<AntDesign name="right" style={[Styles.mh05, Styles.mt02]} color="black" />
)}
<Text> {item.name} </Text>
</Pressable>
))
dataJalur.map((item, index) => (
<Pressable
key={index}
style={[Styles.rowItemsCenter]}
onPress={() => {
setPath(item.id);
}}
>
{item.id != "home" && (
<AntDesign name="right" style={[Styles.mh05, Styles.mt02]} color="black" />
)}
<Text> {item.name} </Text>
</Pressable>
))
}
</View>
<View>
@@ -408,6 +428,7 @@ export default function DocumentDivision() {
: `${item.name}.${item.extension}`
}
dateTime={item.createdAt}
canChecked={(entityUser.role != "user" && entityUser.role != "coadmin") || isMemberDivision}
onChecked={() => {
handleCheckboxChange(index);
}}
@@ -440,20 +461,6 @@ export default function DocumentDivision() {
{(selectedFiles.length > 0 || dariSelectAll) && (
<View style={[ColorsStatus.primary, Styles.bottomMenuSelectDocument]}>
<View style={[Styles.rowItemsCenter, { justifyContent: "center" }]}>
{/* <MenuItemRow
icon={
<MaterialCommunityIcons
name="download-outline"
color="white"
size={25}
/>
}
title="Unduh"
onPress={() => { }}
column="many"
color="white"
disabled={selectedFiles.length == 0 || !copyAllowed}
/> */}
<MenuItemRow
icon={
<MaterialCommunityIcons
@@ -551,7 +558,7 @@ export default function DocumentDivision() {
isVisible={isRename}
setVisible={() => { setRename(false) }}
onSubmit={() => { handleRename() }}
disableSubmit={bodyRename.name == ""}
disableSubmit={bodyRename.name == "" || loadingRename}
>
<View>
<InputForm

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberTask, apiGetDivisionMember, apiGetTaskOne } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
@@ -117,13 +118,13 @@ export default function AddMemberTask() {
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -134,7 +135,9 @@ export default function AddMemberTask() {
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
>
{
data.length > 0 ?
@@ -149,7 +152,7 @@ export default function AddMemberTask() {
}}
>
<View style={[Styles.rowItemsCenter, Styles.w80]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1}>{item.name}</Text>
{
@@ -158,7 +161,7 @@ export default function AddMemberTask() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -8,7 +8,7 @@ import SectionMemberTask from "@/components/task/sectionMemberTask";
import SectionReportTask from "@/components/task/sectionReportTask";
import SectionTanggalTugasTask from "@/components/task/sectionTanggalTugasTask";
import Styles from "@/constants/Styles";
import { apiGetTaskOne } from "@/lib/api";
import { apiGetDivisionOneFeature, apiGetTaskOne } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
@@ -32,6 +32,35 @@ export default function DetailTaskDivision() {
const [progress, setProgress] = useState(0)
const update = useSelector((state: any) => state.taskUpdate)
const [refreshing, setRefreshing] = useState(false)
const [isMemberDivision, setIsMemberDivision] = useState(false);
const [isAdminDivision, setIsAdminDivision] = useState(false);
const entityUser = useSelector((state: any) => state.user);
async function handleCheckMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-member",
});
setIsMemberDivision(response.data);
const response2 = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-admin",
});
setIsAdminDivision(response2.data);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleCheckMember()
}, [])
async function handleLoad(cat: 'data' | 'progress') {
@@ -74,7 +103,9 @@ export default function DetailTaskDivision() {
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: loading ? 'Loading... ' : data?.title,
headerTitleAlign: 'center',
headerRight: () => <HeaderRightTaskDetail id={detail} division={id} status={data?.status} />,
headerRight: () => (entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision
? <></>
: <HeaderRightTaskDetail id={detail} division={id} status={data?.status} isAdminDivision={isAdminDivision} />,
}}
/>
<ScrollView
@@ -91,10 +122,10 @@ export default function DetailTaskDivision() {
}
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
<SectionReportTask refreshing={refreshing} />
<SectionTanggalTugasTask refreshing={refreshing} />
<SectionFileTask refreshing={refreshing} />
<SectionLinkTask refreshing={refreshing} />
<SectionMemberTask refreshing={refreshing} />
<SectionTanggalTugasTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
<SectionFileTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
<SectionLinkTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
<SectionMemberTask refreshing={refreshing} isAdminDivision={isAdminDivision} />
</View>
</ScrollView>
</SafeAreaView>

View File

@@ -9,6 +9,7 @@ import MenuItemRow from "@/components/menuItemRow";
import ModalSelect from "@/components/modalSelect";
import SectionListAddTask from "@/components/project/sectionListAddTask";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateTask } from "@/lib/api";
import { setMemberChoose } from "@/lib/memberChoose";
@@ -189,7 +190,7 @@ export default function CreateTaskDivision() {
borderType="bottom"
icon={
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
size="sm"
/>
}

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetDivisionMember } from "@/lib/api";
import { setMemberChoose } from "@/lib/memberChoose";
@@ -87,13 +88,13 @@ export default function AddMemberCreateTask() {
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -104,7 +105,9 @@ export default function AddMemberCreateTask() {
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
>
{
data.length > 0 ?
@@ -118,13 +121,13 @@ export default function AddMemberCreateTask() {
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]}>{item.name}</Text>
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -106,7 +106,7 @@ export default function ListTask() {
return (
<View style={[Styles.p15, { flex: 1 }]}>
<View>
<ScrollView horizontal style={[Styles.mb10]}>
<ScrollView horizontal style={[Styles.mb10]} showsHorizontalScrollIndicator={false}>
<ButtonTab
active={statusFix}
value="0"
@@ -179,7 +179,7 @@ export default function ListTask() {
</Pressable>
</View>
</View>
<View style={[{ flex: 2 }]}>
<View style={[{ flex: 2 }, Styles.mt05]}>
{
loading ?
isList ?

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberDivision, apiGetDivisionOneDetail, apiGetUser } from "@/lib/api";
import { setUpdateDivision } from "@/lib/divisionUpdate";
@@ -118,13 +119,13 @@ export default function AddMemberDivision() {
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -135,7 +136,9 @@ export default function AddMemberDivision() {
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
>
{
data.length > 0 ?
@@ -150,7 +153,7 @@ export default function AddMemberDivision() {
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
{
@@ -159,7 +162,7 @@ export default function AddMemberDivision() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -10,7 +10,7 @@ import { apiGetDivisionOneDetail } from "@/lib/api"
import { useAuthSession } from "@/providers/AuthProvider"
import { router, Stack, useLocalSearchParams } from "expo-router"
import { useEffect, useState } from "react"
import { SafeAreaView, ScrollView, View } from "react-native"
import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
type Props = {
id: string,
@@ -26,11 +26,12 @@ export default function DetailDivisionFitur() {
const { id } = useLocalSearchParams<{ id: string }>()
const [data, setData] = useState<Props>()
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
async function handleLoad() {
async function handleLoad(loading: boolean) {
try {
setLoading(true)
setLoading(loading)
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDivisionOneDetail({ user: hasil, id })
setData(response.data.division)
@@ -42,9 +43,16 @@ export default function DetailDivisionFitur() {
}
useEffect(() => {
handleLoad()
handleLoad(true)
}, [])
const handleRefresh = async () => {
setRefreshing(true)
handleLoad(false)
await new Promise(resolve => setTimeout(resolve, 2000));
setRefreshing(false)
};
return (
<SafeAreaView>
<Stack.Screen
@@ -55,13 +63,21 @@ export default function DetailDivisionFitur() {
headerRight: () => <HeaderRightDivisionDetail id={id} />,
}}
/>
<ScrollView>
<CaraouselHome />
<ScrollView
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
/>
}
showsVerticalScrollIndicator={false}
>
<CaraouselHome refreshing={refreshing} />
<View style={[Styles.ph15, Styles.mb100]}>
<FiturDivisionDetail />
<TaskDivisionDetail />
<FileDivisionDetail />
<DiscussionDivisionDetail />
<FiturDivisionDetail refreshing={refreshing}/>
<TaskDivisionDetail refreshing={refreshing}/>
<FileDivisionDetail refreshing={refreshing}/>
<DiscussionDivisionDetail refreshing={refreshing}/>
</View>
</ScrollView>
</SafeAreaView>

View File

@@ -7,14 +7,16 @@ import ImageUser from "@/components/imageNew"
import SectionCancel from "@/components/sectionCancel"
import Skeleton from "@/components/skeleton"
import SkeletonTwoItem from "@/components/skeletonTwoItem"
import Text from "@/components/Text"
import { ColorsStatus } from "@/constants/ColorsStatus"
import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles"
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiUpdateStatusAdminDivision } from "@/lib/api"
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiGetDivisionOneFeature, apiUpdateStatusAdminDivision } from "@/lib/api"
import { useAuthSession } from "@/providers/AuthProvider"
import { Feather, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
import { router, Stack, useLocalSearchParams } from "expo-router"
import { useEffect, useState } from "react"
import { Pressable, SafeAreaView, ScrollView, Text, View } from "react-native"
import { Pressable, SafeAreaView, ScrollView, View } from "react-native"
import Toast from "react-native-toast-message"
import { useSelector } from "react-redux"
@@ -37,6 +39,7 @@ type PropsMember = {
}
export default function InformationDivision() {
const entityUser = useSelector((state: any) => state.user)
const { id } = useLocalSearchParams<{ id: string }>()
const [isModal, setModal] = useState(false)
const { token, decryptToken } = useAuthSession()
@@ -46,6 +49,8 @@ export default function InformationDivision() {
const update = useSelector((state: any) => state.divisionUpdate)
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
const [loading, setLoading] = useState(true)
const [isMemberDivision, setIsMemberDivision] = useState(false)
const [isAdminDivision, setIsAdminDivision] = useState(false)
const [dataMemberChoose, setDataMemberChoose] = useState({
id: '',
name: '',
@@ -111,12 +116,34 @@ export default function InformationDivision() {
}
}
async function handleCheckMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-member",
});
const response2 = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-admin",
});
setIsMemberDivision(response.data);
setIsAdminDivision(response2.data);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleLoad(false)
}, [refresh, update])
useEffect(() => {
handleLoad(true)
handleCheckMember()
}, [])
function handleChooseMember(item: PropsMember) {
@@ -131,7 +158,7 @@ export default function InformationDivision() {
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Informasi Divisi',
headerTitleAlign: 'center',
headerRight: () => <HeaderRightDivisionInfo id={id} active={dataDetail?.isActive} />,
headerRight: () => ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) && <HeaderRightDivisionInfo id={id} active={dataDetail?.isActive} />,
}}
/>
<ScrollView style={[Styles.h100]}>
@@ -159,6 +186,7 @@ export default function InformationDivision() {
<Text style={[Styles.textDefault, Styles.mv05]}>{dataMember.length} Anggota</Text>
<View style={[Styles.wrapPaper]}>
{
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) &&
dataDetail?.isActive && (
<BorderBottomItem
onPress={() => { router.push(`/division/${id}/add-member`) }}
@@ -186,9 +214,9 @@ export default function InformationDivision() {
<BorderBottomItem
key={index}
borderType="bottom"
onPress={() => { dataDetail?.isActive && handleChooseMember(item) }}
onPress={() => { dataDetail?.isActive && (isAdminDivision || (entityUser.role != "user" && entityUser.role != "coadmin")) && handleChooseMember(item) }}
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}
title={item.name}
rightTopInfo={item.isAdmin ? "Admin" : "Anggota"}

View File

@@ -2,12 +2,14 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import ImageUser from "@/components/imageNew";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateDivision } from "@/lib/api";
import { setFormCreateDivision } from "@/lib/divisionCreate";
import { setUpdateDivision } from "@/lib/divisionUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons";
import { StackActions, useNavigation } from "@react-navigation/native";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Pressable, SafeAreaView, ScrollView, View } from "react-native";
@@ -22,6 +24,7 @@ type Props = {
export default function CreateDivisionAddAdmin() {
const { token, decryptToken } = useAuthSession()
const navigation = useNavigation()
const { id } = useLocalSearchParams<{ id: string }>()
const [dataOld, setDataOld] = useState<Props[]>([])
const [data, setData] = useState<Props[]>([])
@@ -50,14 +53,14 @@ export default function CreateDivisionAddAdmin() {
async function handleAddMember() {
try {
setLoading(true)
dispatch(setFormCreateDivision({ ...update, admin: selectMember }))
const hasil = await decryptToken(String(token?.current))
const response = await apiCreateDivision({ ...update, user: hasil })
const response = await apiCreateDivision({ ...update, admin: selectMember, user: hasil })
if (response.success) {
Toast.show({ type: 'small', text1: 'Berhasil membuat divisi', })
dispatch(setFormCreateDivision({ admin: [], member: [], data: { idGroup: '', name: '', desc: '' } }))
dispatch(setUpdateDivision(!updateDivision))
router.replace(`/division/`)
navigation.dispatch(StackActions.pop(3))
// router.replace(`/division/`)
} else {
Toast.show({ type: 'small', text1: response.message, })
}
@@ -103,7 +106,7 @@ export default function CreateDivisionAddAdmin() {
}}
>
<View style={[Styles.rowItemsCenter, Styles.w70]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
{
@@ -112,7 +115,7 @@ export default function CreateDivisionAddAdmin() {
</View>
</View>
{
selectMember.some((i: any) => i == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetUser } from "@/lib/api";
import { setFormCreateDivision } from "@/lib/divisionCreate";
@@ -80,13 +81,13 @@ export default function CreateDivisionAddMember() {
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -97,7 +98,9 @@ export default function CreateDivisionAddMember() {
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
>
{
data.length > 0 ?
@@ -112,7 +115,7 @@ export default function CreateDivisionAddMember() {
}}
>
<View style={[Styles.rowItemsCenter, Styles.w70]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
{
@@ -121,7 +124,7 @@ export default function CreateDivisionAddMember() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -180,7 +180,7 @@ export default function ListDivision() {
</View>
}
<View style={[Styles.rowSpaceBetween]}>
<View style={[Styles.rowSpaceBetween, { alignItems: 'center' }]}>
<InputSearch width={68} onChange={setSearch} />
<Pressable
onPress={() => {
@@ -200,7 +200,7 @@ export default function ListDivision() {
</View>
)}
</View>
<View style={[{ flex: 2 }]}>
<View style={[{ flex: 2 }, Styles.mt05]}>
{
loading ?
isList ?

View File

@@ -4,16 +4,20 @@ import { InputForm } from "@/components/inputForm";
import ModalSelect from "@/components/modalSelect";
import SelectForm from "@/components/selectForm";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiEditProfile, apiGetProfile } from "@/lib/api";
import { setEntities } from "@/lib/entitiesSlice";
import { useAuthSession } from "@/providers/AuthProvider";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { useHeaderHeight } from "@react-navigation/elements";
import * as ImagePicker from "expo-image-picker";
import { router, Stack } from "expo-router";
import { useEffect, useState } from "react";
import {
Image,
KeyboardAvoidingView,
Platform,
Pressable,
SafeAreaView,
ScrollView,
@@ -36,6 +40,7 @@ type Props = {
};
export default function EditProfile() {
const headerHeight = useHeaderHeight()
const dispatch = useDispatch()
const entities = useSelector((state: any) => state.entities)
const { token, decryptToken } = useAuthSession()
@@ -121,7 +126,7 @@ export default function EditProfile() {
}
} else if (cat == "phone") {
setData({ ...data, phone: val });
if (val == "" || !(val.length >= 10 && val.length <= 15)) {
if (val == "" || !(val.length >= 9 && val.length <= 16)) {
setError({ ...error, phone: true });
} else {
setError({ ...error, phone: false });
@@ -230,116 +235,122 @@ export default function EditProfile() {
),
}}
/>
<ScrollView style={[Styles.h100]}>
<View style={[Styles.p15]}>
<View style={{ justifyContent: "center", alignItems: "center" }}>
{
selectedImage != undefined ? (
<Pressable onPress={pickImageAsync}>
<Image
src={
typeof selectedImage === "string"
? selectedImage
: selectedImage.uri
}
style={[Styles.userProfileBig]}
onError={() => { setErrorImg(true) }}
/>
<View style={[Styles.absoluteIconPicker]}>
<MaterialCommunityIcons name="image" color={'white'} size={15} />
</View>
</Pressable>
) : (
<Pressable onPress={pickImageAsync}>
<Image
source={errorImg ? require("../../assets/images/user.jpg") : { uri: `https://wibu-storage.wibudev.com/api/files/${data?.img}` }}
style={[Styles.userProfileBig]}
onError={() => { setErrorImg(true) }}
/>
<View style={[Styles.absoluteIconPicker]}>
<MaterialCommunityIcons name="image" color={'white'} size={15} />
</View>
</Pressable>
)
}
<KeyboardAvoidingView
style={[Styles.h100]}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={headerHeight}
>
<ScrollView showsVerticalScrollIndicator={false}>
<View style={[Styles.p15]}>
<View style={{ justifyContent: "center", alignItems: "center" }}>
{
selectedImage != undefined ? (
<Pressable onPress={pickImageAsync}>
<Image
src={
typeof selectedImage === "string"
? selectedImage
: selectedImage.uri
}
style={[Styles.userProfileBig]}
onError={() => { setErrorImg(true) }}
/>
<View style={[Styles.absoluteIconPicker]}>
<MaterialCommunityIcons name="image" color={'white'} size={15} />
</View>
</Pressable>
) : (
<Pressable onPress={pickImageAsync}>
<Image
source={errorImg ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${data?.img}` }}
style={[Styles.userProfileBig]}
onError={() => { setErrorImg(true) }}
/>
<View style={[Styles.absoluteIconPicker]}>
<MaterialCommunityIcons name="image" color={'white'} size={15} />
</View>
</Pressable>
)
}
</View>
<SelectForm
label="Jabatan"
placeholder="Pilih Jabatan"
value={choosePosition.label}
required
onPress={() => {
setValChoose(choosePosition.val);
setValSelect("position");
setSelect(true);
}}
error={error.position}
errorText="Jabatan tidak boleh kosong"
/>
<InputForm
label="NIK"
type="numeric"
placeholder="NIK"
required
value={data?.nik}
error={error.nik}
errorText="NIK Harus 16 Karakter"
onChange={val => {
validationForm("nik", val)
}}
/>
<InputForm
label="Nama"
type="default"
placeholder="Nama"
required
value={data?.name}
error={error.name}
errorText="Nama tidak boleh kosong"
onChange={val => {
validationForm("name", val)
}}
/>
<InputForm
label="Email"
type="default"
placeholder="Email"
required
value={data?.email}
error={error.email}
errorText="Email tidak valid"
onChange={val => {
validationForm("email", val)
}}
/>
<InputForm
label="Nomor Telepon"
type="numeric"
placeholder="8XX-XXX-XXX"
required
itemLeft={<Text>+62</Text>}
value={data?.phone}
error={error.phone}
errorText="Nomor Telepon tidak valid"
onChange={val => {
validationForm("phone", val)
}}
/>
<SelectForm
label="Jenis Kelamin"
placeholder="Pilih Jenis Kelamin"
value={chooseGender.label}
required
onPress={() => {
setValChoose(chooseGender.val);
setValSelect("gender");
setSelect(true);
}}
error={error.gender}
errorText="Jenis Kelamin tidak boleh kosong"
/>
</View>
<SelectForm
label="Jabatan"
placeholder="Pilih Jabatan"
value={choosePosition.label}
required
onPress={() => {
setValChoose(choosePosition.val);
setValSelect("position");
setSelect(true);
}}
error={error.position}
errorText="Jabatan tidak boleh kosong"
/>
<InputForm
label="NIK"
type="numeric"
placeholder="NIK"
required
value={data?.nik}
error={error.nik}
errorText="NIK Harus 16 Karakter"
onChange={val => {
validationForm("nik", val)
}}
/>
<InputForm
label="Nama"
type="default"
placeholder="Nama"
required
value={data?.name}
error={error.name}
errorText="Nama tidak boleh kosong"
onChange={val => {
validationForm("name", val)
}}
/>
<InputForm
label="Email"
type="default"
placeholder="Email"
required
value={data?.email}
error={error.email}
errorText="Email tidak valid"
onChange={val => {
validationForm("email", val)
}}
/>
<InputForm
label="Nomor Telepon"
type="numeric"
placeholder="8XX-XXX-XXX"
required
itemLeft={<Text>+62</Text>}
value={data?.phone}
error={error.phone}
errorText="Nomor Telepon tidak valid"
onChange={val => {
validationForm("phone", val)
}}
/>
<SelectForm
label="Jenis Kelamin"
placeholder="Pilih Jenis Kelamin"
value={chooseGender.label}
required
onPress={() => {
setValChoose(chooseGender.val);
setValSelect("gender");
setSelect(true);
}}
error={error.gender}
errorText="Jenis Kelamin tidak boleh kosong"
/>
</View>
</ScrollView>
</ScrollView>
</KeyboardAvoidingView>
<ModalSelect
category={valSelect}

View File

@@ -34,7 +34,7 @@ export default function Index() {
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
const [loading, setLoading] = useState(true)
const [status, setStatus] = useState<'true' | 'false'>('true')
const [loadingSubmit, setLoadingSubmit] = useState(false)
const [idChoose, setIdChoose] = useState('')
const [activeChoose, setActiveChoose] = useState(true)
const [titleChoose, setTitleChoose] = useState('')
@@ -49,12 +49,14 @@ export default function Index() {
async function handleEdit() {
try {
setLoadingSubmit(true)
const hasil = await decryptToken(String(token?.current))
const response = await apiEditGroup({ user: hasil, name: titleChoose }, idChoose)
dispatch(setUpdateGroup(!update))
} catch (error) {
console.error(error)
} finally {
setLoadingSubmit(false)
setVisibleEdit(false)
setModal(false)
Toast.show({ type: 'small', text1: 'Berhasil mengupdate data', })
@@ -126,7 +128,7 @@ export default function Index() {
return (
<View style={[Styles.p15, { flex: 1 }]}>
<View>
<View style={[Styles.mb10]}>
<View style={[Styles.wrapBtnTab]}>
<ButtonTab
active={status == "false" ? "false" : "true"}
@@ -145,7 +147,7 @@ export default function Index() {
</View>
<InputSearch onChange={setSearch} />
</View>
<View style={{ flex: 2 }}>
<View style={[{ flex: 2 }, Styles.mt05]}>
{
loading ?
arrSkeleton.map((item, index) => {
@@ -234,7 +236,7 @@ export default function Index() {
onChange={(val) => { validationForm(val, 'title') }} />
</View>
<View>
<ButtonForm text="SIMPAN" disabled={Object.values(error).some((v) => v == true) || titleChoose == ""} onPress={() => { handleEdit() }} />
<ButtonForm text="SIMPAN" disabled={Object.values(error).some((v) => v == true) || titleChoose == "" || loadingSubmit} onPress={() => { handleEdit() }} />
</View>
</View>
</DrawerBottom>

View File

@@ -13,8 +13,8 @@ import { apiGetProfile } from "@/lib/api";
import { setEntities } from "@/lib/entitiesSlice";
import { useAuthSession } from "@/providers/AuthProvider";
import { Stack } from "expo-router";
import { useEffect } from "react";
import { Platform, SafeAreaView, ScrollView, View } from "react-native";
import { useEffect, useState } from "react";
import { Platform, RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useDispatch, useSelector } from "react-redux";
@@ -22,8 +22,9 @@ import { useDispatch, useSelector } from "react-redux";
export default function Home() {
const entities = useSelector((state: any) => state.entities)
const dispatch = useDispatch()
const { token, decryptToken } = useAuthSession()
const insets = useSafeAreaInsets();
const { token, decryptToken, signOut } = useAuthSession()
const insets = useSafeAreaInsets()
const [refreshing, setRefreshing] = useState(false)
useEffect(() => {
handleUserLogin()
@@ -31,9 +32,20 @@ export default function Home() {
async function handleUserLogin() {
const hasil = await decryptToken(String(token?.current))
apiGetProfile({ id: hasil }).then((data) => dispatch(setEntities(data.data)));
apiGetProfile({ id: hasil })
.then((data) => dispatch(setEntities(data.data)))
.catch((error) => {
signOut()
});
}
const handleRefresh = async () => {
setRefreshing(true)
handleUserLogin()
await new Promise(resolve => setTimeout(resolve, 2000));
setRefreshing(false)
};
return (
<SafeAreaView>
<Stack.Screen
@@ -48,16 +60,24 @@ export default function Home() {
),
}}
/>
<ScrollView>
<CaraouselHome />
<ScrollView
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
/>
}
showsVerticalScrollIndicator={false}
>
<CaraouselHome refreshing={refreshing}/>
<View style={[Styles.ph15, Styles.mb100]}>
<FiturHome />
<ProjectHome />
<DivisionHome />
<ChartProgresHome />
<ChartDokumenHome />
<EventHome />
<DisccussionHome />
<ProjectHome refreshing={refreshing}/>
<DivisionHome refreshing={refreshing}/>
<ChartProgresHome refreshing={refreshing}/>
<ChartDokumenHome refreshing={refreshing}/>
<EventHome refreshing={refreshing}/>
<DisccussionHome refreshing={refreshing}/>
</View>
</ScrollView>
</SafeAreaView>

View File

@@ -5,6 +5,7 @@ import LabelStatus from "@/components/labelStatus";
import HeaderRightMemberDetail from "@/components/member/headerMemberDetail";
import Skeleton from "@/components/skeleton";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import { valueRoleUser } from "@/constants/RoleUser";
import Styles from "@/constants/Styles";
import { apiGetProfile } from "@/lib/api";
@@ -93,8 +94,8 @@ export default function MemberDetail() {
</>
:
<>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${data?.img}`} size="lg" />
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10]}>{data?.name}</Text>
<ImageUser src={`${ConstEnv.url_storage}/files/${data?.img}`} size="lg" />
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10, { textAlign: 'center' }]}>{data?.name}</Text>
<Text style={[Styles.textMediumNormal, Styles.cWhite]}>{data?.role}</Text>
</>

View File

@@ -117,7 +117,7 @@ export default function CreateMember() {
}
} else if (cat == "phone") {
setDataForm({ ...dataForm, phone: val });
if (val == "" || !(val.length >= 10 && val.length <= 15)) {
if (val == "" || !(val.length >= 9 && val.length <= 16)) {
setError({ ...error, phone: true });
} else {
setError({ ...error, phone: false });
@@ -231,7 +231,7 @@ export default function CreateMember() {
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={headerHeight}
>
<ScrollView>
<ScrollView showsVerticalScrollIndicator={false}>
<View style={[Styles.p15]}>
<View style={{ justifyContent: "center", alignItems: "center" }}>
{selectedImage != undefined ? (

View File

@@ -4,6 +4,7 @@ import { InputForm } from "@/components/inputForm";
import ModalSelect from "@/components/modalSelect";
import SelectForm from "@/components/selectForm";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiEditUser, apiGetProfile } from "@/lib/api";
import { setUpdateMember } from "@/lib/memberSlice";
@@ -83,7 +84,7 @@ export default function EditMember() {
try {
const response = await apiGetProfile({ id: id });
setData(response.data);
setSelectedImage({ uri: `https://wibu-storage.wibudev.com/api/files/${response.data.img}`, });
setSelectedImage({ uri: `${ConstEnv.url_storage}/files/${response.data.img}`, });
setChoosePosition({
val: response.data.idPosition,
label: response.data.position,
@@ -148,7 +149,7 @@ export default function EditMember() {
}
} else if (cat == "phone") {
setData({ ...data, phone: val });
if (val == "" || !(val.length >= 10 && val.length <= 15)) {
if (val == "" || !(val.length >= 9 && val.length <= 16)) {
setError({ ...error, phone: true });
} else {
setError({ ...error, phone: false });
@@ -263,14 +264,14 @@ export default function EditMember() {
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={headerHeight}
>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
<View style={[Styles.p15]}>
<View style={{ justifyContent: "center", alignItems: "center" }}>
{
errorImg ?
<Pressable onPress={pickImageAsync}>
<Image
source={errorImg ? require("../../../../assets/images/user.jpg") : { uri: `https://wibu-storage.wibudev.com/api/files/${data?.img}` }}
source={errorImg ? require("../../../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${data?.img}` }}
style={[Styles.userProfileBig]}
onError={() => { setErrorImg(true) }}
/>

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import InputSearch from "@/components/inputSearch";
import SkeletonTwoItem from "@/components/skeletonTwoItem";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetUser } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
@@ -128,7 +129,7 @@ export default function Index() {
</View>
}
</View>
<View style={[{ flex: 2 }]}>
<View style={[{ flex: 2 }, Styles.mt05]}>
{
loading ?
arrSkeleton.map((item, index) => {
@@ -150,7 +151,7 @@ export default function Index() {
onPress={() => { router.push(`/member/${item.id}`) }}
borderType="all"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />
}
title={item.name}
subtitle={`${item.group} - ${item.position}`}

View File

@@ -9,7 +9,7 @@ import { pushToPage } from "@/lib/pushToPage";
import { useAuthSession } from "@/providers/AuthProvider";
import { Feather } from "@expo/vector-icons";
import { useEffect, useState } from "react";
import { SafeAreaView, View, VirtualizedList } from "react-native";
import { RefreshControl, SafeAreaView, View, VirtualizedList } from "react-native";
import { useDispatch, useSelector } from "react-redux";
type Props = {
@@ -31,6 +31,7 @@ export default function Notification() {
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
const dispatch = useDispatch()
const updateNotification = useSelector((state: any) => state.notificationUpdate)
const [refreshing, setRefreshing] = useState(false)
async function handleLoad(loading: boolean, thisPage: number) {
try {
@@ -88,33 +89,12 @@ export default function Notification() {
}
}
// function pushToPage(category: string, idContent: string) {
// const cat = category.split('/')
// if (cat.length > 1) {
// if (cat[2] == 'calendar') {
// router.push(`/division/${cat[1]}/calendar/${idContent}`)
// } else if (cat[2] == 'discussion') {
// router.push(`/division/${cat[1]}/discussion/${idContent}`)
// } else if (cat[2] == 'document') {
// router.push(`/division/${cat[1]}/document/${idContent}`)
// } else if (cat[2] == 'task') {
// router.push(`/division/${cat[1]}/task/${idContent}`)
// }
// } else {
// if (cat[0] == 'announcement') {
// router.push(`/announcement/${idContent}`)
// } else if (cat[0] == 'discussion-general') {
// router.push(`/discussion/${idContent}`)
// } else if (cat[0] == 'division') {
// router.push(`/division/${idContent}`)
// } else if (cat[0] == 'member') {
// router.push(`/member/${idContent}`)
// } else if (cat[0] == 'project') {
// router.push(`/project/${idContent}`)
// }
// }
// }
const handleRefresh = async () => {
setRefreshing(true)
handleLoad(false, 1)
await new Promise(resolve => setTimeout(resolve, 2000));
setRefreshing(false)
};
return (
<SafeAreaView>
@@ -156,6 +136,12 @@ export default function Notification() {
onEndReached={loadMoreData}
onEndReachedThreshold={0.5}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
/>
}
/>
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>

View File

@@ -40,6 +40,7 @@ export default function Index() {
const [data, setData] = useState<Props[]>([])
const [search, setSearch] = useState('')
const [nameGroup, setNameGroup] = useState('')
const [loadingSubmit, setLoadingSubmit] = useState(false)
const [chooseData, setChooseData] = useState({ name: '', id: '', active: false, idGroup: '' })
const [error, setError] = useState({
name: false,
@@ -94,15 +95,20 @@ export default function Index() {
async function handleEdit() {
try {
setLoadingSubmit(true)
const hasil = await decryptToken(String(token?.current))
const response = await apiEditPosition({ user: hasil, name: chooseData.name, idGroup: chooseData.idGroup }, chooseData.id)
dispatch(setUpdatePosition(!update))
if (response.success) {
dispatch(setUpdatePosition(!update))
} else {
Toast.show({ type: 'small', text1: response.message, })
}
} catch (error) {
console.error(error)
} finally {
setLoadingSubmit(false)
setVisibleEdit(false)
setModal(false)
Toast.show({ type: 'small', text1: 'Berhasil mengupdate data', })
}
}
@@ -165,7 +171,7 @@ export default function Index() {
</View>
}
</View>
<View style={[{ flex: 2 }]}>
<View style={[{ flex: 2 }, Styles.mt05]}>
{
loading ?
arrSkeleton.map((item, index) => {
@@ -183,7 +189,10 @@ export default function Index() {
return (
<BorderBottomItem
key={index}
onPress={() => { handleChooseData(item.id, item.name, item.isActive, item.idGroup) }}
onPress={() => {
entityUser.role != "user" &&
handleChooseData(item.id, item.name, item.isActive, item.idGroup)
}}
borderType="all"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
@@ -251,7 +260,7 @@ export default function Index() {
/>
</View>
<View style={Styles.mb30}>
<ButtonForm text="SIMPAN" onPress={() => { checkForm() }} />
<ButtonForm text="SIMPAN" onPress={() => { handleEdit() }} disabled={Object.values(error).some((v) => v == true) || chooseData.name == "" || loadingSubmit} />
</View>
</View>
</DrawerBottom>

View File

@@ -3,6 +3,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import { ButtonHeader } from "@/components/buttonHeader";
import ItemDetailMember from "@/components/itemDetailMember";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons";
@@ -37,11 +38,11 @@ export default function Profile() {
/>
}}
/>
<ScrollView>
<ScrollView style={[Styles.h100]}>
<View style={{ flexDirection: 'column' }}>
<View style={[Styles.wrapHeadViewMember]}>
<Image
source={error ? require("../../assets/images/user.jpg") : { uri: `https://wibu-storage.wibudev.com/api/files/${entities.img}` }}
source={error ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${entities.img}` }}
onError={() => { setError(true) }}
style={[Styles.userProfileBig]}
/>

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberProject, apiGetProjectOne, apiGetUser } from "@/lib/api";
import { setUpdateProject } from "@/lib/projectUpdate";
@@ -116,13 +117,13 @@ export default function AddMemberProject() {
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -133,35 +134,43 @@ export default function AddMemberProject() {
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
style={[Styles.h100]}
>
{
data.length > 0 ?
data.map((item: any, index: any) => {
const found = dataOld.some((i: any) => i.idUser == item.id)
return (
<Pressable
key={index}
style={[Styles.itemSelectModal]}
onPress={() => {
!found && onChoose(item.id, item.name, item.img)
}}
>
<View style={[Styles.rowItemsCenter, Styles.w80]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1}>{item.name}</Text>
<View style={[Styles.mb100]}>
{
data.map((item: any, index: any) => {
const found = dataOld.some((i: any) => i.idUser == item.id)
return (
<Pressable
key={index}
style={[Styles.itemSelectModal]}
onPress={() => {
!found && onChoose(item.id, item.name, item.img)
}}
>
<View style={[Styles.rowItemsCenter, Styles.w80]}>
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1}>{item.name}</Text>
{
found && <Text style={[Styles.textInformation, Styles.cGray]}>sudah menjadi anggota</Text>
}
</View>
</View>
{
found && <Text style={[Styles.textInformation, Styles.cGray]}>sudah menjadi anggota</Text>
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
}
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
}
</Pressable>
)
}
)
</Pressable>
)
}
)
}
</View>
:
<Text style={[Styles.textDefault, { textAlign: 'center' }]}>Tidak ada data</Text>
}

View File

@@ -88,8 +88,11 @@ export default function ProjectCancel() {
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<ScrollView
showsVerticalScrollIndicator={false}
style={[Styles.h100]}
>
<View style={[Styles.p15]}>
<InputForm
label="Alasan Pembatalan"
type="default"
@@ -99,6 +102,7 @@ export default function ProjectCancel() {
error={error}
errorText="Alasan pembatalan harus diisi"
onChange={(val) => onValidation(val)}
multiline
/>
</View>
</ScrollView>

View File

@@ -107,8 +107,11 @@ export default function ReportProject() {
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<ScrollView
showsVerticalScrollIndicator={false}
style={[Styles.h100]}
>
<View style={[Styles.p15]}>
<InputForm
label="Laporan Kegiatan"
type="default"

View File

@@ -10,6 +10,7 @@ import ModalSelect from "@/components/modalSelect";
import SectionListAddTask from "@/components/project/sectionListAddTask";
import SelectForm from "@/components/selectForm";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateProject } from "@/lib/api";
import { setGroupChoose } from "@/lib/groupChoose";
@@ -41,6 +42,7 @@ export default function CreateProject() {
const taskCreate = useSelector((state: any) => state.taskCreate);
const update = useSelector((state: any) => state.projectUpdate)
const entityUser = useSelector((state: any) => state.user);
const userLogin = useSelector((state: any) => state.entities)
const [fileForm, setFileForm] = useState<any[]>([])
const [indexDelFile, setIndexDelFile] = useState<number>(0)
const [disableBtn, setDisableBtn] = useState(true)
@@ -85,7 +87,7 @@ export default function CreateProject() {
} else {
setError(error => ({ ...error, title: false }))
}
} else if (cat == "task" && hitung > 1) {
} else if (cat == "task" && hitung > 2) {
if (taskCreate.length == 0) {
setError(error => ({ ...error, task: true }))
} else {
@@ -113,6 +115,13 @@ export default function CreateProject() {
validationForm('task', '');
}, [taskCreate]);
useEffect(() => {
if (entityUser.role != "supadmin" && entityUser.role != "developer") {
validationForm('group', userLogin.idGroup, userLogin.group);
}
}, []);
async function handleCreate() {
try {
setLoading(true)
@@ -204,8 +213,11 @@ export default function CreateProject() {
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<ScrollView
showsVerticalScrollIndicator={false}
style={[Styles.h100]}
>
<View style={[Styles.p15]}>
{
(entityUser.role == "supadmin" || entityUser.role == "developer")
&&
@@ -300,7 +312,7 @@ export default function CreateProject() {
borderType="bottom"
icon={
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
size="sm"
/>
}

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetUser } from "@/lib/api";
import { setMemberChoose } from "@/lib/memberChoose";
@@ -94,13 +95,13 @@ export default function AddMemberCreateProject() {
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -111,7 +112,9 @@ export default function AddMemberCreateProject() {
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
>
{
data.length > 0 ?
@@ -125,7 +128,7 @@ export default function AddMemberCreateProject() {
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]}>{item.name}</Text>
</View>

View File

@@ -120,7 +120,7 @@ export default function ListProject() {
return (
<View style={[Styles.p15, { flex: 1 }]}>
<View>
<ScrollView horizontal style={[Styles.mb10]}>
<ScrollView horizontal style={[Styles.mb10]} showsHorizontalScrollIndicator={false}>
<ButtonTab
active={statusFix}
value="0"
@@ -178,7 +178,7 @@ export default function ListProject() {
n={4}
/>
</ScrollView>
<View style={[Styles.rowSpaceBetween]}>
<View style={[Styles.rowSpaceBetween, { alignItems: 'center' }]}>
<InputSearch width={68} onChange={setSearch} />
<Pressable
onPress={() => {
@@ -193,16 +193,19 @@ export default function ListProject() {
</Pressable>
</View>
<View style={[Styles.mv05]}>
<Text>Filter :
{
(entityUser.role == "supadmin" || entityUser.role == "developer") && nameGroup
}
{
(entityUser.role == 'user' || entityUser.role == 'coadmin' || entityUser.role == 'cosupadmin')
? (cat == 'null' || cat == 'undefined' || cat == undefined || cat == '' || cat == 'data-saya') ? 'Kegiatan Saya' : 'Semua Kegiatan'
: ''
}
</Text>
{
entityUser.role != 'cosupadmin' && entityUser.role != 'admin' &&
<Text>Filter :
{
(entityUser.role == "supadmin" || entityUser.role == "developer") && nameGroup
}
{
(entityUser.role == 'user' || entityUser.role == 'coadmin')
? (cat == 'null' || cat == 'undefined' || cat == undefined || cat == '' || cat == 'data-saya') ? 'Kegiatan Saya' : 'Semua Kegiatan'
: ''
}
</Text>
}
</View>
</View>
<View style={[{ flex: 2 }]}>

View File

@@ -1,15 +1,17 @@
import BorderBottomItem from "@/components/borderBottomItem";
import ButtonBackHeader from "@/components/buttonBackHeader";
import ImageUser from "@/components/imageNew";
import InputSearch from "@/components/inputSearch";
import Text from '@/components/Text';
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetSearch } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
import { router, Stack } from "expo-router";
import { useState } from "react";
import { FlatList, Image, SafeAreaView, View } from "react-native";
import React, { useState } from "react";
import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
import Toast from "react-native-toast-message";
type PropsUser = {
@@ -39,10 +41,13 @@ export default function Search() {
const [dataUser, setDataUser] = useState<PropsUser[]>([])
const [dataDivisi, setDataDivisi] = useState<PropDivisi[]>([])
const [dataProject, setDataProject] = useState<PropProject[]>([])
const [refreshing, setRefreshing] = useState(false)
const [search, setSearch] = useState('')
async function handleSearch(cari: string) {
try {
if (cari.length > 3) {
setSearch(cari)
if (cari.length >= 3) {
const user = await decryptToken(String(token?.current))
const hasil = await apiGetSearch({ text: cari, user: user })
if (hasil.success) {
@@ -64,6 +69,14 @@ export default function Search() {
}
const handleRefresh = async () => {
setRefreshing(true)
handleSearch(search)
await new Promise(resolve => setTimeout(resolve, 2000));
setRefreshing(false)
};
return (
<>
<SafeAreaView>
@@ -74,47 +87,49 @@ export default function Search() {
headerTitleAlign: 'center'
}}
/>
{/* <ScrollView> */}
<View style={[Styles.p15]}>
<InputSearch onChange={handleSearch} />
{
dataProject.length + dataDivisi.length + dataUser.length > 0
?
<View style={[Styles.wrapPaper, Styles.mb100]}>
<ScrollView
style={[Styles.h100]}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
/>
}
>
{
dataUser.length > 0 &&
<View style={[Styles.mb05]}>
<View style={[Styles.mv05, Styles.p10]}>
<Text>ANGGOTA</Text>
<FlatList
data={dataUser}
keyExtractor={(item) => String(item.id)}
renderItem={({ item }) => (
{
dataUser.map((item, index) => (
<BorderBottomItem
key={index}
borderType="bottom"
icon={<Image
source={{ uri: `https://wibu-storage.wibudev.com/api/files/${item.img}` }}
style={[Styles.userProfileSmall]}
/>}
icon={<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />}
title={item.name}
subtitle={`${item.group}-${item.position}`}
onPress={() => {
router.push(`/member/${item.id}`)
}}
/>
)}
/>
))
}
</View>
}
{
dataDivisi.length > 0 &&
<View style={[Styles.mb05]}>
<View style={[Styles.mv05, Styles.p10]}>
<Text>DIVISI</Text>
<FlatList
data={dataDivisi}
keyExtractor={(item) => String(item.id)}
renderItem={({ item }) => (
{
dataDivisi.map((item, index) => (
<BorderBottomItem
key={index}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.primary]}>
@@ -127,21 +142,20 @@ export default function Search() {
router.push(`/division/${item.id}`)
}}
/>
)}
/>
))
}
</View>
}
{
dataProject.length > 0 &&
<View style={[Styles.mb10]}>
<View style={[Styles.mv05, Styles.p10]}>
<Text>KEGIATAN</Text>
<FlatList
data={dataProject}
keyExtractor={(item) => String(item.id)}
renderItem={({ item }) => (
{
dataProject.map((item, index) => (
<BorderBottomItem
key={index}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.primary]}>
@@ -154,21 +168,18 @@ export default function Search() {
router.push(`/project/${item.id}`)
}}
/>
)}
/>
))
}
</View>
}
</View>
</ScrollView>
:
<View style={Styles.contentItemCenter}>
<View style={[Styles.contentItemCenter, Styles.mt10]}>
<Text style={[Styles.textInformation, Styles.cGray]}>Tidak ada data</Text>
</View>
}
</View>
{/* </ScrollView> */}
</SafeAreaView>
</>
)

View File

@@ -1,7 +1,6 @@
import ViewLogin from "@/components/auth/viewLogin";
import ViewVerification from "@/components/auth/viewVerification";
import Text from '@/components/Text';
import ToastCustom from "@/components/toastCustom";
import { requestPermission } from "@/lib/useNotification";
import { useAuthSession } from "@/providers/AuthProvider";
import { Redirect } from "expo-router";

View File

@@ -1,5 +1,6 @@
import { ButtonForm } from "@/components/buttonForm";
import Text from '@/components/Text';
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { useAuthSession } from "@/providers/AuthProvider";
import CryptoES from "crypto-es";
@@ -19,20 +20,19 @@ export default function Index() {
const login = (): void => {
const random: string = 'contohLoginMobileDarmasaba';
var mytexttoEncryption = "contohLoginMobileDarmasaba"
const encrypted = CryptoES.AES.encrypt(mytexttoEncryption, "your password").toString();
// var C = require("crypto-js");
// var Decrypted = C.AES.decrypt(encrypted, "your password");
// var result = Decrypted.toString(C.enc.Utf8);
const encrypted = CryptoES.AES.encrypt(mytexttoEncryption, ConstEnv.pass_encrypt).toString();
signIn(encrypted);
}
return (
<View style={Styles.wrapLogin} >
<View style={{ alignItems: "center", marginVertical: 50 }}>
<Image
source={require("../assets/images/splash-icon.png")}
style={{ width: 130, height: 130 }}
source={require("../assets/images/logo.png")}
style={[{ width: 300, height: 150 }]}
width={270}
height={110}
/>
<Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text>
{/* <Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text> */}
</View>
<View style={[Styles.mb30]}>
<Text style={[Styles.textDefaultSemiBold]}>Verifikasi Nomor Telepon</Text>

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
assets/images/logo-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

38
bump-version.js Normal file
View File

@@ -0,0 +1,38 @@
const fs = require("fs");
const path = require("path");
const configPath = path.join(__dirname, "app.config.js");
let configFile = fs.readFileSync(configPath, "utf8");
// --- Update versionCode ---
const codeRegex = /versionCode:\s*(\d+)/;
const codeMatch = configFile.match(codeRegex);
if (!codeMatch) {
console.error("❌ Tidak menemukan versionCode di app.config.js");
process.exit(1);
}
const currentCode = parseInt(codeMatch[1], 10);
const newCode = currentCode + 1;
configFile = configFile.replace(codeRegex, `versionCode: ${newCode}`);
// --- Update versionName ---
const nameRegex = /version:\s*"(.*?)"/;
const nameMatch = configFile.match(nameRegex);
if (!nameMatch) {
console.error("❌ Tidak menemukan version di app.config.js");
process.exit(1);
}
let [major, minor, patch] = nameMatch[1].split(".").map(Number);
patch += 1; // bump patch version
const newName = `${major}.${minor}.${patch}`;
configFile = configFile.replace(nameRegex, `version: "${newName}"`);
// --- Simpan file ---
fs.writeFileSync(configPath, configFile, "utf8");
console.log(`✅ versionCode: ${currentCode}${newCode}`);
console.log(`✅ versionName: ${nameMatch[1]}${newName}`);

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,8 +1,10 @@
import Styles from "@/constants/Styles"
import { apiCheckPhoneLogin, apiSendOtp } from "@/lib/api"
import { useAuthSession } from "@/providers/AuthProvider"
import AsyncStorage from "@react-native-async-storage/async-storage"
import { StatusBar } from "expo-status-bar"
import { useState } from "react"
import { Image, SafeAreaView, View } from "react-native"
import { Image, Platform, SafeAreaView, View } from "react-native"
import Toast from "react-native-toast-message"
import { ButtonForm } from "../buttonForm"
import { InputForm } from "../inputForm"
@@ -10,7 +12,6 @@ import ModalLoading from "../modalLoading"
import Text from "../Text"
import ToastCustom from "../toastCustom"
type Props = {
onValidate: ({ phone, otp }: { phone: string, otp: number }) => void
}
@@ -19,23 +20,29 @@ export default function ViewLogin({ onValidate }: Props) {
const [loadingLogin, setLoadingLogin] = useState(false)
const [disableLogin, setDisableLogin] = useState(true)
const [phone, setPhone] = useState('')
const { signIn, encryptToken } = useAuthSession();
const handleCheckPhone = async () => {
try {
setLoadingLogin(true)
const response = await apiCheckPhoneLogin({ phone: `62${phone}` });
const response = await apiCheckPhoneLogin({ phone: `62${phone}` })
if (response.success) {
const otp = Math.floor(1000 + Math.random() * 9000)
const responseOtp = await apiSendOtp({ phone: `62${phone}`, otp })
if (responseOtp == 200) {
// localStorage.setItem('user', response.id)
await AsyncStorage.setItem('user', response.id);
return onValidate({ phone: `62${phone}`, otp })
if (response.isWithoutOTP) {
const encrypted = await encryptToken(response.id)
signIn(encrypted)
} else {
const otp = Math.floor(1000 + Math.random() * 9000)
const responseOtp = await apiSendOtp({ phone: `62${phone}`, otp })
if (responseOtp == 200) {
await AsyncStorage.setItem('user', response.id)
return onValidate({ phone: `62${phone}`, otp })
}
}
} else {
return Toast.show({ type: 'small', text1: response.message, position: 'top' })
}
return Toast.show({ type: 'small', text1: response.message, })
} catch (error) {
return Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
return Toast.show({ type: 'small', text1: `Terjadi kesalahan, coba lagi`, position: 'top' })
} finally {
setLoadingLogin(false)
}
@@ -43,14 +50,17 @@ export default function ViewLogin({ onValidate }: Props) {
return (
<SafeAreaView>
<StatusBar style={Platform.OS === 'ios' ? 'auto' : 'light'} translucent={false} backgroundColor="black" />
<ToastCustom />
<View style={[Styles.p20, Styles.h100]}>
<View style={{ alignItems: "center", marginVertical: 50 }}>
<View style={{ alignItems: "center", marginTop: 70, marginBottom: 50 }}>
<Image
source={require("../../assets/images/splash-icon.png")}
style={{ width: 130, height: 130 }}
source={require("../../assets/images/logo.png")}
style={[{ width: 300, height: 150 }]}
width={270}
height={110}
/>
<Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text>
{/* <Text style={[Styles.textSubtitle2]}>Digitalisasi Desa Transparansi Kerja</Text> */}
</View>
<InputForm
onChange={(val) => {

View File

@@ -2,8 +2,9 @@ import Styles from "@/constants/Styles";
import { apiSendOtp } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { StatusBar } from "expo-status-bar";
import { useState } from "react";
import { Image, View } from "react-native";
import { Image, Platform, View } from "react-native";
import { OtpInput } from "react-native-otp-entry";
import Toast from 'react-native-toast-message';
import { ButtonForm } from "../buttonForm";
@@ -27,7 +28,7 @@ export default function ViewVerification({ phone, otp }: Props) {
const encrypted = await encryptToken(valueUser);
signIn(encrypted);
} else {
return Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
return Toast.show({ type: 'small', text1: 'Terjadi kesalahan', position: 'top' })
}
}
@@ -35,7 +36,7 @@ export default function ViewVerification({ phone, otp }: Props) {
if (value === otpFix.toString()) {
login()
} else {
return Toast.show({ type: 'small', text1: 'Kode OTP tidak sesuai' });
return Toast.show({ type: 'small', text1: 'Kode OTP tidak sesuai', position: 'top' });
}
}
@@ -56,14 +57,17 @@ export default function ViewVerification({ phone, otp }: Props) {
return (
<>
<StatusBar style={Platform.OS === 'ios' ? 'auto' : 'light'} translucent={false} backgroundColor="black" />
<ToastCustom />
<View style={Styles.wrapLogin} >
<View style={{ alignItems: "center", marginVertical: 50 }}>
<View style={{ alignItems: "center", marginTop: 70, marginBottom: 50 }}>
<Image
source={require("../../assets/images/splash-icon.png")}
style={{ width: 130, height: 130 }}
source={require("../../assets/images/logo.png")}
style={[{ width: 300, height: 150 }]}
width={270}
height={110}
/>
<Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text>
{/* <Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text> */}
</View>
<View style={[Styles.mb30]}>
<Text style={[Styles.textDefaultSemiBold]}>Verifikasi Nomor Telepon</Text>

View File

@@ -48,7 +48,7 @@ export default function BorderBottomItem({ title, subtitle, icon, desc, onPress,
</View>
</View>
{desc && <Text style={[Styles.textDefault, Styles.mt05, { textAlign: 'justify', color: textColorFix }]} numberOfLines={descEllipsize == false ? 0 : 2} ellipsizeMode='tail'>{desc}</Text>}
{desc && <Text style={[Styles.textDefault, Styles.mt05, { textAlign: 'left', color: textColorFix }]} numberOfLines={descEllipsize == false ? 0 : 2} ellipsizeMode='tail'>{desc}</Text>}
{
(leftBottomInfo || rightBottomInfo) &&
(

View File

@@ -15,7 +15,9 @@ export default function DiscussionItem({ title, user, date, onPress }: Props) {
<Pressable style={[Styles.wrapItemDiscussion]} onPress={onPress}>
<View style={[Styles.rowItemsCenter, Styles.mb10]}>
<Ionicons name="chatbox-ellipses-outline" size={22} color="black" style={Styles.mr10} />
<Text style={{ fontWeight: 'bold' }} numberOfLines={1} ellipsizeMode="tail">{title}</Text>
<View style={[{flex:1}]}>
<Text style={{ fontWeight: 'bold' }} numberOfLines={1} ellipsizeMode="tail">{title}</Text>
</View>
</View>
<View style={Styles.rowSpaceBetween}>
<View style={Styles.rowItemsCenter}>

View File

@@ -16,7 +16,10 @@ export default function HeaderDiscussionGeneral() {
return (
<>
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
{
entityUser.role != "user" && entityUser.role != "coadmin" &&
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
}
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu">
<View style={Styles.rowItemsCenter}>
<MenuItemRow

View File

@@ -16,15 +16,15 @@ type Props = {
date: string;
};
export default function DiscussionDivisionDetail() {
export default function DiscussionDivisionDetail({ refreshing }: { refreshing: boolean }) {
const { token, decryptToken } = useAuthSession();
const { id } = useLocalSearchParams<{ id: string }>();
const [data, setData] = useState<Props[]>([]);
const [loading, setLoading] = useState(true)
async function handleLoad() {
async function handleLoad(loading: boolean) {
try {
setLoading(true)
setLoading(loading)
const hasil = await decryptToken(String(token?.current));
const response = await apiGetDivisionOneFeature({
user: hasil,
@@ -40,8 +40,15 @@ export default function DiscussionDivisionDetail() {
}
useEffect(() => {
handleLoad();
}, []);
if (refreshing)
handleLoad(false)
}, [refreshing])
useEffect(() => {
handleLoad(true)
}, [])
return (
<View style={[Styles.mb15]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mv10]}>Diskusi</Text>

View File

@@ -1,3 +1,4 @@
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetDivisionOneFeature } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
@@ -8,11 +9,11 @@ import { useLocalSearchParams } from "expo-router";
import * as Sharing from 'expo-sharing';
import React, { useEffect, useState } from "react";
import { Alert, Dimensions, Platform, Pressable, View } from "react-native";
import Text from "../Text";
import * as mime from 'react-native-mime-types';
import { ICarouselInstance } from "react-native-reanimated-carousel";
import Skeleton from "../skeleton";
import ModalLoading from "../modalLoading";
import Skeleton from "../skeleton";
import Text from "../Text";
type Props = {
id: string
@@ -23,7 +24,7 @@ type Props = {
idStorage: string
}
export default function FileDivisionDetail() {
export default function FileDivisionDetail({ refreshing }: { refreshing: boolean }) {
const ref = React.useRef<ICarouselInstance>(null);
const width = Dimensions.get("window").width;
const [data, setData] = useState<Props[]>([])
@@ -32,9 +33,9 @@ export default function FileDivisionDetail() {
const [loading, setLoading] = useState(true)
const [loadingOpen, setLoadingOpen] = useState(false)
async function handleLoad() {
async function handleLoad(loading: boolean) {
try {
setLoading(true)
setLoading(loading)
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDivisionOneFeature({ user: hasil, id, cat: 'new-file' })
setData(response.data)
@@ -46,13 +47,18 @@ export default function FileDivisionDetail() {
}
useEffect(() => {
handleLoad()
if (refreshing)
handleLoad(false)
}, [refreshing])
useEffect(() => {
handleLoad(true)
}, [])
const openFile = (item: Props) => {
if (Platform.OS == 'android') setLoadingOpen(true)
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + item.idStorage;
let remoteUrl = ConstEnv.url_storage + '/files/' + item.idStorage;
const fileName = item.name + '.' + item.extension;
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName)

View File

@@ -16,7 +16,7 @@ type Props = {
kalender: number
}
export default function FiturDivisionDetail() {
export default function FiturDivisionDetail({ refreshing }: { refreshing: boolean }) {
const { token, decryptToken } = useAuthSession()
const { id } = useLocalSearchParams<{ id: string }>()
const [data, setData] = useState<Props>({
@@ -36,7 +36,10 @@ export default function FiturDivisionDetail() {
}
}
useEffect(() => {
if (refreshing)
handleLoad()
}, [refreshing])
useEffect(() => {
handleLoad()

View File

@@ -18,7 +18,7 @@ type Props = {
projectTitle: string
}
export default function TaskDivisionDetail() {
export default function TaskDivisionDetail({ refreshing }: { refreshing: boolean }) {
const { token, decryptToken } = useAuthSession()
const { id } = useLocalSearchParams<{ id: string }>()
const [data, setData] = useState<Props[]>([])
@@ -26,9 +26,9 @@ export default function TaskDivisionDetail() {
const width = Dimensions.get("window").width;
const [loading, setLoading] = useState(true)
async function handleLoad() {
async function handleLoad(loading: boolean) {
try {
setLoading(true)
setLoading(loading)
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDivisionOneFeature({ user: hasil, id, cat: 'today-task' })
setData(response.data)
@@ -40,7 +40,12 @@ export default function TaskDivisionDetail() {
}
useEffect(() => {
handleLoad()
if (refreshing)
handleLoad(false)
}, [refreshing])
useEffect(() => {
handleLoad(true)
}, [])
return (

View File

@@ -15,7 +15,7 @@ import { InputForm } from "../inputForm";
import MenuItemRow from "../menuItemRow";
import ModalFloat from "../modalFloat";
export default function HeaderRightDocument({ path }: { path: string }) {
export default function HeaderRightDocument({ path, isMember }: { path: string, isMember: boolean }) {
const [isVisible, setVisible] = useState(false);
const [newFolder, setNewFolder] = useState(false);
const { id } = useLocalSearchParams<{ id: string }>();
@@ -24,9 +24,12 @@ export default function HeaderRightDocument({ path }: { path: string }) {
const dispatch = useDispatch()
const update = useSelector((state: any) => state.dokumenUpdate)
const [loading, setLoading] = useState(false)
const [loadingFolder, setLoadingFolder] = useState(false)
const entityUser = useSelector((state: any) => state.user)
async function handleCreateFolder() {
try {
setLoadingFolder(true)
const hasil = await decryptToken(String(token?.current))
const response = await apiCreateFolderDocument({ data: { user: hasil, name, path, idDivision: id } })
if (response.success) {
@@ -39,6 +42,7 @@ export default function HeaderRightDocument({ path }: { path: string }) {
console.error(error)
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
} finally {
setLoadingFolder(false)
setNewFolder(false)
}
}
@@ -99,11 +103,14 @@ export default function HeaderRightDocument({ path }: { path: string }) {
return (
<>
<ButtonMenuHeader
onPress={() => {
setVisible(true);
}}
/>
{
((entityUser.role != "user" && entityUser.role != "coadmin") || isMember) &&
<ButtonMenuHeader
onPress={() => {
setVisible(true);
}}
/>
}
<DrawerBottom
animation="slide"
isVisible={isVisible}
@@ -148,7 +155,7 @@ export default function HeaderRightDocument({ path }: { path: string }) {
title="Buat Folder Baru"
isVisible={newFolder}
setVisible={() => { setNewFolder(false) }}
disableSubmit={name == ""}
disableSubmit={name == "" || loadingFolder}
onSubmit={() => { handleCreateFolder() }}
>
<View>

View File

@@ -11,9 +11,10 @@ type Props = {
checked?: boolean
onChecked?: () => void
onPress?: () => void
canChecked?: boolean
}
export default function ItemFile({ category, checked, dateTime, title, onChecked, onPress }: Props) {
export default function ItemFile({ category, checked, dateTime, title, onChecked, onPress, canChecked }: Props) {
return (
<View style={[Styles.wrapItemBorderBottom]}>
<View style={[Styles.rowItemsCenter]}>
@@ -43,18 +44,22 @@ export default function ItemFile({ category, checked, dateTime, title, onChecked
</Pressable>
<View style={[Styles.rowSpaceBetween, { flex: 1, alignItems: 'center' }]}>
<Pressable style={[Styles.ml10, {flex:1},]} onPress={onPress}>
<Pressable style={[Styles.ml10, { flex: 1 },]} onPress={onPress}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{title}</Text>
<Text style={[Styles.textInformation]}>{dateTime}</Text>
</Pressable>
<Pressable onPress={onChecked}>
{
checked
? <MaterialCommunityIcons name="checkbox-marked-circle" size={25} color={'black'} />
: <MaterialCommunityIcons name="checkbox-blank-circle-outline" size={25} color={'#ced4da'} />
}
{
!canChecked ? <></>
:
<Pressable onPress={onChecked}>
{
checked
? <MaterialCommunityIcons name="checkbox-marked-circle" size={25} color={'black'} />
: <MaterialCommunityIcons name="checkbox-blank-circle-outline" size={25} color={'#ced4da'} />
}
</Pressable>
</Pressable>
}
</View>
</View>
</View>

View File

@@ -0,0 +1,65 @@
import Styles from "@/constants/Styles";
import { apiCreateFolderDocument } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { Pressable, View } from "react-native";
import Toast from "react-native-toast-message";
import Text from "../Text";
import { InputForm } from "../inputForm";
import ModalFloat from "../modalFloat";
export function ModalNewFolder({ path, onCreated }: { path: string, onCreated: () => void }) {
const { token, decryptToken } = useAuthSession()
const [newFolder, setNewFolder] = useState(false)
const [name, setName] = useState("")
const [loadingFolder, setLoadingFolder] = useState(false)
const { id } = useLocalSearchParams<{ id: string }>();
async function handleCreateFolder() {
try {
setLoadingFolder(true)
const hasil = await decryptToken(String(token?.current))
const response = await apiCreateFolderDocument({ data: { user: hasil, name, path, idDivision: id } })
if (response.success) {
Toast.show({ type: 'small', text1: 'Berhasil membuat folder baru', })
} else {
Toast.show({ type: 'small', text1: response.message, })
}
} catch (error) {
console.error(error)
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
} finally {
onCreated()
setLoadingFolder(false)
setNewFolder(false)
}
}
return (
<>
<Pressable style={[Styles.pv05, Styles.borderRight, { width: '50%' }]} onPress={() => setNewFolder(true)}>
<Text style={[Styles.textDefaultSemiBold, { textAlign: 'center' }]}>FOLDER BARU</Text>
</Pressable>
<ModalFloat
title="Buat Folder Baru"
isVisible={newFolder}
setVisible={() => { setNewFolder(false) }}
disableSubmit={name == "" || loadingFolder}
onSubmit={() => { handleCreateFolder() }}
>
<View>
<InputForm
type="default"
placeholder="Nama Folder"
required
label="Nama Folder"
onChange={(value: string) => { setName(value) }}
/>
</View>
</ModalFloat>
</>
)
}

View File

@@ -8,6 +8,7 @@ import { Pressable, View } from "react-native"
import BorderBottomItem from "../borderBottomItem"
import DrawerBottom from "../drawerBottom"
import Text from "../Text"
import { ModalNewFolder } from "./modalNewFolder"
type Props = {
open: boolean
@@ -106,9 +107,7 @@ export default function ModalSalinMove({ open, close, category, onConfirm, dataC
}
</View>
<View style={[Styles.rowOnly, Styles.mt15, Styles.absolute0]}>
<Pressable style={[Styles.pv05, Styles.borderRight, { width: '50%' }]} onPress={() => close(false)}>
<Text style={[Styles.textDefaultSemiBold, { textAlign: 'center' }]}>BATAL</Text>
</Pressable>
<ModalNewFolder path={path} onCreated={() => getData()} />
<Pressable style={[Styles.pv05, { width: '50%' }]} onPress={() => onConfirm(path)}>
<Text style={[Styles.textDefaultSemiBold, { textAlign: 'center' }]}>{category == 'copy' ? 'SALIN' : 'PINDAH'}</Text>
</Pressable>

Some files were not shown because too many files have changed in this diff Show More