Skip to content

Commit 769b396

Browse files
authored
Leader Editing Groups (#343)
* LeaderEditing * LeaderEditingUpdate * LeaderEditing Finished * LeaderEditing Fixed
1 parent a9ede82 commit 769b396

File tree

6 files changed

+206
-92
lines changed

6 files changed

+206
-92
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
},
55
"eslint.format.enable": true,
66
"editor.formatOnSave": true
7-
}
7+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@
4848
"@types/react-helmet": "^6.1.6",
4949
"@types/webfontloader": "^1.6.38",
5050
"copyfiles": "^2.4.1",
51+
"eslint": "^9.18.0",
5152
"eslint-config-next": "15.0.0",
5253
"eslint-plugin-unused-imports": "4.1.4",
5354
"typescript": "^5.3.3"
5455
}
55-
}
56+
}

src/app/[sdSlug]/groups/details/[groupId]/components/AuthenticatedView.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { GroupFiles } from "@/components/groups/GroupFiles";
1010
import { ConfigurationInterface } from "@/helpers/ConfigHelper";
1111
import { GroupHero } from "./GroupHero";
1212
import { GroupTabs } from "./GroupTabs";
13+
import { LeaderEdit } from "./LeaderEdit";
1314

1415
interface Props {
1516
config: ConfigurationInterface;
@@ -20,6 +21,7 @@ export function AuthenticatedView(props: Props) {
2021
const [groupMembers, setGroupMembers] = useState([]);
2122
const [isLoading, setIsLoading] = useState(false);
2223
const [tab, setTab] = useState("details");
24+
const [group, setGroup] = useState(props.group);
2325

2426
const context = useContext(UserContext);
2527

@@ -31,7 +33,11 @@ export function AuthenticatedView(props: Props) {
3133
.finally(() => setIsLoading(false));
3234
};
3335

34-
useEffect(() => { loadData(); }, [props.group]);
36+
useEffect(() => {
37+
loadData();
38+
setGroup(props.group);
39+
}, [props.group]);
40+
3541

3642

3743
const getRows = () => {
@@ -69,24 +75,34 @@ export function AuthenticatedView(props: Props) {
6975

7076
let isLeader = false;
7177
UserHelper.currentUserChurch.groups?.forEach((g) => {
72-
if (g.id === props.group?.id && g.leader) isLeader = true;
78+
if (g.id === group?.id && g.leader) isLeader = true;
7379
});
7480

81+
const handleChange = (g: GroupInterface) => {
82+
setGroup(g);
83+
}
84+
7585
const getTabContent = () => {
7686
console.log("Tab is", tab)
7787
let result = <></>
7888
switch (tab) {
7989
case "details":
80-
result = <><h2>Details</h2><div style={{ paddingTop: "1rem", paddingBottom: "3rem" }}><MarkdownPreviewLight value={props.group.about} /></div></>
90+
result = <>
91+
{isLeader && <LeaderEdit group={group} config={props.config} onChange={handleChange} updatedFunction={handleChange} />}
92+
<h2>Details</h2>
93+
<div style={{ paddingTop: "1rem", paddingBottom: "3rem" }}>
94+
<MarkdownPreviewLight value={group.about} />
95+
</div>
96+
</>
8197
break;
8298
case "calendar":
83-
result = <><h2>Calendar</h2><DisplayBox headerText="Group Calendar"><GroupCalendar groupId={props.group.id} churchId={props.config.church.id} canEdit={isLeader} /></DisplayBox></>
99+
result = <><h2>Calendar</h2><DisplayBox headerText="Group Calendar"><GroupCalendar groupId={group.id} churchId={props.config.church.id} canEdit={isLeader} /></DisplayBox></>
84100
break;
85101
case "conversations":
86-
result = <><h2>Conversations</h2><Conversations context={context} contentType="group" contentId={props.group.id} groupId={props.group.id} /></>
102+
result = <><h2>Conversations</h2><Conversations context={context} contentType="group" contentId={group.id} groupId={group.id} /></>
87103
break;
88104
case "files":
89-
result = <><h2>Files</h2><GroupFiles context={context} groupId={props.group.id} /></>
105+
result = <><h2>Files</h2><GroupFiles context={context} groupId={group.id} /></>
90106
break;
91107
case "members":
92108
result = <><h2>Members</h2><DisplayBox id="groupMembersBox" headerText="Group Members" headerIcon="group">{getTable()}</DisplayBox></>
@@ -97,17 +113,17 @@ export function AuthenticatedView(props: Props) {
97113

98114
return (
99115
<>
100-
<GroupHero group={props.group} />
116+
<GroupHero group={group} />
101117
<Container>
102118
<div id="mainContent">
103119
<Grid container spacing={2}>
104120
<Grid item xs={12} md={2}>
105121
<div className="sideNav">
106-
<GroupTabs config={props.config} onTabChange={(val:string) => { setTab(val) }} />
122+
<GroupTabs config={props.config} onTabChange={(val: string) => { setTab(val) }} />
107123
</div>
108124
</Grid>
109125
<Grid item xs={12} md={10}>
110-
{props.group
126+
{group
111127
? (<>{getTabContent()}</>)
112128
: (<p>No group data found</p>)}
113129

src/app/[sdSlug]/groups/details/[groupId]/components/GroupContact.tsx

Lines changed: 76 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -6,94 +6,94 @@ import { Alert, Button, FormControl, InputLabel, MenuItem, Select, SelectChangeE
66
import { useEffect, useState } from "react";
77

88
interface Props {
9-
leaders: GroupMemberInterface[];
10-
group: GroupInterface;
11-
config: ConfigurationInterface;
9+
leaders: GroupMemberInterface[];
10+
group: GroupInterface;
11+
config: ConfigurationInterface;
1212
}
1313

1414
export function GroupContact(props: Props) {
1515

16-
const [formData, setFormData] = useState<any>({ churchId: props.config.church.id });
16+
const [formData, setFormData] = useState<any>({ churchId: props.config.church.id });
1717

18-
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | SelectChangeEvent<string>) => {
19-
const val = e.target.value;
20-
const fd = { ...formData }
21-
switch (e.target.name) {
22-
case "personId": fd.personId = val; break;
23-
case "firstName": fd.firstName = val; break;
24-
case "lastName": fd.lastName = val; break;
25-
case "email": fd.email = val; break;
26-
case "phone": fd.phone = val; break;
27-
case "message": fd.message = val; break;
28-
}
29-
setFormData(fd);
30-
console.log(fd);
18+
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | SelectChangeEvent<string>) => {
19+
const val = e.target.value;
20+
const fd = { ...formData }
21+
switch (e.target.name) {
22+
case "personId": fd.personId = val; break;
23+
case "firstName": fd.firstName = val; break;
24+
case "lastName": fd.lastName = val; break;
25+
case "email": fd.email = val; break;
26+
case "phone": fd.phone = val; break;
27+
case "message": fd.message = val; break;
3128
}
29+
setFormData(fd);
30+
console.log(fd);
31+
}
3232

33-
const [isSubmitted, setIsSubmitted] = useState(false);
33+
const [isSubmitted, setIsSubmitted] = useState(false);
3434

35-
const handleSubmit = async (e: React.MouseEvent) => {
36-
if (e !== null) e.preventDefault();
37-
const email = {
38-
churchId: formData.churchId,
39-
personId: formData.personId,
40-
appName: "B1",
41-
subject: "Contact Request For " + props.group.name,
42-
body: "First Name: " + formData.firstName + "<br />" +
43-
"Last Name: " + formData.lastName + "<br />" +
44-
"Email Address: " + formData.email + "<br />" +
45-
"Phone Number: " + formData.phone + "<br />" +
46-
"Message: " + formData.message
47-
}
48-
49-
try {
50-
await ApiHelper.post("/people/public/email", email, "MembershipApi");
51-
setIsSubmitted(true);
52-
} catch (error) {
53-
console.error("Error sending email:", error);
54-
}
35+
const handleSubmit = async (e: React.MouseEvent) => {
36+
if (e !== null) e.preventDefault();
37+
const email = {
38+
churchId: formData.churchId,
39+
personId: formData.personId,
40+
appName: "B1",
41+
subject: "Contact Request For " + props.group.name,
42+
body: "First Name: " + formData.firstName + "<br />" +
43+
"Last Name: " + formData.lastName + "<br />" +
44+
"Email Address: " + formData.email + "<br />" +
45+
"Phone Number: " + formData.phone + "<br />" +
46+
"Message: " + formData.message
5547
}
5648

57-
const getSelectLeaders = () => {
58-
const result: JSX.Element[] = [];
59-
props.leaders.forEach((l) => {
60-
result.push(<MenuItem value={l.personId} key={l.personId}>{l.person.name.display}</MenuItem>);
61-
});
62-
return result;
49+
try {
50+
await ApiHelper.post("/people/public/email", email, "MembershipApi");
51+
setIsSubmitted(true);
52+
} catch (error) {
53+
console.error("Error sending email:", error);
6354
}
55+
}
56+
57+
const getSelectLeaders = () => {
58+
const result: JSX.Element[] = [];
59+
props.leaders.forEach((l) => {
60+
result.push(<MenuItem value={l.personId} key={l.personId}>{l.person.name.display}</MenuItem>);
61+
});
62+
return result;
63+
}
6464

65-
useEffect(() => {
66-
if (props.leaders?.length > 0) {
67-
const fd = { ...formData }
68-
fd.personId = props.leaders[0].personId;
69-
setFormData(fd);
70-
}
71-
}, [props.leaders])
65+
useEffect(() => {
66+
if (props.leaders?.length > 0) {
67+
const fd = { ...formData }
68+
fd.personId = props.leaders[0].personId;
69+
setFormData(fd);
70+
}
71+
}, [props.leaders])
7272

73-
if (props.leaders?.length < 1) return <></>
74-
else return <>
75-
<div>
76-
<h2>Contact Group Leader:</h2>
77-
<form>
78-
{(props.leaders?.length > 1) && <FormControl fullWidth>
79-
<InputLabel>Contact</InputLabel>
80-
<Select fullWidth label="Contact" name="personId" value={formData.personId || ""} onChange={handleChange}>
81-
{getSelectLeaders()}
82-
</Select>
83-
</FormControl>}
84-
<TextField fullWidth label="First Name" name="firstName" value={formData.firstName} onChange={handleChange} />
85-
<TextField fullWidth label="Last Name" name="lastName" value={formData.lastName} onChange={handleChange} />
86-
<TextField fullWidth label="Email" name="email" value={formData.email} onChange={handleChange} />
87-
<TextField fullWidth label="Phone Number" name="phone" value={formData.phone} onChange={handleChange} />
88-
<TextField fullWidth label="Message" name="message" value={formData.message} onChange={handleChange} multiline />
89-
<Button onClick={handleSubmit} id="conbtn" style={{ height: "50px", fontWeight: "bold", width: "40%", marginBottom: "10px" }}>Submit</Button>
90-
</form>
73+
if (props.leaders?.length < 1) return <></>
74+
else return <>
75+
<div>
76+
<h2>Contact Group Leader:</h2>
77+
<form>
78+
{(props.leaders?.length > 1) && <FormControl fullWidth>
79+
<InputLabel>Contact</InputLabel>
80+
<Select fullWidth label="Contact" name="personId" value={formData.personId || ""} onChange={handleChange}>
81+
{getSelectLeaders()}
82+
</Select>
83+
</FormControl>}
84+
<TextField fullWidth label="First Name" name="firstName" value={formData.firstName} onChange={handleChange} />
85+
<TextField fullWidth label="Last Name" name="lastName" value={formData.lastName} onChange={handleChange} />
86+
<TextField fullWidth label="Email" name="email" value={formData.email} onChange={handleChange} />
87+
<TextField fullWidth label="Phone Number" name="phone" value={formData.phone} onChange={handleChange} />
88+
<TextField fullWidth label="Message" name="message" value={formData.message} onChange={handleChange} multiline />
89+
<Button onClick={handleSubmit} id="conbtn" style={{ height: "50px", fontWeight: "bold", width: "40%", marginBottom: "10px" }}>Submit</Button>
90+
</form>
9191

92-
{isSubmitted && (
93-
<Alert sx={{ align: "center", fontSize: "18px", fontStyle: "italic", marginBottom: "10px" }} severity="success">
94-
Your message has been sent!&nbsp;
95-
</Alert>
96-
)}
97-
</div>
98-
</>
92+
{isSubmitted && (
93+
<Alert sx={{ align: "center", fontSize: "18px", fontStyle: "italic", marginBottom: "10px" }} severity="success">
94+
Your message has been sent!&nbsp;
95+
</Alert>
96+
)}
97+
</div>
98+
</>
9999
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"use client";
2+
3+
import { ConfigurationInterface } from "@/helpers/ConfigHelper";
4+
import { ApiHelper, GalleryModal, GroupInterface, InputBox } from "@churchapps/apphelper";
5+
import { Button, InputLabel, SelectChangeEvent, TextField } from "@mui/material";
6+
import EditIcon from '@mui/icons-material/Edit';
7+
import { useEffect, useState } from "react";
8+
import React from "react";
9+
import { EnvironmentHelper } from "@/helpers";
10+
11+
interface Props {
12+
config: ConfigurationInterface
13+
group: GroupInterface;
14+
onChange: (group: GroupInterface) => void;
15+
updatedFunction: (group: GroupInterface) => void;
16+
}
17+
18+
export function LeaderEdit(props: Props) {
19+
20+
const [formEdits, setFormEdits] = useState<GroupInterface>(props.group);
21+
let [hidden, setHidden] = useState("none");
22+
const [selectPhotoField, setSelectPhotoField] = React.useState<string>(null);
23+
24+
useEffect(() => {
25+
setFormEdits(props.group);
26+
console.log("group is", props.group);
27+
EnvironmentHelper.initLocale();
28+
}, [props.group])
29+
30+
const hideForm = () => {
31+
if (hidden === "none") {
32+
setHidden("block");
33+
console.log("showing")
34+
} else {
35+
setHidden("none");
36+
console.log("hiding")
37+
}
38+
return;
39+
}
40+
41+
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | SelectChangeEvent<string>) => {
42+
const val = e.target.value;
43+
const fe = { ...formEdits }
44+
switch (e.target.name) {
45+
case "name": fe.name = val; break;
46+
case "meetingTime": fe.meetingTime = val; break;
47+
case "meetingLocation": fe.meetingLocation = val; break;
48+
case "about": fe.about = val; break;
49+
}
50+
setFormEdits(fe);
51+
console.log(fe);
52+
}
53+
54+
const handleSubmit = async () => {
55+
ApiHelper.post("/groups", [formEdits], "MembershipApi").then((groups: GroupInterface[]) => {
56+
hideForm();
57+
props.onChange(groups[0]);
58+
});
59+
}
60+
61+
62+
const handlePhotoSelected = (image: string) => {
63+
let fe = { ...formEdits };
64+
fe.photoUrl = image;
65+
setFormEdits(fe);
66+
setSelectPhotoField(null);
67+
};
68+
69+
return <>
70+
<div style={{ textAlign: "right", float: "right", marginTop: 20 }}>
71+
<Button onClick={hideForm}><EditIcon /></Button>
72+
</div>
73+
74+
<div>
75+
<form style={{ display: hidden, marginTop: 20 }}>
76+
<InputBox saveFunction={handleSubmit} cancelFunction={hideForm}>
77+
<TextField fullWidth label="Group Name" name="name" value={formEdits.name || ""} onChange={handleChange} />
78+
<TextField fullWidth label="Meeting Time" name="meetingTime" value={formEdits.meetingTime || ""} onChange={handleChange} />
79+
<TextField fullWidth label="Meeting Location" name="meetingLocation" value={formEdits.meetingLocation || ""} onChange={handleChange} />
80+
<TextField fullWidth label="Description" name="about" value={formEdits.about || ""} onChange={handleChange} multiline />
81+
{formEdits.photoUrl && (<>
82+
<img src={formEdits.photoUrl} style={{ maxHeight: 100, maxWidth: "100%", width: "auto" }} alt="group" />
83+
<br />
84+
</>)}
85+
{!formEdits.photoUrl && <InputLabel>Group Photo</InputLabel>}
86+
<Button variant="contained" onClick={() => setSelectPhotoField("photoUrl")}>Select Photo</Button>
87+
</InputBox>
88+
89+
{selectPhotoField && (
90+
<GalleryModal
91+
onClose={() => setSelectPhotoField(null)}
92+
onSelect={handlePhotoSelected}
93+
aspectRatio={4}
94+
/>
95+
)}
96+
</form>
97+
</div>
98+
</>
99+
}

0 commit comments

Comments
 (0)