diff --git a/src/api/api.ts b/src/api/api.ts index df74c4bf..a5254b32 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -25,7 +25,7 @@ type TLine = { participants: TParticipant[]; }; -type TBasicProductionResponse = { +export type TBasicProductionResponse = { name: string; productionId: string; }; diff --git a/src/assets/icons/arrow_back.svg b/src/assets/icons/arrow_back.svg index c96460fd..49a234d0 100644 --- a/src/assets/icons/arrow_back.svg +++ b/src/assets/icons/arrow_back.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/assets/icons/chevron_down.svg b/src/assets/icons/chevron_down.svg new file mode 100644 index 00000000..a57acef3 --- /dev/null +++ b/src/assets/icons/chevron_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/chevron_up.svg b/src/assets/icons/chevron_up.svg new file mode 100644 index 00000000..c9ddac51 --- /dev/null +++ b/src/assets/icons/chevron_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/headset.svg b/src/assets/icons/headset.svg new file mode 100644 index 00000000..1514f6e1 --- /dev/null +++ b/src/assets/icons/headset.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/assets/icons/icon.tsx b/src/assets/icons/icon.tsx index 98af7638..1f7e1127 100644 --- a/src/assets/icons/icon.tsx +++ b/src/assets/icons/icon.tsx @@ -9,6 +9,9 @@ import ConfirmSvg from "./done.svg?react"; import StepLeftSvg from "./chevron_left.svg?react"; import StepRightSvg from "./navigate_next.svg?react"; import Settings from "./settings.svg?react"; +import ChevronDown from "./chevron_down.svg?react"; +import ChevronUp from "./chevron_up.svg?react"; +import Headset from "./headset.svg?react"; export const MicMuted = () => ; @@ -31,3 +34,9 @@ export const StepLeftIcon = () => ; export const StepRightIcon = () => ; export const SettingsIcon = () => ; + +export const ChevronDownIcon = () => ; + +export const ChevronUpIcon = () => ; + +export const HeadsetIcon = () => ; diff --git a/src/components/header.tsx b/src/components/header.tsx index 06df055f..6598b0bc 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -3,17 +3,27 @@ import { FC } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { backgroundColour } from "../css-helpers/defaults.ts"; import { useGlobalState } from "../global-state/context-provider.tsx"; +import { HeadsetIcon } from "../assets/icons/icon.tsx"; const HeaderWrapper = styled.div` + display: flex; + align-items: center; width: 100%; background: ${backgroundColour}; padding: 1rem; - font-size: 2rem; - font-weight: bold; + font-size: 3rem; + font-weight: semi-bold; margin: 0 0 1rem 0; cursor: pointer; `; +const Logo = styled.svg` + width: 2.4rem; + height: 2.4rem; + margin-right: 1rem; + margin-left: 2rem; +`; + export const Header: FC = () => { const [, dispatch] = useGlobalState(); const navigate = useNavigate(); @@ -34,5 +44,13 @@ export const Header: FC = () => { navigate("/"); } }; - return Intercom; + + return ( + + + + + Intercom + + ); }; diff --git a/src/components/landing-page/create-production.tsx b/src/components/landing-page/create-production.tsx index bf2b59d9..e4c27159 100644 --- a/src/components/landing-page/create-production.tsx +++ b/src/components/landing-page/create-production.tsx @@ -203,7 +203,10 @@ export const CreateProduction = () => { placeholder="Line Name" /> {index === fields.length - 1 && ( - remove(index)} /> + remove(index)} + /> )} diff --git a/src/components/landing-page/form-elements.tsx b/src/components/landing-page/form-elements.tsx index 331bf393..f535dd62 100644 --- a/src/components/landing-page/form-elements.tsx +++ b/src/components/landing-page/form-elements.tsx @@ -7,10 +7,12 @@ const sharedMargin = `margin: 0 0 2rem`; export const FormInput = styled.input` width: 100%; font-size: 1.6rem; - padding: 0.5rem; + padding: 0.75rem; margin: 0 0 2rem; - border: 0.1rem solid #6f6e6e; + border: 0.1rem solid #6d6d6d; border-radius: 0.5rem; + background: #32383b; + color: white; ${sharedMargin}; @@ -43,8 +45,10 @@ export const FormSelect = styled.select` padding: 0.5rem; ${sharedMargin}; - border: 1px solid #6f6e6e; + border: 1px solid #6d6d6d; border-radius: 0.5rem; + background: #32383b; + color: white; `; export const FormLabel = styled.label` @@ -74,9 +78,8 @@ export const StyledWarningMessage = styled.div` align-items: center; `; -const ActionButton = styled.button` - background-color: transparent; - background-image: linear-gradient(to bottom, #fff, #f8eedb); +export const ActionButton = styled.button` + background: #1db954; border: 0 solid #e5e7eb; border-radius: 0.5rem; box-sizing: border-box; @@ -102,7 +105,8 @@ const ActionButton = styled.button` 0 0.2rem 0.2rem rgba(81, 41, 10, 0.2); &:disabled { - background: #c9c6c0; + background: #1db954; + opacity: 0.5; cursor: not-allowed; } @@ -114,20 +118,10 @@ const ActionButton = styled.button` export const PrimaryButton = styled(ActionButton)` &:active:enabled { - background-color: #f3f4f6; - box-shadow: - -0.1rem 0.2rem 0.5rem rgba(81, 41, 10, 0.15), - 0 0.1rem 0.1rem rgba(81, 41, 10, 0.15); + background: #1db954; transform: translateY(0.125rem); } - &:focus { - box-shadow: - rgba(72, 35, 7, 0.46) 0 0 0 0.4rem, - -0.6rem 0.8rem 1rem rgba(81, 41, 10, 0.1), - 0 0.2rem 0.2rem rgba(81, 41, 10, 0.2); - } - &:not(:disabled):hover { } @@ -169,7 +163,7 @@ export const SecondaryButton = styled(ActionButton)` } &:before { - background-color: rgba(72, 54, 42, 0.32); + background: rgba(89, 203, 232, 1); content: ""; display: block; height: 100%; @@ -182,8 +176,7 @@ export const SecondaryButton = styled(ActionButton)` } &:after { - background-color: initial; - background-image: linear-gradient(to bottom, #fff, #f5ead6); + background: rgba(89, 203, 232, 1) content: ""; display: block; overflow: hidden; diff --git a/src/components/landing-page/manage-production-button.tsx b/src/components/landing-page/manage-production-button.tsx index ae4e2768..18861e41 100644 --- a/src/components/landing-page/manage-production-button.tsx +++ b/src/components/landing-page/manage-production-button.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import styled from "@emotion/styled"; -import { PrimaryButton } from "./form-elements"; +import { SecondaryButton } from "./form-elements"; const ButtonWrapper = styled.div` display: flex; @@ -13,12 +13,12 @@ export const ManageProductionButton = () => { return ( - navigate("/manage-productions")} > Manage Productions - + ); }; diff --git a/src/components/manage-productions/line-table.tsx b/src/components/manage-productions/line-table.tsx new file mode 100644 index 00000000..a3c33fa1 --- /dev/null +++ b/src/components/manage-productions/line-table.tsx @@ -0,0 +1,227 @@ +import { useEffect, useRef, useState } from "react"; +import styled from "@emotion/styled"; +import { TLine } from "../production-line/types"; +import { RemoveLineButton } from "../remove-line-button/remove-line-button"; +import { ConfirmIcon } from "../../assets/icons/icon"; +import { isMobile } from "../../bowser"; + +const TableContainer = styled.div` + margin: 1rem 0; + border: 0.1rem solid #6d6d6d; + border-radius: 0.5rem; + overflow-y: scroll; + width: ${isMobile ? "100%" : "45rem"}; + max-height: 35rem; + border-bottom: ${({ lines }: { lines: TLine[] | undefined }) => + lines && lines.length === 0 ? "0.1rem solid #6d6d6d" : "none"}; + background: #32383b; + position: relative; +`; + +const TableBody = styled.div` + transition: max-height 0.3s ease; +`; + +const Table = styled.table` + width: 100%; +`; + +const TableRow = styled.tr` + background-color: #32383b; + cursor: pointer; + + ${isMobile ? `position: relative;` : ""} +`; + +const TableCell = styled.td` + padding: 1.5rem; + border: 0.1rem solid #6d6d6d; + border-left: none; + border-right: none; + text-align: left; + cursor: pointer; + position: relative; + overflow: hidden; + horizontal-align: center; + vertical-align: middle; +`; + +const TableHeaderCell = styled.th` + padding: 1.8rem; + margin-bottom: 2rem; + background-color: #32383b; + color: #d6d3d1; + text-align: left; + font-weight: 700; + font-size: 1.8rem; +`; + +const TruncatedTableCell = styled.td` + padding: 1.8rem; + border: 0.1rem solid #6d6d6d; + border-left: none; + border-right: none; + text-align: left; + position: relative; + cursor: pointer; + + ${({ isOverflowing }: { isOverflowing: boolean }) => ` + white-space: ${isMobile || !isOverflowing ? "normal" : "nowrap"}; + overflow: ${isMobile || !isOverflowing ? "visible" : "hidden"}; + word-wrap: ${isMobile || !isOverflowing ? "break-word" : "normal"}; + text-overflow: ${isOverflowing && !isMobile ? "ellipsis" : "initial"}; + max-width: ${isOverflowing && !isMobile ? "20rem" : "none"}; + position: relative; + `} +`; + +const ConfirmIconWrapper = styled.div` + width: 3rem; + margin-left: 1rem; + cursor: pointer; +`; + +const ConfirmButton = styled.div` + border-radius: 0.5rem; + cursor: pointer; + position: fixed; + font-size: 1.8rem; + padding: 1rem; + z-index: 100; + margin-left: ${isMobile ? "-1rem" : "1rem"}; + margin-top: ${isMobile ? "3.5rem" : "0"}; + display: flex; + justify-content: center; + align-items: center; + background: #32383b; + color: white; + border: 0.1rem solid #6d6d6d; + + &:hover { + transform: scale(1.1); + } + + ${isMobile ? `width: fit-content;` : ""} +`; + +type TLineTableProps = { + removeActive: boolean; + lines?: TLine[]; + verifyRemove: null | string; + removeLine: React.Dispatch>; + setRemoveId: React.Dispatch>; +}; + +interface ConfirmButtonPosition { + top: number; + left: number; +} + +export const LineTable = ({ + removeActive, + lines, + verifyRemove, + removeLine, + setRemoveId, +}: TLineTableProps) => { + const confirmButtonRef = useRef(null); + const [confirmButtonPosition, setConfirmButtonPosition] = + useState(null); + const rowRefs = useRef>(new Map()); + + useEffect(() => { + if (verifyRemove !== null) { + const targetRow = rowRefs.current.get(parseInt(verifyRemove, 10)); + if (targetRow) { + const rect = targetRow.getBoundingClientRect(); + setConfirmButtonPosition({ + top: isMobile ? rect.top : rect.top + 5, + left: isMobile ? rect.left + 20 : rect.right + 10, + }); + } else { + setConfirmButtonPosition(null); + } + } else { + setConfirmButtonPosition(null); + } + }, [verifyRemove]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (!confirmButtonRef.current?.contains(event.target as Node)) { + removeLine(null); + } + }; + document.addEventListener("mousedown", handleClickOutside); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [removeLine]); + + return ( + + + + + + Line Name + ID + {removeActive && Action} + + + + {lines?.map((l) => ( + { + if (el) rowRefs.current.set(parseInt(l.id, 10), el); + }} + > + 50}> + {l.name.length > 50 && !isMobile + ? `${l.name.slice(0, 47)}...` + : l.name} + + {l.id} + {removeActive && ( + + { + if (!verifyRemove) { + removeLine(l.id); + } else { + removeLine(null); + } + }} + /> + + )} + + ))} + +
+ + {confirmButtonPosition && verifyRemove && ( + { + event.preventDefault(); + setRemoveId(parseInt(verifyRemove!, 10)); + }} + > + Remove {lines?.find((line) => line.id === verifyRemove)?.name}? + + + + + )} + + ); +}; diff --git a/src/components/manage-productions/manage-lines.tsx b/src/components/manage-productions/manage-lines.tsx index 6a59f126..8e163326 100644 --- a/src/components/manage-productions/manage-lines.tsx +++ b/src/components/manage-productions/manage-lines.tsx @@ -1,24 +1,23 @@ import { SubmitHandler, useFieldArray, useForm } from "react-hook-form"; import styled from "@emotion/styled"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState, useRef } from "react"; import { ErrorMessage } from "@hookform/error-message"; import { DisplayContainerHeader } from "../landing-page/display-container-header"; -import { TLine, TProduction } from "../production-line/types"; +import { TProduction } from "../production-line/types"; import { - DecorativeLabel, FormInput, FormLabel, PrimaryButton, - SecondaryButton, StyledWarningMessage, } from "../landing-page/form-elements"; -import { FlexContainer } from "../generic-components"; import { Spinner } from "../loader/loader"; import { useRemoveProductionLine } from "./use-remove-production-line"; import { useAddProductionLine } from "./use-add-production-line"; import { RemoveLineButton } from "../remove-line-button/remove-line-button"; import { darkText, errorColour } from "../../css-helpers/defaults"; -import { ConfirmIcon } from "../../assets/icons/icon"; +import { RemoveButton } from "../remove-button/remove-button"; +import { LineTable } from "./line-table"; +import { isMobile } from "../../bowser"; type TManageLines = { production: TProduction; @@ -29,60 +28,9 @@ type FormValues = { lines: { name: string }[]; }; -type TLastItem = { - lastItem: boolean; -}; - -const Container = styled.div` - max-width: 45rem; - min-width: 35rem; - padding: 2rem; - margin: 0 2rem 2rem 0; - border-radius: 1rem; - border: 0.2rem solid #434343; -`; - const NewLineWrapper = styled.div` position: relative; -`; - -const ListItemWrapper = styled.div` - position: relative; - padding: 0.5rem 0; - - &:hover { - background-color: #434343; - border-radius: 0.2rem; - } - - ${({ lastItem }) => (lastItem ? `margin-bottom: 2rem;` : "")} -`; - -const LineItem = styled(DecorativeLabel)` - padding: 0 1rem 0 0; -`; - -const ConfirmButton = styled.button` - cursor: pointer; - position: absolute; - font-size: 2rem; - padding: 0.7rem; - box-shadow: 0.5rem 0.5rem 1rem #212121; - top: 1.5rem; - right: 2.5rem; - z-index: 100; - display: flex; - justify-content: center; - align-items: center; - - &:hover { - transform: scale(1.1); - } -`; - -const ConfirmIconWrapper = styled.div` - width: 3rem; - margin-left: 1rem; + width: ${isMobile ? "90%" : ""}; `; const ButtonWrapper = styled.div` @@ -101,6 +49,22 @@ const FetchErrorMessage = styled.div` border-radius: 0.5rem; `; +const FlexContainer = styled.div` + margin-top: 6rem; + gap: 2rem; + display: flex; + flex-direction: column; + flex-wrap: wrap; + max-width: 55rem; +`; + +const ButtonContainer = styled.div` + display: flex; + gap: 1rem; + flex-direction: row; + justify-content: space-between; +`; + export const ManageLines = ({ production, setProductionIdToFetch, @@ -108,9 +72,12 @@ export const ManageLines = ({ const [removeActive, setRemoveActive] = useState(false); const [updateLines, setUpdateLines] = useState(null); const [verifyRemove, setVerifyRemove] = useState(null); + const [creatingLineError, setCreatingLineError] = useState(false); const [removeId, setRemoveId] = useState(null); const productionIdToNumber = parseInt(production.productionId, 10); + const inputRef = useRef(null); + const { formState: { errors }, control, @@ -138,6 +105,27 @@ export const ManageLines = ({ error: deleteError, } = useRemoveProductionLine(productionIdToNumber, removeId); + useEffect(() => { + setCreatingLineError(!!createError); + }, [createError]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + inputRef.current && + !inputRef.current.contains(event.target as Node) + ) { + setCreatingLineError(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + const onSubmit: SubmitHandler = useCallback( (value) => { if (fetchInProgress) return; @@ -154,6 +142,7 @@ export const ManageLines = ({ }); setRemoveId(null); setVerifyRemove(null); + setUpdateLines(null); setRemoveActive(false); setUpdateLines(null); setProductionIdToFetch(parseInt(production.productionId, 10)); @@ -167,43 +156,17 @@ export const ManageLines = ({ ]); return ( - + Manage Lines - {production.lines.map((singleLine: TLine, index) => ( - - {singleLine.name} - {removeActive && ( - - !verifyRemove - ? setVerifyRemove(singleLine.id) - : setVerifyRemove(null) - } - /> - )} - {verifyRemove === singleLine.id && ( - { - event.preventDefault(); - setRemoveId(parseInt(singleLine.id, 10)); - }} - > - remove {singleLine.name} - - - - - )} - - ))} + {fields.map((field, index) => ( -
+
{index === fields.length - 1 && ( - remove(0)} /> + { + setCreatingLineError(false); + remove(0); + }} + /> )} @@ -228,21 +197,19 @@ export const ManageLines = ({ />
))} - - - { - setVerifyRemove(null); - append({ name: "" }); - }} - > - Create Line - - + {deleteError && ( + + The production line could not be removed. {deleteError.message}. + + )} + {creatingLineError && ( + + The production line could not be created. {createError?.message}. + + )} + - { @@ -252,7 +219,7 @@ export const ManageLines = ({ > Remove Line {deleteInProgress && } - + {fields.length !== 0 && ( @@ -266,17 +233,19 @@ export const ManageLines = ({ )} - - {deleteError && ( - - The production line could not be removed. {deleteError.message}. - - )} - {createError && ( - - The production line could not be created. {createError.message}. - - )} - + + { + setVerifyRemove(null); + append({ name: "" }); + }} + > + Create Line + + + + ); }; diff --git a/src/components/manage-productions/manage-productions.tsx b/src/components/manage-productions/manage-productions.tsx index 2903f873..dc08ac3a 100644 --- a/src/components/manage-productions/manage-productions.tsx +++ b/src/components/manage-productions/manage-productions.tsx @@ -1,11 +1,10 @@ import { ErrorMessage } from "@hookform/error-message"; import { useForm } from "react-hook-form"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import styled from "@emotion/styled"; import { DisplayContainerHeader } from "../landing-page/display-container-header"; import { DecorativeLabel, - PrimaryButton, StyledWarningMessage, } from "../landing-page/form-elements"; import { useFetchProduction } from "../landing-page/use-fetch-production"; @@ -19,8 +18,8 @@ import { TProduction } from "../production-line/types"; import { useFetchProductionList } from "../landing-page/use-fetch-production-list"; import { isMobile } from "../../bowser"; import { useGlobalState } from "../../global-state/context-provider"; -import { useRefreshAnimation } from "../landing-page/use-refresh-animation"; -import { PaginatedList } from "./paginated-list"; +import { ProductionTable } from "./production-table"; +import { TBasicProductionResponse } from "../../api/api"; type FormValue = { productionId: string; @@ -30,25 +29,38 @@ type MobileLayout = { isMobile: boolean; }; -const ShowListBtn = styled(PrimaryButton)` - margin-bottom: 2rem; +const FormInputWrapper = styled.div` + max-width: 55rem; + flex: 1; + width: 100%; + margin-right: 2rem; `; -const FormInputWrapper = styled.div` - max-width: 45rem; - padding-bottom: 1rem; +const DecorativeLabelWrapper = styled.div<{ isMobile: boolean }>` + ${isMobile + ? ` + white-space: normal; + overflow-wrap: break-word; + word-break: break-word; + width: 100%; + ` + : ` + `} `; const SubContainers = styled.div` width: 100%; display: flex; - margin-bottom: 2rem; - ${() => (isMobile ? `flex-direction: column;` : `flex-direction: row;`)} + ${() => + isMobile + ? `flex-direction: column;` + : `flex-direction: row; gap: 2rem; margin-bottom: 2rem; align-items: center;`} `; const Container = styled.form` padding: 1rem 2rem 0 2rem; + margin-left: ${isMobile ? "0" : "12rem"}; `; const BottomMessagesWrapper = styled.div` @@ -75,14 +87,36 @@ const StyledBackBtnIcon = styled.div` margin: 0 0 3rem 0; `; +const SubFlexContainer = styled.div` + display: flex; + width: 100%; + flex-direction: column; + + ${isMobile ? "" : `align-items: flex-start;`} +`; + +const FlexContainer = styled.div` + margin-top: 2rem; + + display: flex; + ${isMobile ? `flex-direction: column;` : `flex-direction: row;`} +`; + +const PageContainer = styled.div` + margin-left: ${isMobile ? "0" : "12rem"}; + margin-right: ${isMobile ? "0" : "12rem"}; +`; + export const ManageProductions = () => { const [showDeleteDoneMessage, setShowDeleteDoneMessage] = useState(false); const [verifyRemove, setVerifyRemove] = useState(false); - const [showProductionsList, setShowProductionsList] = - useState(false); const [delayOnGuideText, setDelayOnGuideText] = useState(false); const [removeId, setRemoveId] = useState(null); + const [productions, setProductions] = useState( + [] + ); + const [offset, setOffset] = useState(0); const [cachedProduction, setCachedProduction] = useState( null ); @@ -91,7 +125,6 @@ export const ManageProductions = () => { ); // Pagination - const [offset, setOffset] = useState("0"); const limit = "10"; const [, dispatch] = useGlobalState(); @@ -110,14 +143,11 @@ export const ManageProductions = () => { min: 1, }); - const { productions, doInitialLoad, error } = useFetchProductionList({ - limit, - offset, - }); - - const showRefreshing = useRefreshAnimation({ - doInitialLoad, - }); + const { productions: fetchedProductions, error: fetchError } = + useFetchProductionList({ + offset: offset.toString(), + limit: limit.toString(), + }); const { error: productionFetchError, @@ -131,6 +161,14 @@ export const ManageProductions = () => { successfullDelete, } = useDeleteProduction(removeId); + useEffect(() => { + if (fetchedProductions) { + setProductions((prev) => { + return [...prev, ...fetchedProductions.productions]; + }); + } + }, [fetchedProductions]); + useEffect(() => { if (formState.isSubmitSuccessful) { reset({ @@ -154,7 +192,6 @@ export const ManageProductions = () => { if (production) { setCachedProduction(production); setProductionIdToFetch(null); - setShowProductionsList(false); } }, [production]); @@ -198,113 +235,147 @@ export const ManageProductions = () => { event.preventDefault(); }; + const fetchNextProductions = () => { + setOffset((prevOffset) => { + const newOffset = prevOffset + parseInt(limit, 10); + return newOffset; + }); + }; + + const handleScroll = (event: React.UIEvent) => { + const bottom = + event.currentTarget.scrollHeight - event.currentTarget.scrollTop >= + event.currentTarget.clientHeight; + + if (bottom) { + fetchNextProductions(); + } + }; + return ( - Manage Productions - - { - onChange(ev); - const pid = parseInt(ev.target.value, 10); - const confirmedPid = Number.isNaN(pid) ? null : pid; - - setProductionIdToFetch(confirmedPid); - setShowDeleteDoneMessage(false); - }} - label="Production ID" - placeholder="Production ID" - name={name} - inputRef={ref} - onBlur={onBlur} - type="number" - loading={fetchLoader} - /> - {delayOnGuideText && ( - - Please enter a production id - - )} - {productionFetchError && ( - - The production ID could not be fetched. {productionFetchError.name}{" "} - {productionFetchError.message}. - - )} - - - {cachedProduction && ( - { - setShowProductionsList(!showProductionsList); - }} - > - {showProductionsList ? "Hide" : "Show"} Productions List - - )} - {(!cachedProduction || showProductionsList) && ( - setOffset(input)} - showRefreshing={showRefreshing} - productions={productions} - error={error} - manageProduction={(v: string) => { - setProductionIdToFetch(parseInt(v, 10)); - reset({ - productionId: `${v}`, - }); - setShowProductionsList(false); - setShowDeleteDoneMessage(false); - }} - /> - )} - {cachedProduction && ( - <> + + Manage Productions + {cachedProduction && ( - Production name: {cachedProduction.name} + + Production name:{" "} + {isMobile ? ( + + {cachedProduction.name} + + ) : ( + {cachedProduction.name} + )} + - - - handleCustomSubmit()} - verifyRemove={verifyRemove} - setVerifyRemove={(input) => setVerifyRemove(input)} - reset={() => { - setVerifyRemove(false); - setRemoveId(null); - setCachedProduction(null); + )} + + + + {isMobile && ( + { + setProductionIdToFetch(parseInt(v, 10)); + reset({ + productionId: `${v}`, + }); + setShowDeleteDoneMessage(false); + }} + isSelectedProduction={cachedProduction} + onScroll={handleScroll} + /> + )} + + { + onChange(ev); + const pid = parseInt(ev.target.value, 10); + const confirmedPid = Number.isNaN(pid) ? null : pid; + + setProductionIdToFetch(confirmedPid); + setShowDeleteDoneMessage(false); + }} + label="Production ID" + placeholder="Production ID" + name={name} + inputRef={ref} + onBlur={onBlur} + type="number" + loading={fetchLoader} + /> + {delayOnGuideText && ( + + Please enter a production id + + )} + {productionFetchError && ( + + The production ID could not be fetched.{" "} + {productionFetchError.name} {productionFetchError.message}. + + )} + + + {cachedProduction && ( + handleCustomSubmit()} + verifyRemove={verifyRemove} + setVerifyRemove={(input) => setVerifyRemove(input)} + reset={() => { + setVerifyRemove(false); + setRemoveId(null); + }} + /> + )} + + {cachedProduction && ( + + )} + + {!isMobile && ( + { + setProductionIdToFetch(parseInt(v, 10)); reset({ - productionId: "", + productionId: `${v}`, }); + setShowDeleteDoneMessage(false); }} + isSelectedProduction={cachedProduction} + onScroll={handleScroll} /> - - - )} - - {productionDeleteError && ( - - The production ID could not be deleted. {productionDeleteError.name}{" "} - {productionDeleteError.message}. - - )} - {showDeleteDoneMessage && ( - - The production {production?.name} has been removed - - )} - + )} + + + {productionDeleteError && ( + + The production ID could not be deleted.{" "} + {productionDeleteError.name} {productionDeleteError.message}. + + )} + {showDeleteDoneMessage && ( + + The production {production?.name} has been removed + + )} + + ); }; diff --git a/src/components/manage-productions/production-table.tsx b/src/components/manage-productions/production-table.tsx new file mode 100644 index 00000000..3aeb48f7 --- /dev/null +++ b/src/components/manage-productions/production-table.tsx @@ -0,0 +1,183 @@ +import styled from "@emotion/styled"; +import { useState } from "react"; +import { ChevronDownIcon, ChevronUpIcon } from "../../assets/icons/icon"; +import { TBasicProductionResponse } from "../../api/api"; +import { LocalError } from "../error"; +import { isMobile } from "../../bowser"; +import { TProduction } from "../production-line/types"; + +const TableContainer = styled.div` + margin-bottom: 1rem; + margin-top: 2rem; + border-radius: 0.5rem; + overflow: hidden; + width: ${isMobile ? "100%" : "65rem"}; + position: relative; + min-width: 30rem; + margin-left: ${isMobile ? "0" : "2rem"}; +`; + +const TableHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + background-color: #32383b; + color: #d6d3d1; + padding: 1rem; + font-size: 2rem; + font-weight: 700; + cursor: pointer; + border: 0.1rem solid #6d6d6d; + border-bottom: ${({ isOpen }: { isOpen: boolean }) => + isOpen ? "none" : "0.1rem solid #6d6d6d;"}; + border-radius: 0.5rem; + border-bottom-left-radius: ${({ isOpen }: { isOpen: boolean }) => + isOpen ? "0" : "0.5rem"}; + border-bottom-right-radius: ${({ isOpen }: { isOpen: boolean }) => + isOpen ? "0" : "0.5rem"}; +`; + +const TableIcon = styled.span` + font-size: 1.4rem; + transform: ${({ isOpen }: { isOpen: boolean }) => (isOpen ? "50rem" : "0")}; +`; + +const IconWrapper = styled.div` + width: 2.4rem; +`; + +const TableBody = styled.div<{ isOpen: boolean }>` + max-height: ${({ isOpen }) => (isOpen ? "50rem" : "0")}; + overflow: auto; + opacity: ${({ isOpen }) => (isOpen ? "1" : "0")}; + border: ${({ isOpen }) => (isOpen ? "0.1rem solid #6d6d6d" : "none")}; + border-radius: 0.5rem; + border-bottom: none; + border-top: none; + transition: + max-height 0.3s ease, + opacity 0.3s ease; +`; + +const Table = styled.table` + width: 100%; + border-collapse: collapse; +`; + +const TableRow = styled.tr` + cursor: pointer; + + ${({ isSelectedProduction }: { isSelectedProduction?: boolean }) => + isSelectedProduction + ? `background-color: #4B555A` + : `background-color: #32383b;`} +`; + +const TableCell = styled.td` + padding: 1.8rem; + border: 0.1rem solid #6d6d6d; + border-left: none; + border-right: none; + text-align: left; + cursor: pointer; +`; + +const TruncatedTableCell = styled.td` + padding: 1.8rem; + border: 0.1rem solid #6d6d6d; + border-left: none; + border-right: none; + text-align: left; + cursor: pointer; + position: relative; + + ${({ isOverflowing }: { isOverflowing: boolean }) => ` + white-space: ${isMobile || !isOverflowing ? "normal" : "nowrap"}; + overflow: ${isMobile || !isOverflowing ? "visible" : "hidden"}; + word-wrap: ${isMobile || !isOverflowing ? "break-word" : "normal"}; + text-overflow: ${isOverflowing && !isMobile ? "ellipsis" : "initial"}; + max-width: ${isOverflowing && !isMobile ? "20rem" : "none"}; + position: relative; + `} +`; + +const TableHeaderCell = styled.th` + padding: 0.8rem; + margin-bottom: 2rem; + background-color: #32383b; + color: #d6d3d1; + text-align: left; + font-weight: 700; + cursor: pointer; + border-top: none; +`; + +type TProductionTableProps = { + productions?: TBasicProductionResponse[]; + setProductionId: (v: string) => void; + error: Error | null; + isSelectedProduction: TProduction | null; + onScroll: (event: React.UIEvent) => void; +}; + +export const ProductionTable = ({ + productions, + setProductionId, + error, + isSelectedProduction, + onScroll, +}: TProductionTableProps) => { + const [isOpen, setIsOpen] = useState(false); + + const toggleOpen = () => { + setIsOpen(!isOpen); + }; + + return ( + <> + {error && } + {!error && productions && ( + + + Production List + + + {isOpen ? : } + + + + + + + + Production Name + ID + + + + {productions?.map((p) => ( + { + setProductionId(p.productionId); + }} + > + 50}> + {p.name.length > 50 && !isMobile + ? `${p.name.slice(0, 47)}...` + : p.name} + + {p.productionId} + + ))} + +
+
+
+ )} + + ); +}; diff --git a/src/components/manage-productions/remove-production.tsx b/src/components/manage-productions/remove-production.tsx index b9090b09..1c4d8dd0 100644 --- a/src/components/manage-productions/remove-production.tsx +++ b/src/components/manage-productions/remove-production.tsx @@ -1,16 +1,8 @@ import styled from "@emotion/styled"; -import { DisplayContainerHeader } from "../landing-page/display-container-header"; -import { PrimaryButton } from "../landing-page/form-elements"; +import { ActionButton } from "../landing-page/form-elements"; import { Spinner } from "../loader/loader"; - -const Container = styled.div` - max-width: 45rem; - min-width: 35rem; - padding: 2rem; - margin: 0 2rem 2rem 0; - border-radius: 1rem; - border: 0.2rem solid #434343; -`; +import { RemoveButton } from "../remove-button/remove-button"; +import { isMobile } from "../../bowser"; const VerifyBtnWrapper = styled.div` margin: 3rem 0 2rem 2rem; @@ -18,15 +10,24 @@ const VerifyBtnWrapper = styled.div` const VerifyButtons = styled.div` display: flex; - padding: 1rem 0 0 0; + margin-top: 2rem; + gap: 2rem; `; -const Button = styled(PrimaryButton)` - margin: 0 1rem 0 0; +const CancelButton = styled(ActionButton)` + background: #d6d3d1; + color: "#27272a"; `; const ButtonWrapper = styled.div` - margin: 2rem 0 2rem 0; + margin: ${isMobile ? "0 0 1rem" : "2.5rem 0 2rem 0"}; + + ${() => + isMobile && + ` + display: flex; + justify-content: flex-end; + `} `; type TRemoveProduction = { @@ -45,11 +46,10 @@ export const RemoveProduction = ({ reset, }: TRemoveProduction) => { return ( - - Remove Production + <> {!verifyRemove && ( - { @@ -58,37 +58,37 @@ export const RemoveProduction = ({ > Remove {deleteLoader && } - + )} {verifyRemove && (

