Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 5379 - Pin types in tickets #5400

Merged
merged 10 commits into from
Feb 20, 2025
32 changes: 32 additions & 0 deletions frontend/assets/icons/filled/pin_issue-filled.svg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (C) 2025 3D Repo Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/


type IProps = {
className?: any;
};

export default ({ className }: IProps) => (
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<ellipse id="selectionFill" strokeWidth="0" fill="#000000" visibility="hidden" cx="9.7955208" cy="8.7244406" rx="5.6085687" ry="5.5306721" />
<path
id="main"
fill="currentColor"
d="m 9.0472506,-0.05845933 c -2.1953879,0 -4.2648738,0.83648718 -5.8203128,2.35351563 -1.550518,1.5148385 -2.40820298,3.5310865 -2.40820298,5.6777344 0,3.2612473 1.58042498,5.9740053 3.31445298,7.9707033 1.736799,1.999798 3.6811205,3.344473 4.5371096,3.888672 0.2302098,0.1464 0.5236965,0.1464 0.7539063,0 0.8559693,-0.544199 2.8003113,-1.888874 4.5371093,-3.888672 1.733998,-1.996698 3.314453,-4.709456 3.314453,-7.9707033 0,-2.1464879 -0.859258,-4.1631928 -2.410156,-5.6757812 -1.555498,-1.51962752 -3.623131,-2.35546883 -5.8183594,-2.35546883 z m -0.00586,4.67578123 c 2.0351604,0 3.6835934,1.6484517 3.6835934,3.6835938 0,2.0350873 -1.648433,3.6855473 -3.6835934,3.6855473 -2.035133,0 -3.6855474,-1.65046 -3.6855474,-3.6855473 1e-6,-2.0351421 1.6504144,-3.6835938 3.6855474,-3.6835938 z"
/>
</svg>
);
34 changes: 34 additions & 0 deletions frontend/assets/icons/filled/pin_marker-filled.svg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright (C) 2023 3D Repo Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

type IProps = {
className?: any;
};

export default ({ className }: IProps) => (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
id="main"
d="m 10.672,0.109375 c -3.2955383,3e-8 -5.967326,2.6712586 -5.9669219,5.9667969 C 4.7110313,8.9629326 6.6524586,11.431315 9.4945,11.9375 v 7.134766 L 10.693359,19.898438 11.85,18.892578 V 11.894531 C 14.572219,11.282898 16.637461,8.8662557 16.638672,6.0761719 16.639076,2.7806336 13.967538,0.10937497 10.672,0.109375 Z"
fill="currentColor"/>

<path
d="m 10.977607,3.1040296 c 1.996699,0 2.928475,1.4097868 2.928475,2.5809942"
fill="#ffffffcc"
/>
</svg>
);
31 changes: 31 additions & 0 deletions frontend/assets/icons/filled/pin_risk-filled.svg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (C) 2025 3D Repo Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

type IProps = {
className?: any;
};

