Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Genesys PGR
Embedded Genesys UI
Commits
5b7c989a
Commit
5b7c989a
authored
Nov 04, 2020
by
Maxym Borodenko
Browse files
Merge branch '2-shopping-cart' into 'master'
Shopping cart Closes
#2
See merge request
!5
parents
2bfde444
afdf495f
Changes
7
Hide whitespace changes
Inline
Side-by-side
entrypoints/index.html
View file @
5b7c989a
...
...
@@ -22,6 +22,9 @@
<li
class=
"nav-item"
>
<a
class=
"nav-link"
href=
"#/api-info"
>
API Info
</a>
</li>
<li
class=
"nav-item"
>
<a
class=
"nav-link"
href=
"#/cart"
>
Cart
</a>
</li>
<li
class=
"nav-item"
>
<a
class=
"nav-link"
href=
"."
>
EN
</a>
</li>
...
...
src/locales/en/translations.json
View file @
5b7c989a
...
...
@@ -102,5 +102,11 @@
"62"
:
"Field margin"
,
"99"
:
"Other"
}
},
"cart"
:
{
"title"
:
"Shopping cart"
,
"addToCart"
:
"Add to cart"
,
"removeFromCart"
:
"Remove from cart"
,
"isEmpty"
:
"Shopping cart is empty"
}
}
src/ui/AccessionListPage.tsx
View file @
5b7c989a
...
...
@@ -9,10 +9,13 @@ import FilteredPage, { IPageRequest } from '@genesys/client/model/FilteredPage';
import
Pagination
from
'
./Pagination
'
;
import
{
AccessionFilters
}
from
'
./AccessionFilters
'
;
import
{
withTranslation
,
WithTranslation
}
from
'
react-i18next
'
;
import
{
LocalStorageCart
}
from
'
utilities
'
;
interface
IAccessionListPageState
{
filter
:
AccessionFilter
;
accessions
:
FilteredPage
<
Accession
>
;
selected
:
number
[];
isAllSelected
:
boolean
;
}
interface
IAccessionListPageProps
{
...
...
@@ -27,6 +30,8 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr
this
.
state
=
{
filter
:
this
.
props
.
filter
,
accessions
:
null
,
selected
:
[],
isAllSelected
:
false
,
}
}
...
...
@@ -72,9 +77,34 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr
this
.
loadData
({
...
this
.
state
.
filter
,
...
filter
},
{});
};
private
toggleRowSelect
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
{
selected
}
=
this
.
state
;
const
{
id
}
=
(
e
.
currentTarget
as
HTMLElement
).
dataset
;
const
updated
=
selected
.
filter
((
selectedId
)
=>
selectedId
!==
+
id
);
if
(
updated
.
length
===
selected
.
length
)
{
updated
.
push
(
+
id
);
}
this
.
setState
({
selected
:
updated
});
};
private
addSelectedToCart
=
()
=>
{
LocalStorageCart
.
addToCart
(
this
.
state
.
selected
);
this
.
setState
({
selected
:
[],
isAllSelected
:
false
});
};
private
onToggleAll
=
()
=>
{
this
.
setState
((
prevState
)
=>
({
isAllSelected
:
!
prevState
.
isAllSelected
,
selected
:
!
prevState
.
isAllSelected
?
prevState
.
accessions
.
content
.
map
((
acc
)
=>
acc
.
id
)
:
[],
}));
};
public
render
()
{
const
{
accessions
}
=
this
.
state
;
const
{
accessions
,
selected
,
isAllSelected
}
=
this
.
state
;
const
{
t
}
=
this
.
props
;
const
selectedIds
=
new
Set
();
selected
.
forEach
((
id
)
=>
selectedIds
.
add
(
+
id
));
if
(
accessions
===
null
)
{
return
(
<
div
>
{
t
(
'
loading
'
)
}
</
div
>
...
...
@@ -82,31 +112,57 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr
}
else
{
return
(
<>
<
h1
>
{
t
(
'
estimatedNumberOfItems
'
,
{
count
:
accessions
.
totalElements
,
what
:
t
(
'
accession.model
'
,
{
count
:
accessions
.
totalElements
})
})
}
</
h1
>
<
h1
className
=
"d-flex justify-content-between align-items-center"
>
{
t
(
'
estimatedNumberOfItems
'
,
{
count
:
accessions
.
totalElements
,
what
:
t
(
'
accession.model
'
,
{
count
:
accessions
.
totalElements
})
})
}
{
selected
.
length
!==
0
&&
<
button
type
=
"button"
className
=
"btn btn-primary"
onClick
=
{
this
.
addSelectedToCart
}
>
{
t
(
'
cart.addToCart
'
)
}
</
button
>
}
</
h1
>
<
AccessionFilters
filter
=
{
this
.
state
.
filter
}
applyFilter
=
{
this
.
applyFilter
}
key
=
{
`filters-
${
accessions
.
filterCode
}
`
}
/>
<
Pagination
loadData
=
{
this
.
loadData
}
paged
=
{
accessions
}
>
<
table
className
=
"table table-striped"
>
<
thead
className
=
"thead-dark"
>
<
tr
>
<
th
>
{
t
(
'
accession.crop
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.acceNumb
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.accessionName
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.taxonomy
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.countryOfOrigin
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.sampStat
'
)
}
</
th
>
</
tr
>
<
tr
>
<
th
>
<
input
type
=
"checkbox"
name
=
"select-all"
checked
=
{
isAllSelected
}
onChange
=
{
this
.
onToggleAll
}
className
=
"align-middle"
/>
</
th
>
<
th
>
{
t
(
'
accession.crop
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.acceNumb
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.accessionName
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.taxonomy
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.countryOfOrigin
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.sampStat
'
)
}
</
th
>
</
tr
>
</
thead
>
<
tbody
>
{
accessions
.
content
.
map
((
a
)
=>
(
<
tr
key
=
{
a
.
id
}
>
<
td
>
{
a
.
cropName
}
</
td
>
<
td
><
Link
to
=
{
`/a/
${
a
.
uuid
}
`
}
>
{
a
.
accessionNumber
}
</
Link
></
td
>
<
td
>
{
a
.
accessionName
}
</
td
>
<
td
><
span
dangerouslySetInnerHTML
=
{
{
__html
:
a
.
taxonomy
.
taxonNameHtml
}
}
/></
td
>
<
td
>
{
a
.
countryOfOrigin
&&
a
.
countryOfOrigin
.
name
}
</
td
>
<
td
>
{
a
.
sampStat
&&
t
(
`accession.sampleStatus.
${
a
.
sampStat
}
`
)
}
</
td
>
</
tr
>
))
}
{
accessions
.
content
.
map
((
a
,
i
)
=>
(
<
tr
key
=
{
a
.
id
}
className
=
{
selectedIds
.
has
(
a
.
id
)
?
'
table-primary
'
:
''
}
>
<
td
>
<
input
type
=
"checkbox"
name
=
{
`checkbox-
${
a
.
id
}
-
${
i
}
`
}
data-id
=
{
a
.
id
}
checked
=
{
selectedIds
.
has
(
a
.
id
)
}
onChange
=
{
this
.
toggleRowSelect
}
className
=
"align-middle"
/>
</
td
>
<
td
>
{
a
.
cropName
}
</
td
>
<
td
><
Link
to
=
{
`/a/
${
a
.
uuid
}
`
}
>
{
a
.
accessionNumber
}
</
Link
></
td
>
<
td
>
{
a
.
accessionName
}
</
td
>
<
td
><
span
dangerouslySetInnerHTML
=
{
{
__html
:
a
.
taxonomy
.
taxonNameHtml
}
}
/></
td
>
<
td
>
{
a
.
countryOfOrigin
&&
a
.
countryOfOrigin
.
name
}
</
td
>
<
td
>
{
a
.
sampStat
&&
t
(
`accession.sampleStatus.
${
a
.
sampStat
}
`
)
}
</
td
>
</
tr
>
))
}
</
tbody
>
</
table
>
</
Pagination
>
...
...
src/ui/App.tsx
View file @
5b7c989a
...
...
@@ -5,6 +5,7 @@ import ApiInfoPage from './ApiInfoPage';
import
AccessionList
from
'
./AccessionListPage
'
;
import
AccessionDetails
from
'
./AccessionDetailsPage
'
;
import
{
createHashHistory
}
from
'
history
'
;
import
CartPage
from
'
./CartPage
'
;
const
hashHistory
=
createHashHistory
({});
...
...
@@ -25,6 +26,7 @@ export default class App extends React.Component<IAppProps, any> {
<
Route
path
=
"/a/:uuid"
exact
render
=
{
(
props
)
=>
<
AccessionDetails
{
...
props
}
apiUrl
=
{
apiUrl
}
/>
}
/>
<
Route
path
=
"/"
exact
render
=
{
(
props
)
=>
<
AccessionList
{
...
props
}
filter
=
{
this
.
props
.
filter
}
/>
}
/>
<
Route
path
=
"/api-info"
exact
component
=
{
ApiInfoPage
}
/>
<
Route
path
=
"/cart/"
exact
component
=
{
CartPage
}
/>
<
Route
component
=
{
NotFound
}
/>
</
Switch
>
...
...
src/ui/CartPage.tsx
0 → 100644
View file @
5b7c989a
import
React
from
'
react
'
;
import
{
AccessionService
}
from
'
@genesys/client/service
'
;
import
AccessionFilter
from
'
@genesys/client/model/accession/AccessionFilter
'
;
import
Accession
from
'
@genesys/client/model/accession/Accession
'
;
import
FilteredPage
,
{
IPageRequest
}
from
'
@genesys/client/model/FilteredPage
'
;
import
{
withTranslation
,
WithTranslation
}
from
'
react-i18next
'
;
import
{
LocalStorageCart
}
from
'
utilities
'
;
import
{
Link
}
from
'
react-router-dom
'
;
interface
IAccessionListPageState
{
accessions
:
FilteredPage
<
Accession
&
Record
<
string
,
any
>>
;
isEmpty
:
boolean
;
selected
:
number
[];
isAllSelected
:
boolean
;
}
interface
IAccessionListPageProps
extends
WithTranslation
{
filter
?:
AccessionFilter
;
}
export
class
AccessionListPage
extends
React
.
Component
<
IAccessionListPageProps
,
IAccessionListPageState
>
{
public
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
accessions
:
null
,
isEmpty
:
false
,
selected
:
[],
isAllSelected
:
false
,
}
}
public
componentDidMount
():
void
{
if
(
typeof
document
!==
'
undefined
'
)
{
window
.
addEventListener
(
'
storage
'
,
this
.
handleLocalStorageUpdate
);
this
.
receiveData
();
}
}
public
componentWillUnmount
()
{
if
(
typeof
document
!==
'
undefined
'
)
{
window
.
removeEventListener
(
'
storage
'
,
this
.
handleLocalStorageUpdate
);
}
}
private
handleLocalStorageUpdate
=
(
e
:
StorageEvent
)
=>
{
if
(
e
.
key
===
LocalStorageCart
.
LS_KEY
)
{
this
.
receiveData
();
}
};
private
receiveData
=
()
=>
{
const
accessionsIds
=
LocalStorageCart
.
getCartItemsIds
();
if
(
accessionsIds
.
length
===
0
)
{
this
.
setState
({
isEmpty
:
true
,
accessions
:
null
});
}
else
{
this
.
loadData
({
id
:
accessionsIds
},
{});
}
};
private
loadData
=
(
filter
:
AccessionFilter
,
pageR
:
IPageRequest
):
Promise
<
void
>
=>
{
const
cartItems
=
LocalStorageCart
.
getCartItemsLS
();
const
map
=
new
Map
();
cartItems
.
forEach
((
item
)
=>
map
.
set
(
+
item
.
id
,
item
.
distributionFormCode
));
return
AccessionService
.
list
(
filter
,
pageR
)
.
then
((
data
:
FilteredPage
<
Accession
&
any
>
)
=>
{
console
.
log
(
'
accessions:
'
,
data
);
data
.
content
.
forEach
((
accession
)
=>
accession
.
distributionFormCode
=
map
.
get
(
accession
.
id
));
this
.
setState
({
accessions
:
data
,
isEmpty
:
false
});
})
.
catch
((
e
)
=>
{
console
.
log
(
'
Api call failed:
'
,
e
);
});
};
private
toggleRowSelect
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
{
selected
}
=
this
.
state
;
const
{
id
}
=
(
e
.
currentTarget
as
HTMLElement
).
dataset
;
const
updated
=
selected
.
filter
((
selectedId
)
=>
selectedId
!==
+
id
);
if
(
updated
.
length
===
selected
.
length
)
{
updated
.
push
(
+
id
);
}
this
.
setState
({
selected
:
updated
});
};
private
removeItems
=
()
=>
{
const
{
selected
,
accessions
}
=
this
.
state
;
LocalStorageCart
.
removeFromCart
(
selected
);
const
accessionsUpdated
=
{
...
accessions
,
content
:
accessions
.
content
.
filter
((
acc
)
=>
!
selected
.
some
((
id
)
=>
id
===
acc
.
id
))
};
const
isEmpty
=
accessionsUpdated
.
content
.
length
===
0
;
this
.
setState
({
selected
:
[],
accessions
:
isEmpty
?
null
:
accessionsUpdated
,
isEmpty
,
isAllSelected
:
false
});
};
private
onToggleAll
=
()
=>
{
this
.
setState
((
prevState
)
=>
({
isAllSelected
:
!
prevState
.
isAllSelected
,
selected
:
!
prevState
.
isAllSelected
?
prevState
.
accessions
.
content
.
map
((
acc
)
=>
acc
.
id
)
:
[],
}));
};
public
render
()
{
const
{
accessions
,
selected
,
isEmpty
,
isAllSelected
}
=
this
.
state
;
const
{
t
}
=
this
.
props
;
const
selectedIds
=
new
Set
();
selected
.
forEach
((
id
)
=>
selectedIds
.
add
(
id
));
return
(
<>
<
h1
className
=
"d-flex justify-content-between align-items-center"
>
{
t
(
'
cart.title
'
)
}
{
selected
.
length
!==
0
&&
<
button
type
=
"button"
className
=
"btn btn-primary"
onClick
=
{
this
.
removeItems
}
>
{
t
(
'
cart.removeFromCart
'
)
}
</
button
>
}
</
h1
>
{
isEmpty
&&
<
div
>
{
t
(
'
cart.isEmpty
'
)
}
</
div
>
}
{
accessions
&&
(
<
table
className
=
"table table-striped"
>
<
thead
className
=
"thead-dark"
>
<
tr
>
<
th
>
<
input
type
=
"checkbox"
name
=
"select-all"
checked
=
{
isAllSelected
}
onChange
=
{
this
.
onToggleAll
}
className
=
"align-middle"
/>
</
th
>
<
th
>
{
t
(
'
accession.model
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.accessionName
'
)
}
</
th
>
<
th
>
{
t
(
'
accession.countryOfOrigin
'
)
}
</
th
>
</
tr
>
</
thead
>
<
tbody
>
{
accessions
.
content
.
map
((
a
,
i
)
=>
(
<
tr
key
=
{
a
.
id
}
className
=
{
selectedIds
.
has
(
a
.
id
)
?
'
table-primary
'
:
''
}
>
<
td
>
<
input
type
=
"checkbox"
name
=
{
`checkbox-
${
a
.
id
}
-
${
i
}
`
}
data-id
=
{
a
.
id
}
checked
=
{
selectedIds
.
has
(
a
.
id
)
}
onChange
=
{
this
.
toggleRowSelect
}
className
=
"align-middle"
/>
</
td
>
<
td
><
Link
to
=
{
`/a/
${
a
.
id
}
`
}
>
{
a
.
accessionNumber
}
</
Link
></
td
>
<
td
>
{
a
.
accessionName
}
</
td
>
<
td
>
{
a
.
countryOfOrigin
&&
a
.
countryOfOrigin
.
name
}
</
td
>
</
tr
>
))
}
</
tbody
>
</
table
>
)
}
{
!
isEmpty
&&
!
accessions
&&
<
div
>
{
t
(
'
common.loading
'
)
}
...
</
div
>
}
</>
);
};
}
export
default
withTranslation
()(
AccessionListPage
)
src/ui/Navigation.tsx
View file @
5b7c989a
...
...
@@ -2,8 +2,9 @@ import React from 'react';
import
{
Link
}
from
'
react-router-dom
'
;
import
{
WithTranslation
,
withTranslation
}
from
"
react-i18next
"
;
// interface INavigation extends React.ClassAttributes<any> {}
class
Navigation
extends
React
.
Component
<
WithTranslation
,
any
>
{
interface
INavigation
extends
React
.
ClassAttributes
<
any
>
,
WithTranslation
{}
class
Navigation
extends
React
.
Component
<
INavigation
,
any
>
{
public
render
()
{
const
{
t
}
=
this
.
props
;
...
...
@@ -14,6 +15,7 @@ class Navigation extends React.Component<WithTranslation, any> {
<
ul
style
=
{
{
display
:
'
flex
'
,
listStyle
:
'
none
'
}
}
>
<
li
style
=
{
{
marginRight
:
'
20px
'
}
}
><
Link
to
=
"/"
>
{
t
(
"
nav.home
"
)
}
</
Link
></
li
>
<
li
style
=
{
{
marginRight
:
'
20px
'
}
}
><
Link
to
=
"/api-info"
>
{
t
(
"
nav.apiInfo
"
)
}
</
Link
></
li
>
<
li
style
=
{
{
marginRight
:
'
20px
'
}
}
><
Link
to
=
"/cart/"
>
{
t
(
'
nav.cart
'
)
}
</
Link
></
li
>
</
ul
>
</
nav
>
</
header
>
...
...
src/utilities/index.ts
View file @
5b7c989a
...
...
@@ -31,3 +31,39 @@ export function checkAccessTokens(apiUrl: string, clientId: string, clientSecret
return
applicationLogin
();
}
export
class
LocalStorageCart
{
public
static
LS_KEY
=
'
ui-embedded-cart
'
;
public
static
getCartItemsLS
=
()
=>
JSON
.
parse
(
localStorage
.
getItem
(
LocalStorageCart
.
LS_KEY
));
public
static
getCartItemsIds
=
()
=>
{
const
localStorageCart
=
LocalStorageCart
.
getCartItemsLS
();
return
localStorageCart
&&
localStorageCart
.
length
?
localStorageCart
.
map
((
id
)
=>
+
id
)
:
[];
};
public
static
addToCart
=
(
items
:
number
[])
=>
{
// console.log('add to cart: ', items);
const
cartIds
=
LocalStorageCart
.
getCartItemsIds
();
if
(
cartIds
.
length
===
0
)
{
return
localStorage
.
setItem
(
LocalStorageCart
.
LS_KEY
,
JSON
.
stringify
(
items
));
}
const
idsSet
=
new
Set
(
cartIds
);
items
.
forEach
((
id
)
=>
{
if
(
idsSet
.
has
(
id
))
{
return
;
}
idsSet
.
add
(
id
);
});
const
cart
=
[];
idsSet
.
forEach
((
id
)
=>
cart
.
push
(
id
));
localStorage
.
setItem
(
LocalStorageCart
.
LS_KEY
,
JSON
.
stringify
(
cart
));
};
public
static
removeFromCart
=
(
ids
:
number
[])
=>
{
const
cartIds
=
LocalStorageCart
.
getCartItemsIds
();
const
updatedCartItems
=
cartIds
.
filter
((
cartId
)
=>
!
ids
.
some
((
idToRemove
)
=>
cartId
===
idToRemove
));
localStorage
.
setItem
(
LocalStorageCart
.
LS_KEY
,
JSON
.
stringify
(
updatedCartItems
));
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment