Vitalik
2 years ago
37 changed files with 795 additions and 461 deletions
@ -0,0 +1,153 @@
@@ -0,0 +1,153 @@
|
||||
import { fetchTPermittedExecutorsReq } from '@/api' |
||||
import { IPermittedExecutorResponse } from '@/api/users/responses.interface' |
||||
import { transformPermittedExecutors } from '@/api/users/transforms' |
||||
import { SkeletonDataKey } from '@/services/system' |
||||
import { |
||||
$size, |
||||
InfoRowCard, |
||||
IShortUser, |
||||
RouteKey, |
||||
useFlatList, |
||||
useNav, |
||||
} from '@/shared' |
||||
import { ContentBlock } from '@/shared/components/blocks' |
||||
import React, { FC, useCallback, useEffect } from 'react' |
||||
import { |
||||
ActivityIndicator, |
||||
Dimensions, |
||||
InteractionManager, |
||||
StyleSheet, |
||||
View, |
||||
} from 'react-native' |
||||
import Swiper from 'react-native-swiper' |
||||
import { useFetchPermittedExecutors } from '../hooks' |
||||
|
||||
const { width: screenWidth } = Dimensions.get('screen') |
||||
|
||||
interface ExecutorsSliderListSmartProps { |
||||
searchString: string |
||||
onLoaded?: (count: number) => void |
||||
} |
||||
|
||||
export const ExecutorsSliderListSmart: FC<ExecutorsSliderListSmartProps> = ({ |
||||
searchString, |
||||
onLoaded, |
||||
}) => { |
||||
const nav = useNav() |
||||
|
||||
const { |
||||
items: executors, |
||||
isLoading, |
||||
setLoadParams, |
||||
count, |
||||
resetFlatList, |
||||
isEmpty, |
||||
} = useFlatList<IShortUser>({ |
||||
fetchItems: fetchTPermittedExecutorsReq, |
||||
needInit: false, |
||||
serrializatorItems: (_items: IPermittedExecutorResponse[]) => |
||||
transformPermittedExecutors(_items), |
||||
loadParams: { |
||||
sort: 'DESC', |
||||
sortField: 'id', |
||||
searchString, |
||||
}, |
||||
limit: 5, |
||||
defaultLoading: false, |
||||
skeletonDataKey: SkeletonDataKey.HomeExecutors, |
||||
clearWhenReload: false, |
||||
defaultItems: [], |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
InteractionManager.runAfterInteractions(() => { |
||||
resetFlatList() |
||||
}) |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
if (onLoaded) onLoaded(count) |
||||
}, [count]) |
||||
|
||||
useEffect(() => { |
||||
if (searchString !== null) setLoadParams({ searchString }) |
||||
}, [searchString]) |
||||
|
||||
const renderItem = item => { |
||||
return ( |
||||
<View key={item.id} style={styles.container}> |
||||
<InfoRowCard |
||||
title={item.fullName} |
||||
info={item.position} |
||||
imageUri={item.avatarUrl} |
||||
containerStyle={styles.cardContainer} |
||||
onPressCard={() => |
||||
nav.navigate(RouteKey.ExecutorTasks, { |
||||
userData: item, |
||||
}) |
||||
} |
||||
onPressInfo={() => |
||||
nav.navigate(RouteKey.ContactDetail, { |
||||
contactId: item.id, |
||||
}) |
||||
} |
||||
/> |
||||
</View> |
||||
) |
||||
} |
||||
const pressExecutorsHandler = useCallback(() => { |
||||
nav.navigate(RouteKey.Executors) |
||||
}, [nav.navigate]) |
||||
|
||||
if (searchString !== null && !isLoading && isEmpty) return null |
||||
|
||||
const renderContent = () => { |
||||
if (isLoading) { |
||||
return ( |
||||
<View style={styles.loading}> |
||||
<ActivityIndicator /> |
||||
</View> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
<Swiper |
||||
showsButtons={false} |
||||
horizontal |
||||
bounces={false} |
||||
loop={false} |
||||
automaticallyAdjustContentInsets |
||||
showsPagination={false}> |
||||
{executors.map(renderItem)} |
||||
</Swiper> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
<ContentBlock |
||||
title={'Виконавці'} |
||||
btnTitle={'Дивитись усі'} |
||||
onPressBtn={pressExecutorsHandler} |
||||
paddingHorizontal={$size(0)} |
||||
style={styles.content}> |
||||
{renderContent()} |
||||
</ContentBlock> |
||||
) |
||||
} |
||||
|
||||
export const styles = StyleSheet.create({ |
||||
loading: { |
||||
justifyContent: 'center', |
||||
alignItems: 'center', |
||||
}, |
||||
container: { |
||||
width: screenWidth - 40, |
||||
marginLeft: 20, |
||||
}, |
||||
cardContainer: { |
||||
width: '100%', |
||||
}, |
||||
content: { |
||||
marginTop: $size(15, 10), |
||||
}, |
||||
}) |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export * from './executors-slider-list.smart-component' |
@ -1,42 +1,14 @@
@@ -1,42 +1,14 @@
|
||||
export const groupsData = [ |
||||
{ |
||||
title: 'Title', |
||||
info: '25 Pflfx', |
||||
}, |
||||
{ |
||||
title: 'Title', |
||||
info: 'Info', |
||||
}, |
||||
{ |
||||
title: 'Title', |
||||
info: 'Info', |
||||
}, |
||||
{ |
||||
title: 'Title', |
||||
info: 'Info', |
||||
}, |
||||
{ |
||||
title: 'Title', |
||||
info: 'Info', |
||||
}, |
||||
{ |
||||
title: 'Title', |
||||
info: 'Info', |
||||
}, |
||||
{ |
||||
title: 'Title', |
||||
info: 'Info', |
||||
}, |
||||
{ |
||||
title: 'Title', |
||||
info: 'Info', |
||||
}, |
||||
{ |
||||
title: 'Title', |
||||
info: 'Info', |
||||
}, |
||||
{ |
||||
title: 'Title', |
||||
info: 'Info', |
||||
import { ITaxonomyWithTasksCount } from '@/shared' |
||||
|
||||
export const groupsData: ITaxonomyWithTasksCount[] = [ |
||||
{ |
||||
id: null, |
||||
parentId: null, |
||||
name: null, |
||||
type: null, |
||||
isDeleted: null, |
||||
isDefault: null, |
||||
iconUrl: null, |
||||
tasksCount: null, |
||||
}, |
||||
] |
||||
|
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export * from './groups-data.mock' |
@ -0,0 +1,148 @@
@@ -0,0 +1,148 @@
|
||||
import React, { FC, useCallback, useEffect } from 'react' |
||||
import { |
||||
InfoRowCard, |
||||
ITaxonomyWithTasksCount, |
||||
RouteKey, |
||||
useFlatList, |
||||
useNav, |
||||
} from '@/shared' |
||||
import { $size, getTitleByCount } from '@/shared/helpers' |
||||
import { |
||||
ActivityIndicator, |
||||
Dimensions, |
||||
InteractionManager, |
||||
StyleSheet, |
||||
View, |
||||
} from 'react-native' |
||||
import Swiper from 'react-native-swiper' |
||||
import { fetchGroupsWithTasksCountReq } from '@/api' |
||||
import { SkeletonDataKey } from '@/services/system' |
||||
import { ContentBlock } from '@/shared/components/blocks' |
||||
|
||||
const { width: screenWidth } = Dimensions.get('screen') |
||||
|
||||
interface GroupsSliderListProps { |
||||
searchString: string |
||||
onLoaded?: (count: number) => void |
||||
} |
||||
|
||||
export const GroupsSliderList: FC<GroupsSliderListProps> = ({ |
||||
searchString, |
||||
onLoaded, |
||||
}) => { |
||||
const nav = useNav() |
||||
|
||||
const { |
||||
items: groups, |
||||
isLoading, |
||||
resetFlatList, |
||||
setLoadParams, |
||||
count, |
||||
isEmpty, |
||||
} = useFlatList<ITaxonomyWithTasksCount>({ |
||||
fetchItems: fetchGroupsWithTasksCountReq, |
||||
needInit: false, |
||||
loadParams: { |
||||
withoutEmpty: true, |
||||
sort: 'DESC', |
||||
sortField: 'id', |
||||
}, |
||||
limit: 30, |
||||
defaultLoading: false, |
||||
skeletonDataKey: SkeletonDataKey.HomeGroups, |
||||
clearWhenReload: false, |
||||
defaultItems: [], |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
InteractionManager.runAfterInteractions(() => { |
||||
resetFlatList() |
||||
}) |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
if (onLoaded) onLoaded(count) |
||||
}, [count]) |
||||
|
||||
useEffect(() => { |
||||
if (searchString !== null) setLoadParams({ searchString }) |
||||
}, [searchString]) |
||||
|
||||
const renderItem = useCallback(item => { |
||||
if (!item || !item.tasksCount) return null |
||||
return ( |
||||
<View key={item.id}> |
||||
<InfoRowCard |
||||
title={item.name} |
||||
info={getTitleByCount(item.tasksCount, [ |
||||
'задача', |
||||
'задачі', |
||||
'задач', |
||||
])} |
||||
imageUri={item.iconUrl} |
||||
containerStyle={styles.cardContainer} |
||||
onPressCard={() => |
||||
nav.navigate(RouteKey.GroupTasks, { |
||||
groupData: item, |
||||
}) |
||||
} |
||||
onPressInfo={() => |
||||
console.log('i press group info', item.name) |
||||
} |
||||
/> |
||||
</View> |
||||
) |
||||
}, []) |
||||
|
||||
const pressGroupHandler = useCallback(() => { |
||||
nav.navigate(RouteKey.Group) |
||||
}, [nav.navigate]) |
||||
|
||||
if (searchString !== null && !isLoading && isEmpty) return null |
||||
|
||||
const renderContent = () => { |
||||
if (isLoading) { |
||||
return ( |
||||
<View style={styles.loading}> |
||||
<ActivityIndicator /> |
||||
</View> |
||||
) |
||||
} |
||||
return ( |
||||
<Swiper |
||||
showsButtons={false} |
||||
horizontal |
||||
bounces={false} |
||||
loop={false} |
||||
automaticallyAdjustContentInsets |
||||
showsPagination={false}> |
||||
{groups.map(renderItem)} |
||||
</Swiper> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
<ContentBlock |
||||
title={'Групи'} |
||||
btnTitle={'Дивитись усі'} |
||||
onPressBtn={pressGroupHandler} |
||||
paddingHorizontal={$size(0)} |
||||
style={styles.content}> |
||||
{renderContent()} |
||||
</ContentBlock> |
||||
) |
||||
} |
||||
|
||||
const styles = StyleSheet.create({ |
||||
loading: { |
||||
justifyContent: 'center', |
||||
alignItems: 'center', |
||||
}, |
||||
cardContainer: { |
||||
width: screenWidth - 40, |
||||
marginLeft: 20, |
||||
}, |
||||
content: { |
||||
marginTop: $size(15, 10), |
||||
}, |
||||
}) |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export * from './groups-slider-list.smart-component' |
@ -1,50 +0,0 @@
@@ -1,50 +0,0 @@
|
||||
import { |
||||
$size, |
||||
InfoRowCard, |
||||
IShortUser, |
||||
ITaxonomyWithTasksCount, |
||||
RouteKey, |
||||
useNav, |
||||
} from '@/shared' |
||||
import React, { FC } from 'react' |
||||
import { Dimensions, StyleSheet, View } from 'react-native' |
||||
import { HomeCarusel } from '../components' |
||||
|
||||
interface Props { |
||||
items: IShortUser[] |
||||
} |
||||
const { width: screenWidth } = Dimensions.get('screen') |
||||
|
||||
export const HomeExecutorsBlock: FC<Props> = ({ items }) => { |
||||
const { navigate } = useNav() |
||||
|
||||
const renderItem = item => { |
||||
return ( |
||||
<View key={item.id} style={styles.container}> |
||||
<InfoRowCard |
||||
title={item.fullName} |
||||
info={item.position} |
||||
imageUri={item.avatarUrl} |
||||
containerStyle={styles.cardContainer} |
||||
onPressCard={() => |
||||
navigate(RouteKey.ExecutorTasks, { userData: item }) |
||||
} |
||||
onPressInfo={() => |
||||
navigate(RouteKey.ContactDetail, { contactId: item.id }) |
||||
} |
||||
/> |
||||
</View> |
||||
) |
||||
} |
||||
return <HomeCarusel>{items.map(renderItem)}</HomeCarusel> |
||||
} |
||||
|
||||
const styles = StyleSheet.create({ |
||||
container: { |
||||
width: screenWidth - 40, |
||||
marginLeft: 20, |
||||
}, |
||||
cardContainer: { |
||||
width: '100%', |
||||
}, |
||||
}) |
@ -1,53 +0,0 @@
@@ -1,53 +0,0 @@
|
||||
import { |
||||
InfoRowCard, |
||||
ITaxonomyWithTasksCount, |
||||
RouteKey, |
||||
useNav, |
||||
} from '@/shared' |
||||
import { $size, getTitleByCount } from '@/shared/helpers' |
||||
import React, { FC } from 'react' |
||||
import { Dimensions, StyleSheet, View } from 'react-native' |
||||
import { HomeCarusel } from '../components' |
||||
|
||||
interface Props { |
||||
items: ITaxonomyWithTasksCount[] |
||||
} |
||||
const { width: screenWidth } = Dimensions.get('screen') |
||||
|
||||
export const HomeGroupsBlock: FC<Props> = ({ items }) => { |
||||
const { navigate } = useNav() |
||||
|
||||
const renderItem = item => { |
||||
if (!item || !item.tasksCount) return null |
||||
return ( |
||||
<View key={item.id}> |
||||
<InfoRowCard |
||||
title={item.name} |
||||
info={getTitleByCount(item.tasksCount, [ |
||||
'задача', |
||||
'задачі', |
||||
'задач', |
||||
])} |
||||
imageUri={item.iconUrl} |
||||
containerStyle={styles.cardContainer} |
||||
onPressCard={() => |
||||
navigate(RouteKey.GroupTasks, { |
||||
groupData: item, |
||||
}) |
||||
} |
||||
onPressInfo={() => |
||||
console.log('i press group info', item.name) |
||||
} |
||||
/> |
||||
</View> |
||||
) |
||||
} |
||||
return <HomeCarusel>{items.map(renderItem)}</HomeCarusel> |
||||
} |
||||
|
||||
const styles = StyleSheet.create({ |
||||
cardContainer: { |
||||
width: screenWidth - 40, |
||||
marginLeft: 20, |
||||
}, |
||||
}) |
@ -1,2 +0,0 @@
@@ -1,2 +0,0 @@
|
||||
export * from './home-groups-block.componen'; |
||||
export * from './home-executors.component' |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
import { getTitleByCount } from '@/shared/helpers' |
||||
import _ from 'lodash' |
||||
|
||||
export const getHomeSearchResult = ( |
||||
counts: Record<string, number>, |
||||
searchString: string, |
||||
) => { |
||||
if ( |
||||
!searchString || |
||||
!_.isNumber(counts.tasks) || |
||||
!_.isNumber(counts.groups) || |
||||
!_.isNumber(counts.executors) |
||||
) |
||||
return false |
||||
|
||||
const count = |
||||
Number(counts.executors) + Number(counts.groups) + Number(counts.tasks) |
||||
|
||||
return `Знайдено ${getTitleByCount(count, [ |
||||
'результат', |
||||
'результата', |
||||
'результатів', |
||||
])}` |
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage' |
||||
import _ from 'lodash' |
||||
|
||||
export enum SkeletonDataKey { |
||||
HomeGroups = 'homeGroups', |
||||
HomeExecutors = 'homeExecutors', |
||||
HomeTasks = 'homeTasks', |
||||
} |
||||
|
||||
export class SkeletonData { |
||||
private dataKeys = [ |
||||
SkeletonDataKey.HomeGroups, |
||||
SkeletonDataKey.HomeExecutors, |
||||
SkeletonDataKey.HomeTasks, |
||||
] |
||||
|
||||
private loadedData: Partial<Record<SkeletonDataKey, any>> = {} |
||||
|
||||
constructor() { |
||||
this.init() |
||||
} |
||||
|
||||
private async init() { |
||||
for await (const key of this.dataKeys) { |
||||
await this.load(key) |
||||
} |
||||
console.log('LOAD FINISH') |
||||
} |
||||
|
||||
private async load(key: SkeletonDataKey) { |
||||
const data = await AsyncStorage.getItem(key) |
||||
if (!data) return |
||||
this.loadedData[key] = JSON.parse(data) |
||||
console.log(key, this.loadedData) |
||||
} |
||||
|
||||
public set(key: SkeletonDataKey, data: any) { |
||||
console.log('set key', key) |
||||
AsyncStorage.setItem(key, JSON.stringify(data)) |
||||
} |
||||
|
||||
public get(key: SkeletonDataKey, defaultValue?: any) { |
||||
return _.defaultTo(this.loadedData[key], defaultValue) |
||||
} |
||||
} |
||||
|
||||
export const skeletonDataService = new SkeletonData() |
Loading…
Reference in new issue