Implement patch endpoints and "editables"

master
Joshua Sigona 4 years ago
parent 943bcfc1d1
commit d3ae365ed9
  1. 31
      family-tracker/package-lock.json
  2. 1
      family-tracker/package.json
  3. 1
      family-tracker/public/index.html
  4. 9
      family-tracker/src/App.css
  5. 167
      family-tracker/src/App.js
  6. 6
      src/main/java/sig/family/CorsFilter.java
  7. 50
      src/main/java/sig/family/Endpoints.java
  8. 9
      src/main/java/sig/family/FamilyContainer.java
  9. 76
      src/main/java/sig/family/FamilyMemberContainer.java

@ -2435,6 +2435,37 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz",
"integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA=="
}, },
"axios": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
"requires": {
"follow-redirects": "1.5.10"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"axobject-query": { "axobject-query": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",

@ -6,6 +6,7 @@
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0", "@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1", "@testing-library/user-event": "^7.2.1",
"axios": "^0.19.2",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",

@ -10,7 +10,6 @@
content="Family Tracker" content="Family Tracker"
/> />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a

@ -1,3 +1,12 @@
.mouseover:hover{
background-color:#eee;
}
.editable:hover{
border: 1px solid blue;
cursor:text;
}
.App { .App {
text-align: center; text-align: center;
} }

@ -10,7 +10,10 @@ import {
useParams useParams
} from "react-router-dom"; } from "react-router-dom";
function Map() { const axios = require('axios');
function Map(p) {
p.setActive("/map")
return ( return (
<> <>
<h1>Map View</h1> <h1>Map View</h1>
@ -18,26 +21,160 @@ function Map() {
); );
} }
function Message() { function Message(p) {
p.setActive("/messages")
return ( return (
<> <>
<h1>Message View</h1> <h1>Message View</h1>
</> </>
); );
} }
function Member() { function Member(p) {
const [familyId,setFamilyId] = useState(0)
const [firstName,setFirstName] = useState("")
const [lastName,setLastName] = useState("")
const [relationship,setRelationship] = useState("")
const [mobileDevice,setMobileDevice] = useState("")
const [disabled,setDisabled] = useState(false)
useEffect(()=>{
p.setActive("/members")
})
return ( return (
<> <>
<h1>Member View</h1> <h1>Member View</h1>
<div className="row">
<div className="col-3 text-right">
<label htmlFor="family">Family: </label>
</div>
<div className="col-9 text-left">
<select id="family" name="family" value={familyId} onChange={(e)=>{setFamilyId(e.target.value)}}>
{p.family.map((fam)=><option key={fam.id-1} value={fam.id-1}>{fam.name}</option>)}
</select>
</div>
</div>
<div className="row">
<div className="col-12">
{(p.family[familyId])?p.family[familyId].members.map((member)=><Mem key={member.id} member={member} update={p.update} setUpdate={p.setUpdate}/>):<></>}
</div>
</div>
<div className="row">
<div className="col-sm-12 col-lg-8">
<label htmlFor="name">Add Member:</label>
<br/><input type="text" disabled={disabled} onChange={(e)=>{setFirstName(e.target.value);}} value={firstName} id="firstName" style={{width:"30%"}}/><input type="text" disabled={disabled} onChange={(e)=>{setLastName(e.target.value);}} value={lastName} id="lastName" style={{width:"30%"}}/><input type="text" disabled={disabled} onChange={(e)=>{setMobileDevice(e.target.value);}} value={mobileDevice} id="mobileDevice" style={{width:"30%"}}/><input type="text" disabled={disabled} onChange={(e)=>{setRelationship(e.target.value);}} value={relationship} id="relationship" style={{width:"30%"}}/><button disabled={disabled} style={{width:"10%"}} onClick={()=>{
setDisabled(true)
axios.post("http://localhost:8080/member/create",{firstName:firstName,lastName:lastName,mobileId:mobileDevice})
.then((data)=>{
return axios.post("http://localhost:8080/relationship/"+(Number(familyId)+1)+"/"+(data.data.id)+"/"+(relationship))})
.then((data)=>{
p.setUpdate(!p.update)
setFirstName("")
setLastName("")
setMobileDevice("")
setRelationship("")
setDisabled(false)
})}}>Add</button>
</div>
</div>
</> </>
); );
} }
function Family() { function Mem(p) {
return (
<>
<div className="row mouseover">
<div className="col-4 text-right">
<Editable update={p.update} setUpdate={p.setUpdate} endpoint={"http://localhost:8080/member/"+p.member.id} field="firstName" value={p.member.firstName}/>
</div>
<div className="col-4">
<Editable update={p.update} setUpdate={p.setUpdate} endpoint={"http://localhost:8080/member/"+p.member.id} field="lastName" value={p.member.lastName}/>
</div>
<div className="col-2">
<Editable update={p.update} setUpdate={p.setUpdate} endpoint={"http://localhost:8080/member/"+p.member.id} field="mobileId" value={p.member.mobileDeviceId}/>
</div>
</div>
</>
);
}
function Editable(p) {
const [value,setValue] = useState((typeof(p.value)==="string"&&p.value.length>0||typeof(p.value)==="number")?p.value:"-");
const [editing,setEditing] = useState(false);
const [disabled,setDisabled] = useState(false);
const [style,setStyle] = useState({});
return (
<>
{(editing)?<><input type="text" style={style} value={value} disabled={disabled} onChange={(e)=>{setValue(e.target.value);setStyle({})}} onKeyPress={(e)=>{
if (e.key==='Enter') {
if (value.length===0) {
return new Error("Cannot accept 0 length string.")
}
setDisabled(true)
var obj = {}
obj[p.field] = value
axios.patch(p.endpoint,obj)
.then((data)=>{
p.setUpdate(!p.update)
setDisabled(false)
setEditing(false)
})
.catch((err)=>{
setDisabled(false)
setStyle({border:"1px solid red"})
})
}
}
}/></>:<>
<span className="editable" onClick={()=>{setEditing(true)}}>{value}</span>
</>}
</>
);
}
function Fam(p) {
return (<>
<div className="row">
<div className="col-1 border-left border-top border-bottom">
{p.family.id}
</div>
<div className="col-1 border-top border-bottom">
<Editable update={p.update} setUpdate={p.setUpdate} endpoint={"http://localhost:8080/family/"+p.family.id} field="name" value={p.family.name}/>
</div>
<div className="col-sm-12 col-lg-6 mr-3 border-left border-top border-bottom border-right">
{p.family.members.map((member)=><Mem key={member.id} member={member} update={p.update} setUpdate={p.setUpdate}/>)}
</div>
</div>
</>);
}
function Family(p) {
const [family,setFamily] = useState("");
useEffect(()=>{
p.setActive("/")
})
return ( return (
<> <>
<h1>Family View</h1> <h1>Family View</h1>
{p.family.map((family)=><Fam key={family.id} family={family} update={p.update} setUpdate={p.setUpdate}/>)}
<br/>
<div className="row">
<div className="col-sm-12 col-lg-8">
<label htmlFor="name">Add Family Name:</label>
<br/><input type="text" onChange={(e)=>{setFamily(e.target.value);}} value={family} id="name" style={{width:"90%"}}/><button style={{width:"10%"}} onClick={()=>{
axios.post("http://localhost:8080/family",{name:family})
.then((data)=>{
p.setUpdate(!p.update)
setFamily("")})}}>Add</button>
</div>
</div>
</> </>
); );
} }
@ -57,29 +194,37 @@ function L(p) {
function App() { function App() {
const [pageView,setPageView] = useState(null); const [pageView,setPageView] = useState(null);
const [active,setActive] = useState("/"); const [active,setActive] = useState("/");
const [family,setFamily] = useState([]);
const [update,setUpdate] = useState(false);
useEffect(()=>{
axios.get("http://localhost:8080/family")
.then((data)=>{setFamily(data.data)})
},[update])
return ( return (
<Router> <Router>
<div className="container-fluid"> <div className="container-fluid">
<div className="row"> <div className="row">
<div className="col-sm-3 text-center pt-5"> <div className="col-sm-3 text-center pt-5 order-2 order-sm-2 order-md-1">
<Link to="/" onClick={()=>{setActive("/")}}><L highlight="/" active={active} name="Family"/></Link> <Link to="/" onClick={()=>{setActive("/")}}><L highlight="/" active={active} name="Family"/></Link>
<Link to="/members" onClick={()=>{setActive("/members")}}><L highlight="/members" active={active} name="Members"/></Link> <Link to="/members" onClick={()=>{setActive("/members")}}><L highlight="/members" active={active} name="Members"/></Link>
<Link to="/messages" onClick={()=>{setActive("/messages")}}><L highlight="/messages" active={active} name="Notifications"/></Link> <Link to="/messages" onClick={()=>{setActive("/messages")}}><L highlight="/messages" active={active} name="Notifications"/></Link>
<Link to="/map" onClick={()=>{setActive("/map")}}><L highlight="/map" active={active} name="Map"/></Link> <Link to="/map" onClick={()=>{setActive("/map")}}><L highlight="/map" active={active} name="Map"/></Link>
</div> </div>
<div className="col-sm-9"> <div className="col-sm-9 order-1 order-sm-1 order-md-2">
<Switch> <Switch>
<Route path="/map"> <Route path="/map">
<Map/> <Map setActive={setActive}/>
</Route> </Route>
<Route path="/messages"> <Route path="/messages">
<Message/> <Message setActive={setActive}/>
</Route> </Route>
<Route path="/members"> <Route path="/members">
<Member/> <Member setActive={setActive} family={family} update={update} setUpdate={setUpdate}/>
</Route> </Route>
<Route path="/"> <Route path="/">
<Family/> <Family setActive={setActive} family={family} update={update} setUpdate={setUpdate}/>
</Route> </Route>
</Switch> </Switch>
</div> </div>

@ -21,9 +21,9 @@ public class CorsFilter extends OncePerRequestFilter {
throws throws
ServletException, IOException { ServletException, IOException {
response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, HEAD"); response.addHeader("Access-Control-Allow-Methods", "*");
response.addHeader("Access-Control-Allow-Headers", "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"); response.addHeader("Access-Control-Allow-Headers", "*");
response.addHeader("Access-Control-Expose-Headers", "Access-Control-Allow-Origin, Access-Control-Allow-Credentials"); response.addHeader("Access-Control-Expose-Headers", "*");
response.addHeader("Access-Control-Allow-Credentials", "true"); response.addHeader("Access-Control-Allow-Credentials", "true");
response.addIntHeader("Access-Control-Max-Age", 10); response.addIntHeader("Access-Control-Max-Age", 10);
filterChain.doFilter(request, response); filterChain.doFilter(request, response);

@ -158,6 +158,56 @@ public class Endpoints {
} }
} }
@PatchMapping("/member/{id}")
/**
* @RequestBody can have:
* firstName - (Optional)Modified first name.
* lastName - (Optional)Modified last name.
* mobileId - (Optional)Modified mobile Id.
* @return
*/
public FamilyMember _11(
@PathVariable Long id,
@RequestBody HashMap<String,String> body) {
if (members.existsById(id)) {
FamilyMember m = members.findById(id).get();
if (body.containsKey("firstName")) {
m.setFirstName(body.get("firstName"));
}
if (body.containsKey("lastName")) {
m.setLastName(body.get("lastName"));
}
if (body.containsKey("mobileId")) {
m.setMobileDeviceId(Long.parseLong(body.get("mobileId")));
}
members.save(m);
return m;
} else {
return null;
}
}
@PatchMapping("/family/{id}")
/**
* @RequestBody can have:
* name - (Optional)New family name.
* @return
*/
public Family _12(
@PathVariable Long id,
@RequestBody HashMap<String,String> body) {
if (families.existsById(id)) {
Family m = families.findById(id).get();
if (body.containsKey("name")) {
m.setName(body.get("name"));
}
families.save(m);
return m;
} else {
return null;
}
}
@PostMapping("/location") @PostMapping("/location")
/** /**
* @RequestBody requires: * @RequestBody requires:

@ -20,7 +20,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class FamilyContainer extends Family{ public class FamilyContainer extends Family{
List<FamilyMember> members = new ArrayList<>(); List<FamilyMemberContainer> members = new ArrayList<>();
Long id = -1l; Long id = -1l;
FamilyContainer(String name FamilyContainer(String name
@ -31,15 +31,16 @@ public class FamilyContainer extends Family{
id = families.findByName(name).get(0).getId(); id = families.findByName(name).get(0).getId();
List<FamilyRelationship> relations = relationships.findByFamilyId(id); List<FamilyRelationship> relations = relationships.findByFamilyId(id);
for (FamilyRelationship r : relations) { for (FamilyRelationship r : relations) {
this.members.add(members.findById(r.getMemberId()).get()); FamilyMember m = members.findById(r.getMemberId()).get();
this.members.add(new FamilyMemberContainer(m,relationships));
} }
} }
public List<FamilyMember> getMembers() { public List<FamilyMemberContainer> getMembers() {
return members; return members;
} }
public void setMembers(List<FamilyMember> members) { public void setMembers(List<FamilyMemberContainer> members) {
this.members = members; this.members = members;
} }

@ -0,0 +1,76 @@
package sig.family;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.springframework.web.bind.annotation.RequestMapping;
import com.fasterxml.jackson.annotation.JsonInclude;
@RequestMapping
public class FamilyMemberContainer extends FamilyMember{
Long id;
String firstName,lastName;
Long mobileDeviceId;
Long lastLocationId;
String relationship;
FamilyMemberContainer(){}
public FamilyMemberContainer(FamilyMember m,
FamilyRelationshipRepository relationships) {
this.firstName = m.firstName;
this.lastName = m.lastName;
this.mobileDeviceId = m.mobileDeviceId;
this.id=m.id;
relationship = relationships.findByMemberId(m.getId()).get(0).getRelationship();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Long getMobileDeviceId() {
return mobileDeviceId;
}
public void setMobileDeviceId(Long mobileDeviceId) {
this.mobileDeviceId = mobileDeviceId;
}
public Long getLastLocationId() {
return lastLocationId;
}
public void setLastLocationId(Long lastLocationId) {
this.lastLocationId = lastLocationId;
}
public String getRelationship() {
return relationship;
}
public void setRelationship(String relationship) {
this.relationship = relationship;
}
}
Loading…
Cancel
Save