diff --git a/apps/admin-ui/gql/gql-types.ts b/apps/admin-ui/gql/gql-types.ts index 64bff309c7..4480b483e9 100644 --- a/apps/admin-ui/gql/gql-types.ts +++ b/apps/admin-ui/gql/gql-types.ts @@ -5799,6 +5799,7 @@ export type ResourceFieldsFragment = { id: string; pk?: number | null; nameFi?: string | null; + locationType?: ResourceLocationType | null; space?: { id: string; nameFi?: string | null; @@ -5817,6 +5818,7 @@ export type SpaceFieldsFragment = { id: string; pk?: number | null; nameFi?: string | null; + locationType?: ResourceLocationType | null; space?: { id: string; nameFi?: string | null; @@ -6136,6 +6138,7 @@ export type UnitQuery = { id: string; pk?: number | null; nameFi?: string | null; + locationType?: ResourceLocationType | null; space?: { id: string; nameFi?: string | null; @@ -8962,6 +8965,7 @@ export const ResourceFieldsFragmentDoc = gql` id pk nameFi + locationType space { id nameFi diff --git a/apps/admin-ui/package.json b/apps/admin-ui/package.json index 9d1354ddcb..ca39afe11f 100644 --- a/apps/admin-ui/package.json +++ b/apps/admin-ui/package.json @@ -38,7 +38,6 @@ "common": "workspace:*", "date-fns": "^4.1.0", "eslint-config-custom": "workspace:*", - "focus-trap-react": "^10.2.3", "form-data": "^4.0.0", "graphql": "^16.9.0", "hds-core": "^3.11.0", diff --git a/apps/admin-ui/src/common/fragments.tsx b/apps/admin-ui/src/common/fragments.tsx index 949a601d43..8af29ebd6a 100644 --- a/apps/admin-ui/src/common/fragments.tsx +++ b/apps/admin-ui/src/common/fragments.tsx @@ -26,6 +26,7 @@ export const RESOURCE_FRAGMENT = gql` id pk nameFi + locationType space { id nameFi diff --git a/apps/admin-ui/src/i18n/messages.ts b/apps/admin-ui/src/i18n/messages.ts index f0a5c37bcf..47584843b2 100644 --- a/apps/admin-ui/src/i18n/messages.ts +++ b/apps/admin-ui/src/i18n/messages.ts @@ -1691,6 +1691,10 @@ const translations: ITranslations = { Kuvissa näkyviltä ihmisiltä tulee olla kuvauslupa. Kuvissa ei saa näkyä turvakameroita.`, ], }, + locationType: { + FIXED: ["Kiinteä"], + MOVABLE: ["Siirrettävä"], + }, priceUnit: { FIXED: ["Per kerta"], PER_15_MINS: ["Per 15 min"], diff --git a/apps/admin-ui/src/spa/application-rounds/[id]/allocation/ApplicationEventCard.tsx b/apps/admin-ui/src/spa/application-rounds/[id]/allocation/ApplicationEventCard.tsx index a723cec8ce..042a4de874 100644 --- a/apps/admin-ui/src/spa/application-rounds/[id]/allocation/ApplicationEventCard.tsx +++ b/apps/admin-ui/src/spa/application-rounds/[id]/allocation/ApplicationEventCard.tsx @@ -23,7 +23,7 @@ import { AllocatedTimeSlotNodeT, } from "./modules/applicationRoundAllocation"; import { useFocusAllocatedSlot, useFocusApplicationEvent } from "./hooks"; -import { PopupMenu } from "@/component/PopupMenu"; +import { PopupMenu } from "common/src/components/PopupMenu"; import { type ApolloQueryResult } from "@apollo/client"; import { getApplicationUrl } from "@/common/urls"; import { errorToast } from "common/src/common/toast"; diff --git a/apps/admin-ui/src/spa/unit/ResourcesTable.tsx b/apps/admin-ui/src/spa/unit/ResourcesTable.tsx index a1b2b659a0..9126531213 100644 --- a/apps/admin-ui/src/spa/unit/ResourcesTable.tsx +++ b/apps/admin-ui/src/spa/unit/ResourcesTable.tsx @@ -1,39 +1,36 @@ import React, { useRef, useState } from "react"; import { trim } from "lodash"; import { useTranslation } from "react-i18next"; -import styled from "styled-components"; import { gql, type ApolloQueryResult } from "@apollo/client"; import { useNavigate } from "react-router-dom"; import { ConfirmationDialog } from "common/src/components/ConfirmationDialog"; import { useDeleteResourceMutation, type Maybe, - type ResourceNode, type UnitQuery, } from "@gql/gql-types"; -import { PopupMenu } from "@/component/PopupMenu"; +import { PopupMenu } from "common/src/components/PopupMenu"; import { getResourceUrl } from "@/common/urls"; import { CustomTable } from "@/component/Table"; import { errorToast, successToast } from "common/src/common/toast"; import { truncate } from "common/src/helpers"; import { MAX_NAME_LENGTH } from "@/common/const"; import { TableLink } from "@/styles/util"; +import { Flex } from "common/styles/util"; interface IProps { unit: UnitQuery["unit"]; refetch: () => Promise>; } -const ResourceNodeContainer = styled.div` - display: flex; - align-items: center; -`; +type SpaceT = NonNullable["spaces"][0]; +type ResourceT = NonNullable["resources"][0]; type ResourcesTableColumn = { headerName: string; key: string; isSortable: boolean; - transform?: (space: ResourceNode) => JSX.Element | string; + transform?: (resource: ResourceT) => JSX.Element | string; }; export function ResourcesTable({ unit, refetch }: IProps): JSX.Element { @@ -49,7 +46,7 @@ export function ResourcesTable({ unit, refetch }: IProps): JSX.Element { const history = useNavigate(); const [resourceWaitingForDelete, setResourceWaitingForDelete] = - useState(null); + useState(null); function handleEditResource(pk: Maybe | undefined) { if (pk == null || unit?.pk == null) { @@ -58,7 +55,7 @@ export function ResourcesTable({ unit, refetch }: IProps): JSX.Element { history(getResourceUrl(pk, unit.pk)); } - function handleDeleteResource(resource: ResourceNode) { + function handleDeleteResource(resource: ResourceT) { if (resource.pk == null) { return; } @@ -69,7 +66,7 @@ export function ResourcesTable({ unit, refetch }: IProps): JSX.Element { { headerName: t("ResourceTable.headings.name"), key: `nameFi`, - transform: ({ pk, nameFi }: ResourceNode) => { + transform: ({ pk, nameFi }: ResourceT) => { const link = getResourceUrl(pk, unit?.pk); const name = nameFi != null && nameFi.length > 0 ? nameFi : "-"; return ( @@ -83,14 +80,14 @@ export function ResourcesTable({ unit, refetch }: IProps): JSX.Element { { headerName: t("ResourceTable.headings.unitName"), key: "space.unit.nameFi", - transform: ({ space }: ResourceNode) => + transform: ({ space }: ResourceT) => space?.unit?.nameFi ?? t("ResourceTable.noSpace"), isSortable: false, }, { headerName: "", key: "type", - transform: (resource: ResourceNode) => ( + transform: (resource: ResourceT) => ( handleEditResource(resource.pk)} @@ -101,14 +98,27 @@ export function ResourcesTable({ unit, refetch }: IProps): JSX.Element { }, ]; + const handleConfirmDelete = async () => { + if (resourceWaitingForDelete?.pk == null) { + return; + } + try { + await deleteResource(resourceWaitingForDelete.pk); + successToast({ text: t("ResourceTable.removeSuccess") }); + setResourceWaitingForDelete(null); + refetch(); + } catch (error) { + errorToast({ text: t("ResourceTable.removeFailed") }); + } + }; + const rows = resources ?? []; // TODO add if no resources: // const hasSpaces={Boolean(unit?.spaces?.length)} // noResultsKey={hasSpaces ? "Unit.noResources" : "Unit.noResourcesSpaces"} return ( - // has to be a grid otherwise inner table breaks -
+ <> {resourceWaitingForDelete && ( setResourceWaitingForDelete(null)} - onAccept={async () => { - if (resourceWaitingForDelete.pk == null) { - return; - } - try { - await deleteResource(resourceWaitingForDelete.pk); - successToast({ text: t("ResourceTable.removeSuccess") }); - setResourceWaitingForDelete(null); - refetch(); - } catch (error) { - errorToast({ text: t("ResourceTable.removeFailed") }); - } - }} + onAccept={handleConfirmDelete} /> )} -
+ ); } @@ -152,13 +150,20 @@ function ResourceMenu({ locationType, onEdit, onDelete, -}: ResourceNode & { onDelete: () => void; onEdit: () => void }) { +}: ResourceT & { onDelete: () => void; onEdit: () => void }) { const { t } = useTranslation(); const ref = useRef(null); + const type = locationType ? t(`locationType.${locationType}`) : "-"; return ( - - {locationType} + + {type} - + ); } diff --git a/apps/admin-ui/src/spa/unit/SpacesTable.tsx b/apps/admin-ui/src/spa/unit/SpacesTable.tsx index 8971fe3d94..58e27ee7ce 100644 --- a/apps/admin-ui/src/spa/unit/SpacesTable.tsx +++ b/apps/admin-ui/src/spa/unit/SpacesTable.tsx @@ -2,17 +2,15 @@ import React, { useRef, useState } from "react"; import { IconGroup } from "hds-react"; import { trim } from "lodash"; import { useTranslation } from "react-i18next"; -import styled from "styled-components"; import { gql, type ApolloQueryResult } from "@apollo/client"; import { useNavigate } from "react-router-dom"; import { ConfirmationDialog } from "common/src/components/ConfirmationDialog"; import { type Maybe, - type SpaceNode, useDeleteSpaceMutation, - UnitQuery, + type UnitQuery, } from "@gql/gql-types"; -import { PopupMenu } from "@/component/PopupMenu"; +import { PopupMenu } from "common/src/components/PopupMenu"; import Modal, { useModal as useHDSModal } from "@/component/HDSModal"; import { NewSpaceModal } from "./space/new-space-modal/NewSpaceModal"; import { errorToast } from "common/src/common/toast"; @@ -21,27 +19,16 @@ import { getSpaceUrl } from "@/common/urls"; import { truncate } from "common/src/helpers"; import { MAX_NAME_LENGTH } from "@/common/const"; import { TableLink } from "@/styles/util"; +import { Flex } from "common/styles/util"; interface IProps { unit: UnitQuery["unit"]; refetch: () => Promise>; } -const Prop = styled.div` - margin-top: auto; - display: flex; - align-items: center; - gap: var(--spacing-2-xs); - font-family: var(--tilavaraus-admin-font-medium); - font-weight: 500; - margin-bottom: var(--spacing-xs); -`; - -const MaxPersons = styled.div` - display: flex; -`; +type SpaceT = NonNullable["spaces"][0]; -function countSubSpaces(space: SpaceNode): number { +function countSubSpaces(space: Pick): number { return (space.children || []).reduce( (p, c) => p + 1 + (c ? countSubSpaces(c) : 0), 0 @@ -52,7 +39,7 @@ type SpacesTableColumn = { headerName: string; key: string; isSortable: boolean; - transform?: (space: SpaceNode) => JSX.Element | string; + transform?: (space: SpaceT) => JSX.Element | string; }; export function SpacesTable({ unit, refetch }: IProps): JSX.Element { @@ -99,9 +86,9 @@ export function SpacesTable({ unit, refetch }: IProps): JSX.Element { const history = useNavigate(); const [spaceWaitingForDelete, setSpaceWaitingForDelete] = - useState(null); + useState(null); - function handleRemoveSpace(space: SpaceNode) { + function handleRemoveSpace(space: SpaceT) { if (space && space.resources && space?.resources.length > 0) { errorToast({ text: t("SpaceTable.removeConflictMessage"), @@ -112,7 +99,7 @@ export function SpacesTable({ unit, refetch }: IProps): JSX.Element { setSpaceWaitingForDelete(space); } - function handeAddSubSpace(space: SpaceNode) { + function handeAddSubSpace(space: SpaceT) { openWithContent( { + transform: (space: SpaceT) => { const { pk, nameFi } = space; const link = getSpaceUrl(pk, unit?.pk); const name = nameFi != null && nameFi.length > 0 ? nameFi : "-"; @@ -151,7 +138,7 @@ export function SpacesTable({ unit, refetch }: IProps): JSX.Element { { headerName: t("Unit.headings.code"), key: "code", - transform: ({ code }: SpaceNode) => trim(code), + transform: ({ code }: SpaceT) => trim(code), isSortable: false, }, { @@ -166,7 +153,7 @@ export function SpacesTable({ unit, refetch }: IProps): JSX.Element { { headerName: t("Unit.headings.surfaceArea"), key: "surfaceArea", - transform: ({ surfaceArea }: SpaceNode) => + transform: ({ surfaceArea }: SpaceT) => surfaceArea ? `${surfaceArea}m²` : "", isSortable: false, }, @@ -174,13 +161,19 @@ export function SpacesTable({ unit, refetch }: IProps): JSX.Element { headerName: t("Unit.headings.maxPersons"), key: "maxPersons", // TODO this is weird it creates both the max Persons and the buttons to the same cell - transform: (space: SpaceNode) => ( - + transform: (space: SpaceT) => ( + {space.maxPersons != null && ( - + {space.maxPersons} - + )} - + ), isSortable: false, }, @@ -210,8 +203,7 @@ export function SpacesTable({ unit, refetch }: IProps): JSX.Element { // TODO add if no spaces => "Unit.noSpaces" return ( - // has to be a grid otherwise inner table breaks -
+ <> )} -
+ ); } diff --git a/apps/admin-ui/src/spa/unit/space/new-space-modal/NewSpaceModal.tsx b/apps/admin-ui/src/spa/unit/space/new-space-modal/NewSpaceModal.tsx index fcee97b7f6..6ed463b51b 100644 --- a/apps/admin-ui/src/spa/unit/space/new-space-modal/NewSpaceModal.tsx +++ b/apps/admin-ui/src/spa/unit/space/new-space-modal/NewSpaceModal.tsx @@ -3,7 +3,6 @@ import { type ApolloQueryResult } from "@apollo/client"; import { useCreateSpaceMutation, type SpaceCreateMutationInput, - type SpaceNode, type UnitQuery, } from "@gql/gql-types"; import { Page1 } from "./Page1"; @@ -16,11 +15,13 @@ import { useTranslation } from "react-i18next"; type Props = { unit: UnitQuery["unit"]; - parentSpace?: SpaceNode; + parentSpace?: SpaceT; closeModal: () => void; refetch: () => Promise>; }; +type SpaceT = NonNullable["spaces"][0]; + export function NewSpaceModal({ unit, closeModal, @@ -54,8 +55,6 @@ export function NewSpaceModal({ closeModal(); refetch(); } catch (e) { - // eslint-disable-next-line no-console - console.error(e); errorToast({ text: t("SpaceModal.page2.saveFailed") }); } } diff --git a/apps/admin-ui/src/styles/variables.css b/apps/admin-ui/src/styles/variables.css index 10d3718d73..202c58bc7b 100644 --- a/apps/admin-ui/src/styles/variables.css +++ b/apps/admin-ui/src/styles/variables.css @@ -12,30 +12,22 @@ --tilavaraus-admin-gray-darker: #f0f1f4; --tilavaraus-ui-gray: var(--color-black-40); --tilavaraus-admin-handling-count-color: #ff8003; - --container-width-xxl: 1440px; - --container-width-small: 944px; - --main-menu-width: 200px; - --ingress-container-width: 1080px; /* TODO remove these variables and use the common ones directly */ --tilavaraus-admin-font: var(--font-regular); --tilavaraus-admin-font-medium: var(--font-medium); --tilavaraus-admin-font-bold: var(--font-bold); - --tilavaraus-admin-stack-modal: 1000; - --tilavaraus-admin-stack-seranwrap: 900; - --tilavaraus-admin-stack-dialog: 800; --tilavaraus-admin-stack-button-stripe: 799; /* one under hds dialog backdrop */ --tilavaraus-admin-stack-notification: 801; - --tilavaraus-admin-stack-sticky-header: 700; --tilavaraus-admin-stack-popup-menu: 501; --tilavaraus-admin-stack-main-menu: 500; --tilavaraus-admin-stack-sticky-reservation-header: 101; - --tilavaraus-admin-stack-sticky-filters: 5; - --tilavaraus-admin-stack-calendar-buffer: 2; --tilavaraus-admin-stack-select-over-calendar: 12; --tilavaraus-admin-stack-calendar-header-times: 11; --tilavaraus-admin-stack-calendar-header-names: 10; --tilavaraus-stack-order-calendar-gutter: 9; + --tilavaraus-admin-stack-calendar-buffer: 2; + --tilavaraus-calendar-selected: var(--color-bus); --tilavaraus-calendar-selected-secondary: #ffe977; diff --git a/apps/ui/components/application/ApprovedReservations.tsx b/apps/ui/components/application/ApprovedReservations.tsx index b8aab3042c..d27a4e7e1b 100644 --- a/apps/ui/components/application/ApprovedReservations.tsx +++ b/apps/ui/components/application/ApprovedReservations.tsx @@ -398,7 +398,6 @@ function ReservationUnitTable({ }, ]; - const getTranslation = ( elem: ModalT | null, field: "name" | "reservationConfirmedInstructions" diff --git a/apps/ui/styles/variables.css b/apps/ui/styles/variables.css index 35364a1306..42dfd164c7 100644 --- a/apps/ui/styles/variables.css +++ b/apps/ui/styles/variables.css @@ -20,13 +20,13 @@ --tilavaraus-event-initial-border: var(--color-bus); --tilavaraus-event-reservation-color: #f9e9e8; --tilavaraus-event-reservation-border: var(--color-error); - --tilavaraus-stack-order-start-application-bar: 20; - --tilavaraus-stack-order-modal: 200; + --tilavaraus-stack-order-modal: 1000; --tilavaraus-stack-order-toast: 180; --tilavaraus-stack-order-sticky-container: 150; --tilavaraus-stack-order-tooltip: 100; --tilavaraus-stack-order-navigation: 100; --tilavaraus-stack-order-calendar-gutter: 101; + --tilavaraus-stack-order-start-application-bar: 20; --tilavaraus-page-max-width: 1200px; } diff --git a/packages/common/package.json b/packages/common/package.json index 29ffb71938..ca752c1fe3 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -21,6 +21,7 @@ "classnames": "^2.5.1", "date-fns": "^4.1.0", "eslint-config-custom": "workspace:*", + "focus-trap-react": "^10.2.3", "hds-react": "^3.11.0", "i18next": "^23.15.1", "lodash": "^4.17.21", diff --git a/apps/admin-ui/src/component/PopupMenu.tsx b/packages/common/src/components/PopupMenu.tsx similarity index 93% rename from apps/admin-ui/src/component/PopupMenu.tsx rename to packages/common/src/components/PopupMenu.tsx index 4a6dc2b620..69aa96a05d 100644 --- a/apps/admin-ui/src/component/PopupMenu.tsx +++ b/packages/common/src/components/PopupMenu.tsx @@ -3,8 +3,9 @@ import React, { useEffect, useRef, useState } from "react"; import styled from "styled-components"; import FocusTrap from "focus-trap-react"; import ReactDOM from "react-dom"; +import { Flex } from "../../styles/util"; -interface IProps { +interface PopupMenuProps { items: { name: string; disabled?: boolean; @@ -26,16 +27,15 @@ const MenuIcon = styled(IconMenuDots)` `; const Container = styled.div` - margin-left: auto; position: relative; `; -const Popup = styled.div` +const Popup = styled(Flex).attrs({ $gap: "none" })` display: flex; flex-direction: column; background-color: white; padding: 0; - z-index: var(--tilavaraus-admin-stack-popup-menu); + z-index: var(--tilavaraus-stack-popup-menu); border: 1px solid var(--color-black-50); :not(:has(> button:disabled)) { @@ -78,7 +78,7 @@ const Overlay = styled.div` // - the popup is forced to open on the left side so using it on the left of a page would cause an overflow // - the popup will expand containers the buttons are inside of (like , not the cell) // These seem to be ok for this use case, but for others would need some more work. -export function PopupMenu({ items }: IProps): JSX.Element { +export function PopupMenu({ items }: PopupMenuProps): JSX.Element { const buttonRef = useRef(null); const firstMenuItemRef = useRef(null); @@ -132,7 +132,7 @@ function PopupContent({ closePopup, firstMenuItemRef, }: { - items: IProps["items"]; + items: PopupMenuProps["items"]; closePopup: () => void; firstMenuItemRef: React.RefObject; }) { diff --git a/packages/common/styles/variables.css b/packages/common/styles/variables.css index 50af3f0a25..7d85dbb7fc 100644 --- a/packages/common/styles/variables.css +++ b/packages/common/styles/variables.css @@ -6,4 +6,5 @@ /* the colour is not a variable in hds, but it's the visited link colour in their component */ --link-visited-color: #551a8b; --spacing-none: 0; + --tilavaraus-stack-popup-menu: 501; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41a7ec516d..64eba8b3c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,9 +71,6 @@ importers: eslint-config-custom: specifier: workspace:* version: link:../../packages/eslint-config-custom - focus-trap-react: - specifier: ^10.2.3 - version: 10.2.3(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) form-data: specifier: ^4.0.0 version: 4.0.0 @@ -409,6 +406,9 @@ importers: eslint-config-custom: specifier: workspace:* version: link:../eslint-config-custom + focus-trap-react: + specifier: ^10.2.3 + version: 10.2.3(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) hds-react: specifier: ^3.11.0 version: 3.11.0(@types/react@18.2.7)(eslint@8.57.1)(graphql-ws@5.16.0(graphql@16.9.0))(postcss@8.4.45)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)