Are you sure?

- - +
)} -
+ ); }; diff --git a/src/components/navigate-to-root-button/navigate-to-root-button.tsx b/src/components/navigate-to-root-button/navigate-to-root-button.tsx index fbfecf0b..82eaf43f 100644 --- a/src/components/navigate-to-root-button/navigate-to-root-button.tsx +++ b/src/components/navigate-to-root-button/navigate-to-root-button.tsx @@ -1,9 +1,11 @@ import styled from "@emotion/styled"; import { useNavigate } from "react-router-dom"; import { BackArrow } from "../../assets/icons/icon"; -import { PrimaryButton } from "../landing-page/form-elements"; -const StyledBackBtn = styled(PrimaryButton)` +const StyledBackBtn = styled.div` + cursor: pointer; + color: #59cbe8; + background: transparent; padding: 0; margin: 0; width: 4rem; @@ -18,7 +20,6 @@ export const NavigateToRootButton = ({ return ( { if (resetOnExit) { resetOnExit(); diff --git a/src/components/production-line/long-press-to-talk-button.tsx b/src/components/production-line/long-press-to-talk-button.tsx index 002c62cb..adaa2461 100644 --- a/src/components/production-line/long-press-to-talk-button.tsx +++ b/src/components/production-line/long-press-to-talk-button.tsx @@ -8,6 +8,9 @@ type TLongPressToTalkButton = { }; const Button = styled(PrimaryButton)` + background: rgba(50, 56, 59, 1); + color: white; + border: 0.2rem solid #6d6d6d; position: relative; width: 100%; diff --git a/src/components/production-line/production-line.tsx b/src/components/production-line/production-line.tsx index d186ebe5..d812a3c0 100644 --- a/src/components/production-line/production-line.tsx +++ b/src/components/production-line/production-line.tsx @@ -5,7 +5,7 @@ import { useGlobalState } from "../../global-state/context-provider.tsx"; import { useAudioInput } from "./use-audio-input.ts"; import { useRtcConnection } from "./use-rtc-connection.ts"; import { useEstablishSession } from "./use-establish-session.ts"; -import { SecondaryButton } from "../landing-page/form-elements.tsx"; +import { ActionButton } from "../landing-page/form-elements.tsx"; import { UserList } from "./user-list.tsx"; import { MicMuted, @@ -82,7 +82,9 @@ const FlexButtonWrapper = styled.div` } `; -const UserControlBtn = styled(SecondaryButton)` +const UserControlBtn = styled(ActionButton)` + background: rgba(50, 56, 59, 1); + border: 0.2rem solid #6d6d6d; width: 100%; `; diff --git a/src/components/production-line/settings-modal.tsx b/src/components/production-line/settings-modal.tsx index 9f578e97..3453d11a 100644 --- a/src/components/production-line/settings-modal.tsx +++ b/src/components/production-line/settings-modal.tsx @@ -3,12 +3,12 @@ import React, { useRef, useState } from "react"; import { ErrorMessage } from "@hookform/error-message"; import { PrimaryButton, - SecondaryButton, FormLabel, FormInput, FormContainer, DecorativeLabel, StyledWarningMessage, + ActionButton, } from "../landing-page/form-elements"; const ModalOverlay = styled.div` @@ -50,8 +50,8 @@ const ModalCloseButton = styled.button` font-size: 1.6rem; `; -const CancelButton = styled(SecondaryButton)` - background-color: #000000; +const CancelButton = styled(ActionButton)` + background: #d6d3d1; `; const ButtonDiv = styled.div` diff --git a/src/components/remove-button/remove-button.tsx b/src/components/remove-button/remove-button.tsx new file mode 100644 index 00000000..1ddd21dc --- /dev/null +++ b/src/components/remove-button/remove-button.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { ActionButton } from "../landing-page/form-elements"; + +const RemoveBtn = styled(ActionButton)` + cursor: pointer; + padding: 1rem; + background: #dc2626; + color: white; + z-index: 1; + + &:active:enabled { + background: #990f0f; + } +`; + +type TRemoveButton = { + onClick: () => void; + type?: "button" | "submit" | "reset"; + disabled?: boolean; + className?: string; + children: React.ReactNode; +}; + +export const RemoveButton = ({ + onClick, + type, + children, + disabled, + className, +}: TRemoveButton) => { + return ( + + {children} + + ); +}; diff --git a/src/components/remove-line-button/remove-line-button.tsx b/src/components/remove-line-button/remove-line-button.tsx index 47a0a022..fe377381 100644 --- a/src/components/remove-line-button/remove-line-button.tsx +++ b/src/components/remove-line-button/remove-line-button.tsx @@ -3,12 +3,10 @@ import { RemoveIcon } from "../../assets/icons/icon.tsx"; const RemoveLineBtn = styled.button` cursor: pointer; - position: absolute; - top: -0.7rem; - right: -0.5rem; - padding: 1rem; background: transparent; border: transparent; + position: ${({ isCreatingLine }: { isCreatingLine?: boolean }) => + isCreatingLine ? "absolute" : "relative"}; `; const ButtonIcon = styled.div` @@ -17,11 +15,17 @@ const ButtonIcon = styled.div` export const RemoveLineButton = ({ removeLine, + isCreatingLine, }: { removeLine: () => void; + isCreatingLine?: boolean; }) => { return ( - removeLine()}> + removeLine()} + > diff --git a/src/css-helpers/defaults.ts b/src/css-helpers/defaults.ts index 83ea50cc..67bf6c53 100644 --- a/src/css-helpers/defaults.ts +++ b/src/css-helpers/defaults.ts @@ -1,4 +1,4 @@ -export const backgroundColour = "#1a1a1a"; +export const backgroundColour = "#242424"; export const darkText = "#1a1a1a"; export const errorColour = "#f96c6c";