@planningcenter/chat-react-native 3.2.0 → 3.4.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts +9 -0
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts.map +1 -0
- package/build/hooks/services/use_find_or_create_services_conversation.js +72 -0
- package/build/hooks/services/use_find_or_create_services_conversation.js.map +1 -0
- package/build/hooks/services/use_team_members_for_new_conversation.d.ts +176 -0
- package/build/hooks/services/use_team_members_for_new_conversation.d.ts.map +1 -0
- package/build/hooks/services/use_team_members_for_new_conversation.js +29 -0
- package/build/hooks/services/use_team_members_for_new_conversation.js.map +1 -0
- package/build/hooks/services/use_team_plans.d.ts +8 -0
- package/build/hooks/services/use_team_plans.d.ts.map +1 -0
- package/build/hooks/services/use_team_plans.js +37 -0
- package/build/hooks/services/use_team_plans.js.map +1 -0
- package/build/hooks/services/use_teams_i_lead.d.ts +8 -0
- package/build/hooks/services/use_teams_i_lead.d.ts.map +1 -0
- package/build/hooks/services/use_teams_i_lead.js +34 -0
- package/build/hooks/services/use_teams_i_lead.js.map +1 -0
- package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +1 -1
- package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
- package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +4 -28
- package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
- package/build/screens/conversation_new/components/filter_by_plan_options.d.ts +8 -0
- package/build/screens/conversation_new/components/filter_by_plan_options.d.ts.map +1 -0
- package/build/screens/conversation_new/components/filter_by_plan_options.js +37 -0
- package/build/screens/conversation_new/components/filter_by_plan_options.js.map +1 -0
- package/build/screens/conversation_new/components/services_form.d.ts +8 -0
- package/build/screens/conversation_new/components/services_form.d.ts.map +1 -0
- package/build/screens/conversation_new/components/services_form.js +137 -0
- package/build/screens/conversation_new/components/services_form.js.map +1 -0
- package/build/screens/conversation_new/conversation_new_screen.js +2 -2
- package/build/screens/conversation_new/conversation_new_screen.js.map +1 -1
- package/build/types/resources/services/team_resource.d.ts +6 -3
- package/build/types/resources/services/team_resource.d.ts.map +1 -1
- package/build/types/resources/services/team_resource.js.map +1 -1
- package/package.json +2 -2
- package/src/hooks/services/use_find_or_create_services_conversation.ts +97 -0
- package/src/hooks/services/use_team_members_for_new_conversation.ts +40 -0
- package/src/hooks/services/use_team_plans.ts +42 -0
- package/src/hooks/services/use_teams_i_lead.ts +44 -0
- package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +5 -48
- package/src/screens/conversation_new/components/filter_by_plan_options.tsx +64 -0
- package/src/screens/conversation_new/components/services_form.tsx +229 -0
- package/src/screens/conversation_new/conversation_new_screen.tsx +2 -2
- package/src/types/resources/services/team_resource.ts +7 -3
- package/build/screens/conversation_new/components/team_form.d.ts +0 -8
- package/build/screens/conversation_new/components/team_form.d.ts.map +0 -1
- package/build/screens/conversation_new/components/team_form.js +0 -11
- package/build/screens/conversation_new/components/team_form.js.map +0 -1
- package/src/screens/conversation_new/components/team_form.tsx +0 -18
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { StackActions, useNavigation } from '@react-navigation/native';
|
|
2
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
3
|
+
import { StyleSheet, View } from 'react-native';
|
|
4
|
+
import { Badge, Banner, ChildNotice, Heading, Switch, Text } from '../../../components';
|
|
5
|
+
import { ActionButton } from '../../../components/display/action_button';
|
|
6
|
+
import { Divider, FormList } from './form_list';
|
|
7
|
+
import { pluralize } from '../../../utils';
|
|
8
|
+
import { uniq } from 'lodash';
|
|
9
|
+
import { useTeamsILead } from '../../../hooks/services/use_teams_i_lead';
|
|
10
|
+
import { FilterByPlanOptions } from './filter_by_plan_options';
|
|
11
|
+
import { useTeamMembersForNewConversation } from '../../../hooks/services/use_team_members_for_new_conversation';
|
|
12
|
+
import { useFindOrCreateServicesConversation } from '../../../hooks/services/use_find_or_create_services_conversation';
|
|
13
|
+
export const ServicesForm = ({ initialTeamIds, initialPlanId }) => {
|
|
14
|
+
const styles = useStyles();
|
|
15
|
+
const [selectedPlanId, setSelectedPlanId] = useState(initialPlanId);
|
|
16
|
+
const initialState = uniq(initialTeamIds) || []; // Uniq here because services can send duplicates in the teams_i_lead response.
|
|
17
|
+
const [selectedTeamIds, setSelectedTeamIds] = useState(initialState);
|
|
18
|
+
const removeSelection = useCallback((teamId) => {
|
|
19
|
+
setSelectedTeamIds(selectedTeamIds.filter(id => id !== teamId));
|
|
20
|
+
}, [selectedTeamIds]);
|
|
21
|
+
const [filerByPlan, setFilterByPlan] = useState(false);
|
|
22
|
+
const { members, isError: isMemberError } = useTeamMembersForNewConversation({
|
|
23
|
+
teamIds: selectedTeamIds,
|
|
24
|
+
planId: selectedPlanId,
|
|
25
|
+
});
|
|
26
|
+
const navigation = useNavigation();
|
|
27
|
+
const { mutate: createConversation } = useFindOrCreateServicesConversation({
|
|
28
|
+
teamIds: selectedTeamIds,
|
|
29
|
+
planId: filerByPlan ? selectedPlanId : undefined,
|
|
30
|
+
onSuccess: (conversation) => {
|
|
31
|
+
// exit from the create stack
|
|
32
|
+
navigation.getParent()?.goBack();
|
|
33
|
+
// navigate to the conversation screen
|
|
34
|
+
navigation.dispatch(StackActions.push('Conversation', {
|
|
35
|
+
conversation_id: conversation.id,
|
|
36
|
+
}));
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
return (<View style={styles.formContainer}>
|
|
40
|
+
<FormList memberData={members} FormContent={<FormContent selectedTeamIds={selectedTeamIds} removeSelection={removeSelection} selectedPlanId={selectedPlanId} setSelectedPlanId={setSelectedPlanId} filterByPlan={filerByPlan} setFilterByPlan={setFilterByPlan} members={members} isMemberError={isMemberError}/>}/>
|
|
41
|
+
<ActionButton disabled={!selectedTeamIds.length} title="Start Conversation" onPress={createConversation} infoText="Conversation will be automatically updated if any members are added or removed from included teams."/>
|
|
42
|
+
</View>);
|
|
43
|
+
};
|
|
44
|
+
function FormContent({ selectedTeamIds, removeSelection, selectedPlanId, setSelectedPlanId, filterByPlan, setFilterByPlan, members, isMemberError, }) {
|
|
45
|
+
const { teamsILead } = useTeamsILead();
|
|
46
|
+
const selectedTeamsILead = useMemo(() => {
|
|
47
|
+
return teamsILead.filter(team => selectedTeamIds.includes(team.value.teamId));
|
|
48
|
+
}, [selectedTeamIds, teamsILead]);
|
|
49
|
+
const styles = useStyles();
|
|
50
|
+
const teamCountHeader = pluralize(selectedTeamIds.length, 'team');
|
|
51
|
+
const memberCount = members.length;
|
|
52
|
+
const childMembers = members.filter(member => member.child);
|
|
53
|
+
const hasChildren = childMembers.length > 0;
|
|
54
|
+
const multipleServiceTypes = uniq(selectedTeamsILead.map(team => team.value.serviceTypeId)).length > 1;
|
|
55
|
+
const firstTeamId = selectedTeamIds[0];
|
|
56
|
+
filterByPlan = filterByPlan && !!firstTeamId && !multipleServiceTypes;
|
|
57
|
+
return (<View style={styles.formContent}>
|
|
58
|
+
<View style={styles.toSection}>
|
|
59
|
+
<View style={styles.toSectionRow}>
|
|
60
|
+
<Heading variant="h3">To:</Heading>
|
|
61
|
+
<Text>{teamCountHeader}</Text>
|
|
62
|
+
</View>
|
|
63
|
+
<View style={styles.toSectionRow}>
|
|
64
|
+
{selectedTeamsILead.map(team => (<View key={team.value.teamId} style={styles.badgeRow}>
|
|
65
|
+
<Badge iconNameRight="general.x" label={team.name} onPress={() => removeSelection(team.value.teamId)}/>
|
|
66
|
+
</View>))}
|
|
67
|
+
</View>
|
|
68
|
+
</View>
|
|
69
|
+
<Divider />
|
|
70
|
+
<View style={styles.filterByPlanSection}>
|
|
71
|
+
<View style={styles.filterByPlanSectionLead}>
|
|
72
|
+
<Heading variant="h3">Filter by plan</Heading>
|
|
73
|
+
<Switch value={filterByPlan} onValueChange={setFilterByPlan} disabled={multipleServiceTypes}/>
|
|
74
|
+
</View>
|
|
75
|
+
{multipleServiceTypes ? (<Banner appearance="neutral" description="Plan filtering is not possible using teams from multiple service types. Try choosing teams above with only one service type."/>) : filterByPlan ? (<FilterByPlanOptions teamIds={selectedTeamIds} planId={selectedPlanId} onChange={setSelectedPlanId}/>) : null}
|
|
76
|
+
</View>
|
|
77
|
+
<Divider />
|
|
78
|
+
<View style={styles.memberSection}>
|
|
79
|
+
<Heading variant="h3">{pluralize(memberCount, 'member')} selected</Heading>
|
|
80
|
+
{hasChildren && <ChildNotice childMembers={childMembers} style={styles.banner}/>}
|
|
81
|
+
{isMemberError && (<Banner appearance="error" description="There was an issue loading team members, please refresh and try again." style={styles.banner}/>)}
|
|
82
|
+
</View>
|
|
83
|
+
</View>);
|
|
84
|
+
}
|
|
85
|
+
const useStyles = () => {
|
|
86
|
+
const sectionPadding = 16;
|
|
87
|
+
return StyleSheet.create({
|
|
88
|
+
formContainer: {
|
|
89
|
+
flex: 1,
|
|
90
|
+
},
|
|
91
|
+
formContent: {
|
|
92
|
+
paddingVertical: sectionPadding,
|
|
93
|
+
flex: 1,
|
|
94
|
+
},
|
|
95
|
+
toSection: {
|
|
96
|
+
padding: sectionPadding,
|
|
97
|
+
gap: 8,
|
|
98
|
+
},
|
|
99
|
+
toSectionRow: {
|
|
100
|
+
flexDirection: 'row',
|
|
101
|
+
gap: 8,
|
|
102
|
+
alignItems: 'baseline',
|
|
103
|
+
flexWrap: 'wrap',
|
|
104
|
+
},
|
|
105
|
+
badgeRow: {
|
|
106
|
+
flexDirection: 'row',
|
|
107
|
+
},
|
|
108
|
+
groupName: {
|
|
109
|
+
fontSize: 18,
|
|
110
|
+
},
|
|
111
|
+
filterByPlanSection: {
|
|
112
|
+
padding: sectionPadding,
|
|
113
|
+
gap: 8,
|
|
114
|
+
},
|
|
115
|
+
filterByPlanSectionLead: {
|
|
116
|
+
flexDirection: 'row',
|
|
117
|
+
gap: 8,
|
|
118
|
+
justifyContent: 'space-between',
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
},
|
|
121
|
+
filterByPlanSectionRow: {
|
|
122
|
+
flexDirection: 'row',
|
|
123
|
+
gap: 8,
|
|
124
|
+
},
|
|
125
|
+
titleInput: {
|
|
126
|
+
fontSize: 18,
|
|
127
|
+
},
|
|
128
|
+
memberSection: {
|
|
129
|
+
padding: sectionPadding,
|
|
130
|
+
paddingBottom: 0,
|
|
131
|
+
},
|
|
132
|
+
banner: {
|
|
133
|
+
marginTop: 16,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=services_form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services_form.js","sourceRoot":"","sources":["../../../../src/screens/conversation_new/components/services_form.tsx"],"names":[],"mappings":"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","sourcesContent":["import { StackActions, useNavigation } from '@react-navigation/native'\nimport React, { useCallback, useMemo, useState } from 'react'\nimport { StyleSheet, View } from 'react-native'\nimport { Badge, Banner, ChildNotice, Heading, Switch, Text } from '../../../components'\nimport { ActionButton } from '../../../components/display/action_button'\nimport { Divider, FormList } from './form_list'\nimport { pluralize } from '../../../utils'\nimport { uniq } from 'lodash'\nimport { useTeamsILead } from '../../../hooks/services/use_teams_i_lead'\nimport { FilterByPlanOptions } from './filter_by_plan_options'\nimport { useTeamMembersForNewConversation } from '../../../hooks/services/use_team_members_for_new_conversation'\nimport { useFindOrCreateServicesConversation } from '../../../hooks/services/use_find_or_create_services_conversation'\nimport { ConversationResource, MemberResource } from '../../../types'\n\ntype ServicesFormProps = {\n initialTeamIds?: number[]\n initialPlanId?: number\n}\n\nexport const ServicesForm = ({ initialTeamIds, initialPlanId }: ServicesFormProps) => {\n const styles = useStyles()\n const [selectedPlanId, setSelectedPlanId] = useState<number | undefined>(initialPlanId)\n const initialState = uniq(initialTeamIds) || [] // Uniq here because services can send duplicates in the teams_i_lead response.\n const [selectedTeamIds, setSelectedTeamIds] = useState<number[]>(initialState)\n\n const removeSelection = useCallback(\n (teamId: number) => {\n setSelectedTeamIds(selectedTeamIds.filter(id => id !== teamId))\n },\n [selectedTeamIds]\n )\n\n const [filerByPlan, setFilterByPlan] = useState(false)\n\n const { members, isError: isMemberError } = useTeamMembersForNewConversation({\n teamIds: selectedTeamIds,\n planId: selectedPlanId,\n })\n\n const navigation = useNavigation()\n const { mutate: createConversation } = useFindOrCreateServicesConversation({\n teamIds: selectedTeamIds,\n planId: filerByPlan ? selectedPlanId : undefined,\n onSuccess: (conversation: ConversationResource) => {\n // exit from the create stack\n navigation.getParent()?.goBack()\n // navigate to the conversation screen\n navigation.dispatch(\n StackActions.push('Conversation', {\n conversation_id: conversation.id,\n })\n )\n },\n })\n\n return (\n <View style={styles.formContainer}>\n <FormList\n memberData={members}\n FormContent={\n <FormContent\n selectedTeamIds={selectedTeamIds}\n removeSelection={removeSelection}\n selectedPlanId={selectedPlanId}\n setSelectedPlanId={setSelectedPlanId}\n filterByPlan={filerByPlan}\n setFilterByPlan={setFilterByPlan}\n members={members}\n isMemberError={isMemberError}\n />\n }\n />\n <ActionButton\n disabled={!selectedTeamIds.length}\n title=\"Start Conversation\"\n onPress={createConversation}\n infoText=\"Conversation will be automatically updated if any members are added or removed from included teams.\"\n />\n </View>\n )\n}\n\ninterface FormContentProps {\n selectedTeamIds: number[]\n removeSelection: (teamId: number) => void\n selectedPlanId?: number\n setSelectedPlanId: (planId: number | undefined) => void\n filterByPlan: boolean\n setFilterByPlan: (value: boolean) => void\n members: MemberResource[]\n isMemberError: boolean\n}\n\nfunction FormContent({\n selectedTeamIds,\n removeSelection,\n selectedPlanId,\n setSelectedPlanId,\n filterByPlan,\n setFilterByPlan,\n members,\n isMemberError,\n}: FormContentProps) {\n const { teamsILead } = useTeamsILead()\n const selectedTeamsILead = useMemo(() => {\n return teamsILead.filter(team => selectedTeamIds.includes(team.value.teamId))\n }, [selectedTeamIds, teamsILead])\n\n const styles = useStyles()\n const teamCountHeader = pluralize(selectedTeamIds.length, 'team')\n const memberCount = members.length\n const childMembers = members.filter(member => member.child)\n const hasChildren = childMembers.length > 0\n const multipleServiceTypes =\n uniq(selectedTeamsILead.map(team => team.value.serviceTypeId)).length > 1\n const firstTeamId = selectedTeamIds[0]\n filterByPlan = filterByPlan && !!firstTeamId && !multipleServiceTypes\n\n return (\n <View style={styles.formContent}>\n <View style={styles.toSection}>\n <View style={styles.toSectionRow}>\n <Heading variant=\"h3\">To:</Heading>\n <Text>{teamCountHeader}</Text>\n </View>\n <View style={styles.toSectionRow}>\n {selectedTeamsILead.map(team => (\n <View key={team.value.teamId} style={styles.badgeRow}>\n <Badge\n iconNameRight=\"general.x\"\n label={team.name}\n onPress={() => removeSelection(team.value.teamId)}\n />\n </View>\n ))}\n </View>\n </View>\n <Divider />\n <View style={styles.filterByPlanSection}>\n <View style={styles.filterByPlanSectionLead}>\n <Heading variant=\"h3\">Filter by plan</Heading>\n <Switch\n value={filterByPlan}\n onValueChange={setFilterByPlan}\n disabled={multipleServiceTypes}\n />\n </View>\n {multipleServiceTypes ? (\n <Banner\n appearance=\"neutral\"\n description=\"Plan filtering is not possible using teams from multiple service types. Try choosing teams above with only one service type.\"\n />\n ) : filterByPlan ? (\n <FilterByPlanOptions\n teamIds={selectedTeamIds}\n planId={selectedPlanId}\n onChange={setSelectedPlanId}\n />\n ) : null}\n </View>\n <Divider />\n <View style={styles.memberSection}>\n <Heading variant=\"h3\">{pluralize(memberCount, 'member')} selected</Heading>\n {hasChildren && <ChildNotice childMembers={childMembers} style={styles.banner} />}\n {isMemberError && (\n <Banner\n appearance=\"error\"\n description=\"There was an issue loading team members, please refresh and try again.\"\n style={styles.banner}\n />\n )}\n </View>\n </View>\n )\n}\n\nconst useStyles = () => {\n const sectionPadding = 16\n\n return StyleSheet.create({\n formContainer: {\n flex: 1,\n },\n formContent: {\n paddingVertical: sectionPadding,\n flex: 1,\n },\n toSection: {\n padding: sectionPadding,\n gap: 8,\n },\n toSectionRow: {\n flexDirection: 'row',\n gap: 8,\n alignItems: 'baseline',\n flexWrap: 'wrap',\n },\n badgeRow: {\n flexDirection: 'row',\n },\n groupName: {\n fontSize: 18,\n },\n filterByPlanSection: {\n padding: sectionPadding,\n gap: 8,\n },\n filterByPlanSectionLead: {\n flexDirection: 'row',\n gap: 8,\n justifyContent: 'space-between',\n alignItems: 'center',\n },\n filterByPlanSectionRow: {\n flexDirection: 'row',\n gap: 8,\n },\n titleInput: {\n fontSize: 18,\n },\n memberSection: {\n padding: sectionPadding,\n paddingBottom: 0,\n },\n banner: {\n marginTop: 16,\n },\n })\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { GroupsForm } from './components/groups_form';
|
|
3
|
-
import {
|
|
3
|
+
import { ServicesForm } from './components/services_form';
|
|
4
4
|
import { SourceAppErrorCard } from './components/source_app_error_card';
|
|
5
5
|
export const ConversationNewScreen = ({ route }) => {
|
|
6
6
|
const { group_id, team_ids, source_app_name, plan_id, chat_group_graph_id } = route.params;
|
|
@@ -8,7 +8,7 @@ export const ConversationNewScreen = ({ route }) => {
|
|
|
8
8
|
case 'Groups':
|
|
9
9
|
return group_id ? (<GroupsForm groupId={group_id} chat_group_graph_id={chat_group_graph_id}/>) : (<SourceAppErrorCard />);
|
|
10
10
|
case 'Services':
|
|
11
|
-
return <
|
|
11
|
+
return <ServicesForm initialTeamIds={team_ids} initialPlanId={plan_id}/>;
|
|
12
12
|
default:
|
|
13
13
|
return <SourceAppErrorCard />;
|
|
14
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation_new_screen.js","sourceRoot":"","sources":["../../../src/screens/conversation_new/conversation_new_screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"conversation_new_screen.js","sourceRoot":"","sources":["../../../src/screens/conversation_new/conversation_new_screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAavE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,EAAE,KAAK,EAA8B,EAAE,EAAE;IAC7E,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,KAAK,CAAC,MAAM,CAAA;IAE1F,QAAQ,eAAe,EAAE,CAAC;QACxB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC,CAAC,CAAC,CAChB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,mBAAmB,CAAC,CAAC,mBAAmB,CAAC,EAAG,CAC5E,CAAC,CAAC,CAAC,CACF,CAAC,kBAAkB,CAAC,AAAD,EAAG,CACvB,CAAA;QACH,KAAK,UAAU;YACb,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAAG,CAAA;QAC3E;YACE,OAAO,CAAC,kBAAkB,CAAC,AAAD,EAAG,CAAA;IACjC,CAAC;AACH,CAAC,CAAA","sourcesContent":["import { StaticScreenProps } from '@react-navigation/native'\nimport React from 'react'\nimport { GroupsForm } from './components/groups_form'\nimport { ServicesForm } from './components/services_form'\nimport { SourceAppErrorCard } from './components/source_app_error_card'\nimport { AppName } from '../../types/resources/app_name'\nimport { GraphId } from '../../types/resources/group_resource'\n\ntype ConversationNewScreenProps = StaticScreenProps<{\n group_id?: number\n team_ids?: number[]\n plan_id?: number\n source_app_name: AppName\n chat_group_graph_id?: GraphId\n group_source_app_name?: AppName\n}>\n\nexport const ConversationNewScreen = ({ route }: ConversationNewScreenProps) => {\n const { group_id, team_ids, source_app_name, plan_id, chat_group_graph_id } = route.params\n\n switch (source_app_name) {\n case 'Groups':\n return group_id ? (\n <GroupsForm groupId={group_id} chat_group_graph_id={chat_group_graph_id} />\n ) : (\n <SourceAppErrorCard />\n )\n case 'Services':\n return <ServicesForm initialTeamIds={team_ids} initialPlanId={plan_id} />\n default:\n return <SourceAppErrorCard />\n }\n}\n"]}
|
|
@@ -25,14 +25,14 @@ export type TeamOptionResponseItemGroupName = 'teams_i_lead' | 'other_teams';
|
|
|
25
25
|
export interface PlansResource extends ServicesChatResource {
|
|
26
26
|
plans: PlansResponseItem[];
|
|
27
27
|
}
|
|
28
|
-
interface PlansResponseItem {
|
|
28
|
+
export interface PlansResponseItem {
|
|
29
29
|
value: number;
|
|
30
30
|
name: string;
|
|
31
31
|
}
|
|
32
32
|
export interface TeamPeopleResource extends ServicesChatResource {
|
|
33
|
-
people:
|
|
33
|
+
people: TeamPersonResponseItem[];
|
|
34
34
|
}
|
|
35
|
-
interface
|
|
35
|
+
export interface TeamPersonResponseItem {
|
|
36
36
|
id: number;
|
|
37
37
|
name: string;
|
|
38
38
|
avatar: string;
|
|
@@ -44,5 +44,8 @@ interface PersonResponseItem {
|
|
|
44
44
|
export interface ServicesChatPayloadResource extends ServicesChatResource {
|
|
45
45
|
payload: string;
|
|
46
46
|
}
|
|
47
|
+
export interface ServicesChatGroupIdentifiersResource extends ServicesChatResource {
|
|
48
|
+
groupIdentifiers: string[];
|
|
49
|
+
}
|
|
47
50
|
export {};
|
|
48
51
|
//# sourceMappingURL=team_resource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"team_resource.d.ts","sourceRoot":"","sources":["../../../../src/types/resources/services/team_resource.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,UAAU,oBAAqB,SAAQ,cAAc;IACnD,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,YAAa,SAAQ,oBAAoB;IACxD,UAAU,EAAE,gBAAgB,EAAE,CAAA;CAC/B;AACD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAA;QACd,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,KAAK,EAAE,sBAAsB,EAAE,CAAA;CAChC;AAED,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,KAAK,EAAE,+BAA+B,CAAA;CACvC;AAED,MAAM,MAAM,+BAA+B,GAAG,cAAc,GAAG,aAAa,CAAA;AAE5E,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,KAAK,EAAE,iBAAiB,EAAE,CAAA;CAC3B;AAED,
|
|
1
|
+
{"version":3,"file":"team_resource.d.ts","sourceRoot":"","sources":["../../../../src/types/resources/services/team_resource.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,UAAU,oBAAqB,SAAQ,cAAc;IACnD,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,YAAa,SAAQ,oBAAoB;IACxD,UAAU,EAAE,gBAAgB,EAAE,CAAA;CAC/B;AACD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAA;QACd,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,KAAK,EAAE,sBAAsB,EAAE,CAAA;CAChC;AAED,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,KAAK,EAAE,+BAA+B,CAAA;CACvC;AAED,MAAM,MAAM,+BAA+B,GAAG,cAAc,GAAG,aAAa,CAAA;AAE5E,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,KAAK,EAAE,iBAAiB,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,MAAM,EAAE,sBAAsB,EAAE,CAAA;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,2BAA4B,SAAQ,oBAAoB;IACvE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,oCAAqC,SAAQ,oBAAoB;IAChF,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAC3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"team_resource.js","sourceRoot":"","sources":["../../../../src/types/resources/services/team_resource.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,kFAAkF;AAClF,6DAA6D;AAC7D,wGAAwG;AACxG,2FAA2F","sourcesContent":["// All of our calls out to services go out to /chat, which isn't a typical pco-api vertex.\n// They all have a shared type of 'Chat' which has the following array attributes:\n// group_identifiers, people, plans, teams and, teams_i_lead.\n// All of these have a type array, are only rendered on request, and are exclusively used for this form.\n// There is also a payload which is a string, and is used to create a conversation in chat.\n\nimport { ResourceObject } from '../../api_primitives'\n\ninterface ServicesChatResource extends ResourceObject {\n type: 'Chat'\n}\n\nexport interface TeamResource extends ServicesChatResource {\n teamsILead: TeamResponseItem[]\n}\nexport interface TeamResponseItem {\n name: string\n value: {\n teamId: number\n serviceTypeId: number\n }\n serviceTypeName: string\n teamName: string\n order: [number, string, string] // [serviceTypeId, serviceTypeName, teamName]\n}\n\nexport interface TeamOptionResource extends ServicesChatResource {\n teams: TeamOptionResponseItem[]\n}\n\nexport interface TeamOptionResponseItem extends TeamResponseItem {\n group: TeamOptionResponseItemGroupName\n}\n\nexport type TeamOptionResponseItemGroupName = 'teams_i_lead' | 'other_teams'\n\nexport interface PlansResource extends ServicesChatResource {\n plans: PlansResponseItem[]\n}\n\
|
|
1
|
+
{"version":3,"file":"team_resource.js","sourceRoot":"","sources":["../../../../src/types/resources/services/team_resource.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,kFAAkF;AAClF,6DAA6D;AAC7D,wGAAwG;AACxG,2FAA2F","sourcesContent":["// All of our calls out to services go out to /chat, which isn't a typical pco-api vertex.\n// They all have a shared type of 'Chat' which has the following array attributes:\n// group_identifiers, people, plans, teams and, teams_i_lead.\n// All of these have a type array, are only rendered on request, and are exclusively used for this form.\n// There is also a payload which is a string, and is used to create a conversation in chat.\n\nimport { ResourceObject } from '../../api_primitives'\n\ninterface ServicesChatResource extends ResourceObject {\n type: 'Chat'\n}\n\nexport interface TeamResource extends ServicesChatResource {\n teamsILead: TeamResponseItem[]\n}\nexport interface TeamResponseItem {\n name: string\n value: {\n teamId: number\n serviceTypeId: number\n }\n serviceTypeName: string\n teamName: string\n order: [number, string, string] // [serviceTypeId, serviceTypeName, teamName]\n}\n\nexport interface TeamOptionResource extends ServicesChatResource {\n teams: TeamOptionResponseItem[]\n}\n\nexport interface TeamOptionResponseItem extends TeamResponseItem {\n group: TeamOptionResponseItemGroupName\n}\n\nexport type TeamOptionResponseItemGroupName = 'teams_i_lead' | 'other_teams'\n\nexport interface PlansResource extends ServicesChatResource {\n plans: PlansResponseItem[]\n}\n\nexport interface PlansResponseItem {\n value: number\n name: string\n}\n\nexport interface TeamPeopleResource extends ServicesChatResource {\n people: TeamPersonResponseItem[]\n}\n\nexport interface TeamPersonResponseItem {\n id: number\n name: string\n avatar: string\n badges: { title: string }[]\n child: boolean\n}\n\nexport interface ServicesChatPayloadResource extends ServicesChatResource {\n payload: string\n}\n\nexport interface ServicesChatGroupIdentifiersResource extends ServicesChatResource {\n groupIdentifiers: string[]\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0-rc.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"prettier": "^3.4.2",
|
|
56
56
|
"typescript": "<5.6.0"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "464031a14a1949e994a9720294029b82e2513705"
|
|
59
59
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query'
|
|
2
|
+
import {
|
|
3
|
+
ApiCollection,
|
|
4
|
+
ApiResource,
|
|
5
|
+
ConversationResource,
|
|
6
|
+
ServicesChatGroupIdentifiersResource,
|
|
7
|
+
ServicesChatPayloadResource,
|
|
8
|
+
} from '../../types'
|
|
9
|
+
import { ApiClient, useApiClient } from '../use_api_client'
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
teamIds: number[]
|
|
13
|
+
planId?: number
|
|
14
|
+
onSuccess?: (conversation: ConversationResource) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useFindOrCreateServicesConversation({ teamIds, planId, onSuccess }: Props) {
|
|
18
|
+
const teamAndPlanParams: TeamAndPlanParams = {
|
|
19
|
+
team_id: teamIds.join(','),
|
|
20
|
+
...(planId ? { plan_id: planId } : {}),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const apiClient = useApiClient()
|
|
24
|
+
return useMutation({
|
|
25
|
+
throwOnError: true,
|
|
26
|
+
onSuccess: result => {
|
|
27
|
+
onSuccess && onSuccess(result)
|
|
28
|
+
},
|
|
29
|
+
mutationFn: async () => {
|
|
30
|
+
const foundConversations = await getGroupIdsFromServices(apiClient, teamAndPlanParams)
|
|
31
|
+
.then(res => res.data.groupIdentifiers)
|
|
32
|
+
.then(groupIdentifiers => findConversationWithExactTeams(apiClient, groupIdentifiers))
|
|
33
|
+
.catch(() => null)
|
|
34
|
+
const foundConversation = foundConversations?.data[0]
|
|
35
|
+
|
|
36
|
+
if (foundConversation?.id) {
|
|
37
|
+
return foundConversation
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return fetchServicesPayload(apiClient, teamAndPlanParams)
|
|
41
|
+
.then(res => res.data.payload)
|
|
42
|
+
.then(payload => createConversation(apiClient, payload))
|
|
43
|
+
.then(res => res.data)
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface TeamAndPlanParams {
|
|
49
|
+
plan_id?: number | undefined
|
|
50
|
+
team_id: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function fetchServicesPayload(apiClient: ApiClient, teamAndPlanParams: TeamAndPlanParams) {
|
|
54
|
+
return apiClient.services.get({
|
|
55
|
+
url: `/chat`,
|
|
56
|
+
data: {
|
|
57
|
+
...teamAndPlanParams,
|
|
58
|
+
fields: {
|
|
59
|
+
Chat: ['payload'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}) as Promise<ApiResource<ServicesChatPayloadResource>>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getGroupIdsFromServices(apiClient: ApiClient, teamAndPlanParams: TeamAndPlanParams) {
|
|
66
|
+
return apiClient.services.get({
|
|
67
|
+
url: '/chat',
|
|
68
|
+
data: {
|
|
69
|
+
...teamAndPlanParams,
|
|
70
|
+
fields: {
|
|
71
|
+
Chat: ['group_identifiers'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
}) as Promise<ApiResource<ServicesChatGroupIdentifiersResource>>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function findConversationWithExactTeams(apiClient: ApiClient, groupIdentifiers: string[]) {
|
|
78
|
+
return apiClient.chat.get({
|
|
79
|
+
url: '/me/conversations',
|
|
80
|
+
data: {
|
|
81
|
+
fields: {
|
|
82
|
+
Conversation: ['stream_channel'],
|
|
83
|
+
},
|
|
84
|
+
filter: 'with_exact_groups',
|
|
85
|
+
gids: groupIdentifiers.join(','),
|
|
86
|
+
},
|
|
87
|
+
}) as Promise<ApiCollection<ConversationResource>>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createConversation(apiClient: ApiClient, payload: string) {
|
|
91
|
+
return apiClient.chat.post({
|
|
92
|
+
url: '/me/conversations',
|
|
93
|
+
data: {
|
|
94
|
+
data: { type: 'Conversation', attributes: { payload } },
|
|
95
|
+
},
|
|
96
|
+
}) as Promise<ApiResource<ConversationResource>>
|
|
97
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isNil, omitBy } from 'lodash'
|
|
2
|
+
import { useApiGet } from '../use_api'
|
|
3
|
+
import { MemberResource, TeamPeopleResource, TeamPersonResponseItem } from '../../types'
|
|
4
|
+
import { useMemo } from 'react'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
teamIds: number[]
|
|
8
|
+
planId?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useTeamMembersForNewConversation({ teamIds, planId }: Props) {
|
|
12
|
+
const params = omitBy({ team_id: teamIds.join(','), plan_id: planId }, isNil)
|
|
13
|
+
|
|
14
|
+
const { data, ...rest } = useApiGet<TeamPeopleResource>({
|
|
15
|
+
url: '/chat',
|
|
16
|
+
data: {
|
|
17
|
+
...params,
|
|
18
|
+
fields: {
|
|
19
|
+
Chat: ['people'],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
app: 'services',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const people = data?.people || stableEmptyPersonArray
|
|
26
|
+
|
|
27
|
+
const members: MemberResource[] = useMemo(() => {
|
|
28
|
+
return people.map(person => ({
|
|
29
|
+
...person,
|
|
30
|
+
type: 'Member',
|
|
31
|
+
}))
|
|
32
|
+
}, [people])
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
members,
|
|
36
|
+
...rest,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const stableEmptyPersonArray: TeamPersonResponseItem[] = []
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ApiResource, PlansResource } from '../../types'
|
|
2
|
+
import { ApiClient, useApiClient } from '../use_api_client'
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import { useQueries } from '@tanstack/react-query'
|
|
5
|
+
import { uniqBy } from 'lodash'
|
|
6
|
+
|
|
7
|
+
export function useTeamPlans({ teamIds }: { teamIds: number[] }) {
|
|
8
|
+
const apiClient = useApiClient()
|
|
9
|
+
const planQueries = useQueries({
|
|
10
|
+
queries: teamIds.map(teamId => ({
|
|
11
|
+
queryKey: ['plansForTeam', teamId],
|
|
12
|
+
queryFn: () => fetchTeamPlans(apiClient, teamId),
|
|
13
|
+
initialData: NULL_RESPONSE,
|
|
14
|
+
retry: false,
|
|
15
|
+
})),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const uniqPlanOptions = useMemo(() => {
|
|
19
|
+
const planOptions = planQueries.flatMap(({ data }) => data?.data.plans).filter(p => !!p)
|
|
20
|
+
return uniqBy(planOptions, 'value')
|
|
21
|
+
}, [planQueries])
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
planOptions: uniqPlanOptions,
|
|
25
|
+
isFetching: planQueries.some(({ isFetching }) => isFetching),
|
|
26
|
+
isError: planQueries.some(({ isError }) => isError),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const NULL_RESPONSE = { data: { id: 0, plans: [], type: 'Chat' }, links: {}, meta: {} }
|
|
31
|
+
|
|
32
|
+
function fetchTeamPlans(apiClient: ApiClient, teamId: number) {
|
|
33
|
+
return apiClient.services.get<ApiResource<PlansResource>>({
|
|
34
|
+
url: '/chat',
|
|
35
|
+
data: {
|
|
36
|
+
team_id: teamId,
|
|
37
|
+
fields: {
|
|
38
|
+
Chat: ['plans'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
import { useApiGet } from '../../hooks/use_api'
|
|
3
|
+
import { ResourceObject, TeamResponseItem } from '../../types'
|
|
4
|
+
|
|
5
|
+
export function useTeamsILead() {
|
|
6
|
+
const response = useApiGet<ServicesChatResource>({
|
|
7
|
+
url: `/chat`,
|
|
8
|
+
data: {
|
|
9
|
+
fields: {
|
|
10
|
+
Chat: ['teams_i_lead'],
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
app: 'services',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const { data, isFetching, isError, error } = response
|
|
17
|
+
|
|
18
|
+
const teamsILead = data?.teamsILead || stableEmptyTeamResponseArray
|
|
19
|
+
|
|
20
|
+
const hasNoServicesAccess = useCallback(() => {
|
|
21
|
+
if (isError && error) {
|
|
22
|
+
const errorArray = error
|
|
23
|
+
const allErrors = errorArray.errors.map(e => e.detail)
|
|
24
|
+
if (allErrors.some(e => e.includes(NOT_IN_APPLICATION_CODE))) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return false
|
|
29
|
+
}, [isError, error])
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
teamsILead,
|
|
33
|
+
isFetching,
|
|
34
|
+
isError,
|
|
35
|
+
noServicesAccess: hasNoServicesAccess(),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ServicesChatResource extends ResourceObject {
|
|
40
|
+
teamsILead: TeamResponseItem[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const stableEmptyTeamResponseArray: TeamResponseItem[] = []
|
|
44
|
+
const NOT_IN_APPLICATION_CODE = 'TRASH_PANDA'
|
|
@@ -1,44 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ResourceObject } from '../../../types'
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { TeamResponseItem } from '../../../types'
|
|
4
3
|
import { ServiceTypeWithTeams } from '../types'
|
|
5
4
|
import { uniqBy } from 'lodash'
|
|
5
|
+
import { useTeamsILead } from '../../../hooks/services/use_teams_i_lead'
|
|
6
6
|
|
|
7
7
|
export function useServiceTypesWithTeams() {
|
|
8
|
-
const
|
|
9
|
-
url: `/chat`,
|
|
10
|
-
data: {
|
|
11
|
-
fields: {
|
|
12
|
-
Chat: ['teams_i_lead'],
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
app: 'services',
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
const { data, isFetching, isError, error } = response
|
|
19
|
-
|
|
20
|
-
const teamsILead = data?.teamsILead || stableEmptyTeamResponseArray
|
|
8
|
+
const { teamsILead, ...data } = useTeamsILead()
|
|
21
9
|
|
|
22
10
|
const decoratedResponse = useMemo(() => {
|
|
23
11
|
return decorateTeamResponseItems(teamsILead)
|
|
24
12
|
}, [teamsILead])
|
|
25
13
|
|
|
26
|
-
const hasNoServicesAccess = useCallback(() => {
|
|
27
|
-
if (isError && error) {
|
|
28
|
-
const errorArray = error
|
|
29
|
-
const allErrors = errorArray.errors.map(e => e.detail)
|
|
30
|
-
if (allErrors.some(e => e.includes(NOT_IN_APPLICATION_CODE))) {
|
|
31
|
-
return true
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return false
|
|
35
|
-
}, [isError, error])
|
|
36
|
-
|
|
37
14
|
return {
|
|
38
15
|
serviceTypes: decoratedResponse,
|
|
39
|
-
|
|
40
|
-
isError,
|
|
41
|
-
noServicesAccess: hasNoServicesAccess(),
|
|
16
|
+
...data,
|
|
42
17
|
}
|
|
43
18
|
}
|
|
44
19
|
|
|
@@ -70,21 +45,3 @@ function decorateTeamResponseItems(teamResponseItems: TeamResponseItem[]) {
|
|
|
70
45
|
return acc
|
|
71
46
|
}, [])
|
|
72
47
|
}
|
|
73
|
-
|
|
74
|
-
interface TeamResponseItem {
|
|
75
|
-
value: {
|
|
76
|
-
teamId: number
|
|
77
|
-
serviceTypeId: number
|
|
78
|
-
}
|
|
79
|
-
name: string
|
|
80
|
-
teamName: string
|
|
81
|
-
serviceTypeName: string
|
|
82
|
-
order: string[]
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
interface ServicesChatResource extends ResourceObject {
|
|
86
|
-
teamsILead: TeamResponseItem[]
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const stableEmptyTeamResponseArray: TeamResponseItem[] = []
|
|
90
|
-
const NOT_IN_APPLICATION_CODE = 'TRASH_PANDA'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Pressable, StyleSheet, View } from 'react-native'
|
|
2
|
+
import { Banner, Spinner, Text } from '../../../components'
|
|
3
|
+
import { useTeamPlans } from '../../../hooks/services/use_team_plans'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
teamIds: number[]
|
|
7
|
+
planId?: number
|
|
8
|
+
onChange: (planId: number | undefined) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function FilterByPlanOptions({ teamIds, planId, onChange }: Props) {
|
|
12
|
+
const styles = useStyles()
|
|
13
|
+
const { planOptions, isFetching, isError } = useTeamPlans({ teamIds })
|
|
14
|
+
|
|
15
|
+
if (isFetching) {
|
|
16
|
+
return (
|
|
17
|
+
<View style={styles.spinnerContainer}>
|
|
18
|
+
<Spinner size={20} />
|
|
19
|
+
</View>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isError) {
|
|
24
|
+
return (
|
|
25
|
+
<Banner
|
|
26
|
+
appearance="error"
|
|
27
|
+
description="An error occurred while fetching plans. Please try again."
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!planOptions || planOptions.length === 0) {
|
|
33
|
+
return (
|
|
34
|
+
<Banner
|
|
35
|
+
appearance="neutral"
|
|
36
|
+
description="No plans available with the selected service type."
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<View style={styles.filterByPlanSectionRow}>
|
|
43
|
+
{planOptions.map(plan => (
|
|
44
|
+
<Pressable onPress={() => onChange(plan.value)} key={plan.value}>
|
|
45
|
+
<Text style={[planId === plan.value ? styles.selectedFilter : null]}>{plan.name}</Text>
|
|
46
|
+
</Pressable>
|
|
47
|
+
))}
|
|
48
|
+
</View>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const useStyles = () => {
|
|
53
|
+
return StyleSheet.create({
|
|
54
|
+
spinnerContainer: {
|
|
55
|
+
marginVertical: 8,
|
|
56
|
+
},
|
|
57
|
+
filterByPlanSectionRow: {
|
|
58
|
+
gap: 8,
|
|
59
|
+
},
|
|
60
|
+
selectedFilter: {
|
|
61
|
+
fontWeight: '800',
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
}
|