Vitalik
2 years ago
37 changed files with 795 additions and 461 deletions
@ -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 @@ |
|||||||
|
export * from './executors-slider-list.smart-component' |
@ -1,42 +1,14 @@ |
|||||||
export const groupsData = [ |
import { ITaxonomyWithTasksCount } from '@/shared' |
||||||
{ |
|
||||||
title: 'Title', |
export const groupsData: ITaxonomyWithTasksCount[] = [ |
||||||
info: '25 Pflfx', |
{ |
||||||
}, |
id: null, |
||||||
{ |
parentId: null, |
||||||
title: 'Title', |
name: null, |
||||||
info: 'Info', |
type: null, |
||||||
}, |
isDeleted: null, |
||||||
{ |
isDefault: null, |
||||||
title: 'Title', |
iconUrl: null, |
||||||
info: 'Info', |
tasksCount: null, |
||||||
}, |
|
||||||
{ |
|
||||||
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', |
|
||||||
}, |
}, |
||||||
] |
] |
||||||
|
@ -0,0 +1 @@ |
|||||||
|
export * from './groups-data.mock' |
@ -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 @@ |
|||||||
|
export * from './groups-slider-list.smart-component' |
@ -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 @@ |
|||||||
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 @@ |
|||||||
export * from './home-groups-block.componen'; |
|
||||||
export * from './home-executors.component' |
|
@ -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 @@ |
|||||||
|
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