25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

210 satır
8.7KB

  1. import * as React from "react";
  2. import {useCallback, useEffect, useRef, useState} from "react";
  3. import useAlbumsApi from "../services/albums-api";
  4. import {
  5. AddAPhoto,
  6. KeyboardArrowRight,
  7. MoveDown,
  8. MoveUp
  9. } from "@mui/icons-material";
  10. import {Link, useNavigate} from "react-router-dom";
  11. import Loader from "../components/Loader";
  12. import {Button, TextField} from "@mui/material";
  13. import {Album} from "../models/album";
  14. import "./portfolio-manager.scss"
  15. import {useInstances} from "../services/instances/use-instances";
  16. function PortfolioManager() {
  17. const navigate = useNavigate();
  18. const {selectedInstance} = useInstances();
  19. const [apiError, setApiError] = useState(null);
  20. const {fetchAlbums, patchAlbum, deleteAlbum, moveAlbumDown, moveAlbumUp} = useAlbumsApi();
  21. const [albums, setAlbums] = useState(null);
  22. const [isLoading, setIsLoading] = useState(false);
  23. const [albumsToUpdate, setAlbumsToUpdate] = useState(null);
  24. const fetchedRef = useRef(false)
  25. const fetchData = useCallback(async () => {
  26. try {
  27. setIsLoading(true);
  28. setApiError(null);
  29. const albums = await fetchAlbums(selectedInstance);
  30. setAlbums(albums);
  31. setAlbumsToUpdate(albums.map(a => new Album(a)));
  32. } catch (err) {
  33. console.error("Could not fetch albums", err);
  34. setApiError("An error occurred trying to fetch your albums, please contact the system administrator");
  35. setAlbums([]);
  36. } finally {
  37. setIsLoading(false);
  38. }
  39. }, [fetchAlbums, selectedInstance])
  40. useEffect(() => {
  41. if (selectedInstance && !fetchedRef.current) {
  42. fetchData();
  43. fetchedRef.current = true;
  44. }
  45. }, [selectedInstance, fetchData]);
  46. const handleAlbumNameChange = (albumIndex, e) => {
  47. albumsToUpdate[albumIndex].name = e.target.value;
  48. setAlbumsToUpdate([...albumsToUpdate]);
  49. }
  50. const handleAlbumDescriptionChange = (albumIndex, e) => {
  51. albumsToUpdate[albumIndex].description = e.target.value;
  52. setAlbumsToUpdate([...albumsToUpdate]);
  53. }
  54. const albumHasChanges = (idx) => {
  55. return albumsToUpdate[idx].name !== albums[idx].name
  56. || albumsToUpdate[idx].description !== albums[idx].description;
  57. }
  58. const handleSaveAlbum = async (idx) => {
  59. try {
  60. setIsLoading(true);
  61. if (albumHasChanges(idx)) {
  62. albumsToUpdate[idx] = await patchAlbum(selectedInstance, albumsToUpdate[idx]);
  63. setAlbums([
  64. ...albumsToUpdate.map(a => new Album(a))
  65. ]);
  66. }
  67. } catch (err) {
  68. if (typeof err === "string" && err.toLowerCase().indexOf("duplicate") >= 0) {
  69. setApiError("Name already in use");
  70. } else {
  71. console.error(err);
  72. navigate(`/${selectedInstance.instanceId}/albums`, {state: {error: err}});
  73. }
  74. } finally {
  75. setIsLoading(false);
  76. }
  77. }
  78. const handleDeleteAlbum = async (e, albumId) => {
  79. try {
  80. setIsLoading(true);
  81. const updatedAlbums = await deleteAlbum(selectedInstance, albumId);
  82. setAlbums(updatedAlbums);
  83. setAlbumsToUpdate(albumsToUpdate.filter(a => a.albumId !== albumId));
  84. } catch (err) {
  85. console.error(err);
  86. } finally {
  87. setIsLoading(false);
  88. }
  89. }
  90. const handleMoveAlbumDown = async (idx) => {
  91. try {
  92. setIsLoading(true);
  93. const updatedAlbums = await moveAlbumDown(selectedInstance, albumsToUpdate[idx].albumId);
  94. const origAlbum = albumsToUpdate[idx];
  95. albumsToUpdate[idx] = albumsToUpdate[idx + 1];
  96. albumsToUpdate[idx + 1] = origAlbum;
  97. setAlbums(updatedAlbums);
  98. } catch (err) {
  99. console.error(err);
  100. } finally {
  101. setIsLoading(false);
  102. }
  103. }
  104. const handleMoveAlbumUp = async (idx) => {
  105. try {
  106. setIsLoading(true);
  107. const updatedAlbums = await moveAlbumUp(selectedInstance, albumsToUpdate[idx].albumId);
  108. const origAlbum = albumsToUpdate[idx];
  109. albumsToUpdate[idx] = albumsToUpdate[idx - 1];
  110. albumsToUpdate[idx - 1] = origAlbum;
  111. setAlbums(updatedAlbums);
  112. } catch (err) {
  113. console.error(err);
  114. } finally {
  115. setIsLoading(false);
  116. }
  117. }
  118. return <div id="portfolio-manager">
  119. <div id="page-title">
  120. <div id="left-part">
  121. <div className="title">Albums</div>
  122. {albums && <div className="photos-count">({albums.length} albums)</div>}
  123. </div>
  124. <div id="right-part">
  125. <Button id="page-save" variant="contained" color="success"
  126. onClick={() => navigate("new", {state: {album: new Album()}})}>
  127. Create
  128. </Button>
  129. </div>
  130. </div>
  131. {apiError && <div className="error-box">{apiError}</div>}
  132. {!apiError && albums?.length > 0 && <div id="album-list">
  133. {albumsToUpdate.map((album, idx) =>
  134. <div className="card-layout album-list-item" key={album.albumId}>
  135. <div className={`cover-photo ${(album.coverPhoto ? "" : "cover-photo--no-img")}`}
  136. onClick={() => navigate(album.albumId, {state: {album: albums[idx]}})}>
  137. {album.coverPhoto
  138. ? <img src={`${album.coverPhoto.url}?width=450&height=450&fit=cover`}
  139. alt={album.coverPhoto.name}/>
  140. : <AddAPhoto className="no-photos"/>
  141. }
  142. </div>
  143. <div className="card-details">
  144. <div className="title input-element">
  145. <TextField fullWidth={true} variant="standard"
  146. value={album.name} onChange={(e) => handleAlbumNameChange(idx, e)}
  147. />
  148. <Button endIcon={<KeyboardArrowRight/>} variant="contained"
  149. color="secondary"
  150. onClick={() => navigate(album.albumId, {state: {album: albums[idx]}})}>
  151. Photos
  152. </Button>
  153. </div>
  154. <div className="description input-element">
  155. <TextField multiline={true} fullWidth={true} rows={8}
  156. onChange={(e) => handleAlbumDescriptionChange(idx, e)}
  157. value={album.description || ""}
  158. />
  159. </div>
  160. <div className="input-buttons">
  161. <div className="left-buttons">
  162. <Button className="photo-save" variant="contained" color="error"
  163. onClick={(e) => handleDeleteAlbum(e, album.albumId)}>
  164. Delete
  165. </Button>
  166. <Button className="photo-save" variant="contained" color="info" disabled={!albumHasChanges(idx)}
  167. onClick={() => handleSaveAlbum(idx)}>
  168. Save
  169. </Button>
  170. </div>
  171. <div className="right-buttons">
  172. <Button
  173. style={{visibility: (idx > 0 ? "visible" : "hidden")}}
  174. startIcon={<MoveUp/>} variant="contained" color="action"
  175. onClick={() => handleMoveAlbumUp(idx)}>
  176. Move up
  177. </Button>
  178. <Button
  179. style={{visibility: (idx < albumsToUpdate.length - 1 ? "visible" : "hidden")}}
  180. startIcon={<MoveDown/>} variant="contained" color="action"
  181. onClick={(e) => handleMoveAlbumDown(idx)}>
  182. Move down
  183. </Button>
  184. </div>
  185. </div>
  186. </div>
  187. </div>
  188. )}
  189. </div>}
  190. {!apiError && albums?.length === 0 && <div><Link to="new" state={{album: new Album()}}>Click here</Link> to create your first album!</div>}
  191. <Loader loading={isLoading} />
  192. </div>
  193. }
  194. export default PortfolioManager;