export default ({ className }: IProps) => (
<svg width="69" height="64" viewBox="0 0 69 64" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<ellipse id="selectionFill" strokeWidth="0" fill="#000000" visibility="hidden" cx="35.747791" cy="28.091486" rx="14.706719" ry="23.134165" />
<path
id="main"
fill="currentColor"
d="M 4.7423497,0.06085732 C 1.1279459,0.1822271 -1.1319948,4.0945447 0.62924934,7.2993732 L 30.435698,61.527356 c 1.857352,3.379836 6.712781,3.376189 8.564448,-0.0069 L 68.684827,7.292608 C 70.466875,4.0371175 68.113912,0.06086078 64.402603,0.06085732 H 4.9114742 c -0.058048,0 -0.1117517,-0.0019265 -0.1691245,0 z M 34.657038,7.5699719 c 0.819883,-4.849e-4 1.627024,0.1898642 2.360975,0.5547272 0.7336,0.3648274 1.373903,0.8958621 1.867129,1.5491776 2.032132,2.5301693 2.602639,6.8861863 1.542415,11.6695693 -1.337667,6.029609 -3.435633,13.211981 -5.770519,13.211981 -2.334888,0 -4.432679,-7.183328 -5.770517,-13.22551 -1.060192,-4.783384 -0.489578,-9.138479 1.542412,-11.6560403 0.491078,-0.6554976 1.126105,-1.1908726 1.860366,-1.5559424 0.734227,-0.3650698 1.547337,-0.551426 2.367739,-0.5479624 z m 0,31.4706291 c 0.540366,-0.0066 1.074352,0.09102 1.576237,0.290913 0.501885,0.2002 0.959338,0.49618 1.346229,0.87268 0.386545,0.376154 0.700256,0.823864 0.913271,1.319168 0.213015,0.495304 0.324891,1.030526 0.331472,1.569472 -3.46e-4,0.797336 -0.240967,1.57488 -0.683263,2.239204 -0.442303,0.66433 -1.069524,1.185407 -1.806245,1.495059 -0.736721,0.309305 -1.548528,0.398896 -2.333913,0.250319 -0.785454,-0.148245 -1.511414,-0.525438 -2.08361,-1.082395 -0.572162,-0.556957 -0.961727,-1.270462 -1.12975,-2.049786 -0.168022,-0.779671 -0.102739,-1.59165 0.189418,-2.333913 0.29216,-0.741917 0.792794,-1.382522 1.447703,-1.840072 0.654908,-0.457203 1.433269,-0.710529 2.232438,-0.730618 z"
/>
</svg>
);
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type IProps = {

export default ({ className }: IProps) => (
<svg width="50" height="64" viewBox="0 0 50 64" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path id="selectionFill" stroke-width="0" d="M 7.3534566,14.021421 H 42.562804 V 26.360273 H 6.6056475 V 13.959104 Z" fill="0"/>
<path fillRule="evenodd" clipRule="evenodd" d="M0 8C0 3.58172 3.58172 0 8 0H42C46.4183 0 50 3.58172 50 8V41C50 45.4183 46.4183 49 42 49H32L26 64L20 49H8C3.58172 49 0 45.4183 0 41V8ZM10.0002 14.5C10.0002 13.1193 11.1195 12 12.5002 12H37.5002C38.8809 12 40.0002 13.1193 40.0002 14.5C40.0002 15.8807 38.8809 17 37.5002 17H12.5002C11.1195 17 10.0002 15.8807 10.0002 14.5ZM16.0294 23.9999C14.6487 23.9999 13.5294 25.1192 13.5294 26.4999C13.5294 27.8806 14.6487 28.9999 16.0294 28.9999H33.9706C35.3513 28.9999 36.4706 27.8806 36.4706 26.4999C36.4706 25.1192 35.3513 23.9999 33.9706 23.9999H16.0294Z" fill="currentColor"/>
<path id="selectionFill" stroke="black" strokeWidth="7" d="M 7.3534566,14.021421 H 42.562804 V 26.360273 H 6.6056475 V 13.959104 Z" fill="0" visibility="hidden"/>
<path id="main" fillRule="evenodd" clipRule="evenodd" d="M0 8C0 3.58172 3.58172 0 8 0H42C46.4183 0 50 3.58172 50 8V41C50 45.4183 46.4183 49 42 49H32L26 64L20 49H8C3.58172 49 0 45.4183 0 41V8ZM10.0002 14.5C10.0002 13.1193 11.1195 12 12.5002 12H37.5002C38.8809 12 40.0002 13.1193 40.0002 14.5C40.0002 15.8807 38.8809 17 37.5002 17H12.5002C11.1195 17 10.0002 15.8807 10.0002 14.5ZM16.0294 23.9999C14.6487 23.9999 13.5294 25.1192 13.5294 26.4999C13.5294 27.8806 14.6487 28.9999 16.0294 28.9999H33.9706C35.3513 28.9999 36.4706 27.8806 36.4706 26.4999C36.4706 25.1192 35.3513 23.9999 33.9706 23.9999H16.0294Z" fill="currentColor"/>
</svg>
);
4 changes: 3 additions & 1 deletion frontend/src/v4/services/viewer/viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ interface IViewerConstructor {
name?: string;
}

export type PinType = 'issue' | 'risk' | 'bookmark' | 'ticket' | null;

export interface IPin {
id: string;
type?: 'issue' | 'risk' | 'bookmark' | 'ticket' | null;
type?: PinType
position: number[];
norm?: number[];
colour: number[];
Expand Down
44 changes: 39 additions & 5 deletions frontend/src/v5/services/api/tickets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,53 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ITicket, ITemplate, NewTicket, Group } from '@/v5/store/tickets/tickets.types';
import { ITicket, ITemplate, NewTicket, Group, PinIcon, PropertyDefinition } from '@/v5/store/tickets/tickets.types';
import api from './default';
import { isObject } from 'lodash';

export const modelType = (isFed: boolean) => (isFed ? 'federations' : 'containers');

/*************** TEMPORAL CODE FOR MOCKING PIN ICONS *****************/
const templates = {};
const pinIcons = [ 'DEFAULT', 'ISSUE', 'RISK', 'MARKER'];
let pinIndex = 0;

const getCoords = (properties: PropertyDefinition[]) => (properties || []).filter((p) => p.type === 'coords');

const fillDummyIconInProp = (prop) => ( { ...prop, icon: pinIcons[(pinIndex++) % 4] });

const fillDummyPinIcon = (template:ITemplate): ITemplate => {
if (templates[template._id]) return templates[template._id];


if (template.config.pin) {
if (isObject(template.config.pin)) {
template.config.pin.icon = pinIcons[(pinIndex++) % 4] as PinIcon;
} else {
template.config.pin = fillDummyIconInProp(template.config.pin);
}
}

getCoords(template.properties).forEach((p) => Object.assign(p, fillDummyIconInProp(p)));

template.modules.forEach((module) => {
getCoords(module.properties).forEach((p) => Object.assign(p, fillDummyIconInProp(p)));
});

return template;
};

/*********************************************************************/


export const fetchContainerTemplates = async (
teamspace: string,
projectId: string,
containerId: string,
getDetails?: boolean,
): Promise<FetchTemplatesResponse> => {
const { data } = await api.get(`teamspaces/${teamspace}/projects/${projectId}/containers/${containerId}/tickets/templates?getDetails=${getDetails}`);
return data.templates;
return data.templates.map(fillDummyPinIcon);
};

export const fetchContainerTemplate = async (
Expand All @@ -36,7 +70,7 @@ export const fetchContainerTemplate = async (
templateId:string,
): Promise<ITemplate> => {
const { data } = await api.get(`teamspaces/${teamspace}/projects/${projectId}/containers/${containerId}/tickets/templates/${templateId}`);
return data;
return fillDummyPinIcon(data);
};

export const fetchFederationTemplates = async (
Expand All @@ -46,7 +80,7 @@ export const fetchFederationTemplates = async (
getDetails?: boolean,
): Promise<FetchTemplatesResponse> => {
const { data } = await api.get(`teamspaces/${teamspace}/projects/${projectId}/federations/${federationId}/tickets/templates?getDetails=${getDetails}`);
return data.templates;
return data.templates.map(fillDummyPinIcon);
};

export const fetchFederationTemplate = async (
Expand All @@ -56,7 +90,7 @@ export const fetchFederationTemplate = async (
templateId: string,
): Promise<ITemplate> => {
const { data } = await api.get(`teamspaces/${teamspace}/projects/${projectId}/federations/${federationId}/tickets/templates/${templateId}`);
return data;
return fillDummyPinIcon(data);
};

export const fetchContainerTickets = async (
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { SequencingProperties, TicketsCardViews } from '@/v5/ui/routes/viewer/ti
import { createSelector } from 'reselect';
import { selectTemplateById, selectTemplates, selectTicketById, selectTickets } from '../tickets.selectors';
import { ITicketsCardState } from './ticketsCard.redux';
import { DEFAULT_PIN, getPinColorHex, formatPin, getTicketPins } from '@/v5/ui/routes/viewer/tickets/ticketsForm/properties/coordsProperty/coordsProperty.helpers';
import { DEFAULT_PIN, getTicketPins, toPin } from '@/v5/ui/routes/viewer/tickets/ticketsForm/properties/coordsProperty/coordsProperty.helpers';
import { IPin } from '@/v4/services/viewer/viewer';
import { selectSelectedDate } from '@/v4/modules/sequences';
import { ticketIsCompleted } from '@controls/chip/statusChip/statusChip.helpers';
Expand Down Expand Up @@ -176,9 +176,7 @@ export const selectTicketPins = createSelector(
(accum, ticket) => {
const pin = ticket.properties?.Pin;
if (!pin) return accum;
const template = templates.find(({ _id }) => _id === ticket.type);
const color = getPinColorHex(DEFAULT_PIN, template, ticket);


const { sequencing } = ticket.modules;

if (sequencing && selectedSequenceDate) {
Expand All @@ -189,8 +187,10 @@ export const selectTicketPins = createSelector(
endDate && new Date(endDate) < new Date(selectedSequenceDate)
) return accum;
}

const template = templates.find(({ _id }) => _id === ticket.type);
const isSelected = selectedTicketPinId === ticket._id;
return [...accum, formatPin(ticket._id, pin, isSelected, color)];
return [...accum, toPin(DEFAULT_PIN, template, ticket, isSelected)];
},
[],
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/v5/store/tickets/tickets.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const sortTicketsByCreationDate = (tickets: any[]) => orderBy(tickets, `p

const getTemplateDefaultStatus = (template: ITemplate) => template.properties?.find(({ name }) => name === BaseProperties.STATUS)?.default;

export const getTicketWithStatus = (ticket: ITicket, template: ITemplate) => {
const getTicketWithStatus = (ticket: ITicket, template: ITemplate) => {
if (ticket.properties[BaseProperties.STATUS] || !template) return ticket;
return {
...ticket,
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/v5/store/tickets/tickets.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,12 @@ export type IPinColorMapping = {
]
};

export type IPinSchema = {
name: string;
type: 'coords';
color: RgbArray | IPinColorMapping;
export type PinIcon = 'DEFAULT' | 'RISK' | 'ISSUE' | 'MARKER';

export type PinConfig = {
name?: string;
color?: RgbArray | IPinColorMapping;
icon?: PinIcon;
};

export type StatusValue = {
Expand All @@ -96,7 +98,7 @@ export interface ITemplate {
comments?: boolean;
defaultView?: boolean;
issueProperties?: boolean;
pin?: boolean | IPinSchema;
pin?: boolean | PinConfig;
status?: IStatusConfig;
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@
*/

import { IPin } from '@/v4/services/viewer/viewer';
import PinIcon from '@assets/icons/filled/ticket_pin-filled.svg';
import { PinContainer } from './pin2D.styles';
import { TicketsCardActionsDispatchers } from '@/v5/services/actionsDispatchers';
import { TicketsHooksSelectors } from '@/v5/services/selectorsHooks';
import { useParams } from 'react-router';
import { ViewerParams } from '@/v5/ui/routes/routes.constants';
import { Pin } from '@/v5/ui/routes/viewer/tickets/pin';
import { toPinIcon } from '@/v5/ui/routes/viewer/tickets/ticketsForm/properties/coordsProperty/coordsProperty.helpers';

type Pin2DProps = IPin & { scale: number };
export const Pin2D = ({ id, isSelected, position, colour, scale }: Pin2DProps) => {
export const Pin2D = ({ id, isSelected, position, colour, scale, type }: Pin2DProps) => {
const { containerOrFederation } = useParams<ViewerParams>();
const tickets = TicketsHooksSelectors.selectTickets(containerOrFederation);

Expand All @@ -43,7 +44,7 @@ export const Pin2D = ({ id, isSelected, position, colour, scale }: Pin2DProps) =
selected={isSelected}
style={{ transform: `translate(${position[0]}px, ${position[1]}px) scale(${0.333 / scale})` }}
>
<PinIcon />
<Pin pinIcon={toPinIcon(type)} />
</PinContainer>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,30 @@ const popupAnimation = keyframes`

export const PinContainer = styled.div<{ colour, selected?: boolean }>`
position: absolute;
color: rgb(${({ colour }) => colour.map((val) => Math.round(val * 256)).join()});


svg {
${({ selected }) => selected && css`
transform-origin: bottom center;
animation: ${popupAnimation} 1s forwards;

#selectionFill {
stroke-width: 7px;
}
transform-origin: bottom center;
animation: ${popupAnimation} 1s forwards;
#selectionFill {
visibility: visible;
}
`}
position: absolute;

left: -25px;
top: -64px;

#main {
color: rgb(${({ colour }) => colour.map((val) => Math.round(val * 256)).join()});
stroke: #000;
stroke-width: 2%;
}

stroke: #000;
stroke-width: 2%;
overflow: visible;

height: 64px;
width: 64px;
}
`;
35 changes: 35 additions & 0 deletions frontend/src/v5/ui/routes/viewer/tickets/pin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright (C) 2025 3D Repo Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import TicketPin from '@assets/icons/filled/pin_ticket-filled.svg';
import IssuePin from '@assets/icons/filled/pin_issue-filled.svg';
import RiskPin from '@assets/icons/filled/pin_risk-filled.svg';
import MarkerPin from '@assets/icons/filled/pin_marker-filled.svg';
import { PinIcon } from '@/v5/store/tickets/tickets.types';


const PinPerType =
{
'ISSUE': IssuePin,
'RISK': RiskPin,
'DEFAULT': TicketPin,
'MARKER': MarkerPin,
};

export const Pin = ({ pinIcon }: { pinIcon:PinIcon }) => {
const Icon = PinPerType[pinIcon];
return (<Icon />);
};
Loading
Loading