refactor: migrate project data

This commit is contained in:
Carlos Valente
2025-07-08 17:11:25 +02:00
committed by Carlos Valente
parent 1fc6f9b3ea
commit 6f34e1006b
38 changed files with 136 additions and 228 deletions
@@ -103,7 +103,7 @@ export default function Backstage(props: BackstageProps) {
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
<ViewParamsEditor viewOptions={backstageOptions} />
<div className='project-header'>
{general?.projectLogo && <ViewLogo name={general.projectLogo} className='logo' />}
{general?.logo && <ViewLogo name={general.logo} className='logo' />}
<div className='title'>{general.title}</div>
<div className='clock-container'>
<div className='label'>{getLocalizedString('common.time_now')}</div>
@@ -171,8 +171,8 @@ export default function Backstage(props: BackstageProps) {
{showSchedule && <ScheduleExport selectedId={selectedId} />}
<div className={cx(['info', !showSchedule && 'info--stretch'])}>
{general.backstageUrl && <QRCode value={general.backstageUrl} size={qrSize} level='L' className='qr' />}
{general.backstageInfo && <div className='info__message'>{general.backstageInfo}</div>}
{general.url && <QRCode value={general.url} size={qrSize} level='L' className='qr' />}
{general.info && <div className='info__message'>{general.info}</div>}
</div>
</div>
);
@@ -72,7 +72,7 @@ export default function Countdown({
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
<ViewParamsEditor viewOptions={countdownOptions} />
<div className='project-header'>
{general?.projectLogo && <ViewLogo name={general.projectLogo} className='logo' />}
{general?.logo && <ViewLogo name={general.logo} className='logo' />}
<div className='title'>{general.title}</div>
<div className='clock-container'>
<div className='label'>{getLocalizedString('common.time_now')}</div>
@@ -38,7 +38,7 @@ describe('makeTable()', () => {
const headerData = {
title: 'test title',
description: 'test description',
projectLogo: 'test logo',
logo: 'test logo',
};
const tableData = [
{
@@ -41,7 +41,10 @@
white-space: break-spaces;
}
a.info__value {
.link.info__value {
display: flex;
gap: 0.5rem;
align-items: center;
color: $action-text-color;
&:hover {
@@ -1,37 +1,30 @@
import { ProjectData } from 'ontime-types';
import { Fragment } from 'react/jsx-runtime';
import { IoOpenOutline } from 'react-icons/io5';
import Empty from '../../common/components/state/Empty';
import ViewNavigationMenu from '../../common/components/navigation-menu/ViewNavigationMenu';
import EmptyPage from '../../common/components/state/EmptyPage';
import ViewLogo from '../../common/components/view-logo/ViewLogo';
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
import useProjectData from '../../common/hooks-query/useProjectData';
import { useViewOptionsStore } from '../../common/stores/viewOptions';
import { useTranslation } from '../../translation/TranslationProvider';
import BackstageInfo from './backstage-info/BackstageInfo';
import CustomInfo from './custom-info/CustomInfo';
import { projectInfoOptions } from './projectInfo.options';
import './ProjectInfo.scss';
interface ProjectInfoProps {
general: ProjectData;
isMirrored: boolean;
}
export default function ProjectInfo(props: ProjectInfoProps) {
const { general, isMirrored } = props;
export default function ProjectInfo() {
// persisted app state
const isMirrored = useViewOptionsStore((state) => state.mirror);
const { data, status } = useProjectData();
const { getLocalizedString } = useTranslation();
useWindowTitle('Project info');
if (!general) {
return <Empty text={getLocalizedString('common.no_data')} />;
}
if (!general) {
if (status === 'pending' || !data) {
return (
<>
<ViewParamsEditor viewOptions={projectInfoOptions} />
<ViewNavigationMenu isLockable suppressSettings />
<ViewParamsEditor viewOptions={[]} />
<EmptyPage text={getLocalizedString('common.no_data')} />;
</>
);
@@ -41,13 +34,12 @@ export default function ProjectInfo(props: ProjectInfoProps) {
* Check if there is data to show at all
* We need a special check for the project fields which can be an empty array
*/
const isEmpty = Object.values(general).every(
(value) => !value || (value && Array.isArray(value) && value.length === 0),
);
const isEmpty = Object.values(data).every((value) => !value || (value && Array.isArray(value) && value.length === 0));
if (isEmpty) {
return (
<>
<ViewParamsEditor viewOptions={projectInfoOptions} />
<ViewNavigationMenu isLockable suppressSettings />
<ViewParamsEditor viewOptions={[]} />
<EmptyPage text={getLocalizedString('common.no_data')} />;
</>
);
@@ -55,23 +47,47 @@ export default function ProjectInfo(props: ProjectInfoProps) {
return (
<div className={`project ${isMirrored ? 'mirror' : ''}`} data-testid='project-view'>
<ViewParamsEditor viewOptions={projectInfoOptions} />
{general.projectLogo && <ViewLogo name={general.projectLogo} className='logo' />}
<ViewNavigationMenu isLockable suppressSettings />
<ViewParamsEditor viewOptions={[]} />
{data.logo && <ViewLogo name={data.logo} className='logo' />}
<div className='info'>
{general.title && (
{data.title && (
<>
<div className='info__label'>{getLocalizedString('project.title')}</div>
<div className='info__value'>{general.title}</div>
<div className='info__value'>{data.title}</div>
</>
)}
{general.description && (
{data.description && (
<>
<div className='info__label'>{getLocalizedString('project.description')}</div>
<div className='info__value'>{general.description}</div>
<div className='info__value'>{data.description}</div>
</>
)}
<BackstageInfo general={general} />
<CustomInfo general={general} />
{data.info && (
<>
<div className='info__label'>{getLocalizedString('project.info')}</div>
<div className='info__value'>{data.info}</div>
</>
)}
{data.url && (
<>
<div className='info__label'>{getLocalizedString('project.url')}</div>
<a href={data.url} target='_blank' rel='noreferrer' className='info__value link'>
{data.url} <IoOpenOutline style={{ fontSize: '1em' }} />
</a>
</>
)}
{data.custom.map((info, idx) => {
if (!info.title || !info.value) {
return null;
}
return (
<Fragment key={`${info.title}-${idx}`}>
<div className='info__label'>{info.title}</div>
<div className='info__value'>{info.value}</div>
</Fragment>
);
})}
</div>
</div>
);
@@ -1,40 +0,0 @@
import { useSearchParams } from 'react-router-dom';
import { ProjectData } from 'ontime-types';
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
interface BackstageInfoProps {
general: ProjectData;
}
export default function BackstageInfo(props: BackstageInfoProps) {
const { general } = props;
const [searchParams] = useSearchParams();
const { getLocalizedString } = useTranslation();
const showBackstage = isStringBoolean(searchParams.get('showBackstage'));
if (!showBackstage) {
return null;
}
return (
<>
{general.backstageInfo && (
<>
<div className='info__label'>{getLocalizedString('project.backstage_info')}</div>
<div className='info__value'>{general.backstageInfo}</div>
</>
)}
{general.backstageUrl && (
<>
<div className='info__label'>{getLocalizedString('project.backstage_url')}</div>
<a href={general.backstageUrl} target='_blank' rel='noreferrer' className='info__value'>
{general.backstageUrl}
</a>
</>
)}
</>
);
}
@@ -1,36 +0,0 @@
import { Fragment } from 'react';
import { useSearchParams } from 'react-router-dom';
import { ProjectData } from 'ontime-types';
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
interface CustomInfoProps {
general: ProjectData;
}
export default function CustomInfo(props: CustomInfoProps) {
const { general } = props;
const [searchParams] = useSearchParams();
const showCustom = isStringBoolean(searchParams.get('showCustom'));
if (!showCustom || general.custom === undefined || general.custom.length === 0) {
return null;
}
return (
<>
{general.custom.map((info, idx) => {
if (!info.title || !info.value) {
return null;
}
return (
<Fragment key={`${info.title}-${idx}`}>
<div className='info__label'>{info.title}</div>
<div className='info__value'>{info.value}</div>
</Fragment>
);
})}
</>
);
}
@@ -1,25 +0,0 @@
import { OptionTitle } from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
export const projectInfoOptions: ViewOption[] = [
{
title: OptionTitle.BehaviourOptions,
collapsible: true,
options: [
{
id: 'showBackstage',
title: 'Show backstage Data',
description: 'Whether to show fields related to the backstage views',
type: 'boolean',
defaultValue: false,
},
{
id: 'showCustom',
title: 'Show Custom Data',
description: 'Whether to show fields related to the custom data',
type: 'boolean',
defaultValue: false,
},
],
},
];
+1 -1
View File
@@ -50,7 +50,7 @@ export default function Studio({
<ViewParamsEditor viewOptions={studioOptions} />
<div className='project-header'>
{general?.projectLogo && <ViewLogo name={general.projectLogo} className='logo' />}
{general?.logo && <ViewLogo name={general.logo} className='logo' />}
<div className='title'>{general.title}</div>
</div>
@@ -75,7 +75,7 @@ export default function TimelinePage(props: TimelinePageProps) {
<div className='timeline' data-testid='timeline-view'>
<ViewParamsEditor viewOptions={progressOptions} />
<div className='project-header'>
{general?.projectLogo && <ViewLogo name={general.projectLogo} className='logo' />}
{general?.logo && <ViewLogo name={general.logo} className='logo' />}
{general.title}
<div className='clock-container'>
<div className='label'>{getLocalizedString('common.time_now')}</div>
+1 -1
View File
@@ -131,7 +131,7 @@ export default function Timer(props: TimerProps) {
className={cx(['stage-timer', isMirrored && 'mirror', showFinished && 'stage-timer--finished'])}
data-testid='timer-view'
>
{general?.projectLogo && <ViewLogo name={general.projectLogo} className='logo' />}
{general?.logo && <ViewLogo name={general.logo} className='logo' />}
<ViewParamsEditor viewOptions={timerOptions} />