diff --git a/apps/Administration-FE/scripts/check-contract-drift.mjs b/apps/Administration-FE/scripts/check-contract-drift.mjs index b14fde0..61c1e4d 100644 --- a/apps/Administration-FE/scripts/check-contract-drift.mjs +++ b/apps/Administration-FE/scripts/check-contract-drift.mjs @@ -23,6 +23,13 @@ const required = [ 'admin-cloud-services.v1.ts', 'admin-data-analytics.v1.ts', 'admin-digital-transformation.v1.ts', + 'admin-devops-consult.v1.ts', + 'admin-infrastructure-management.v1.ts', + 'admin-application-development.v1.ts', + 'admin-application-modernization.v1.ts', + 'admin-mobile-application-development.v1.ts', + 'admin-user-centered-design.v1.ts', + 'admin-overview.v1.ts', ]; let failed = false; diff --git a/apps/Administration-FE/scripts/check-i18n.mjs b/apps/Administration-FE/scripts/check-i18n.mjs index deeda43..a64d733 100644 --- a/apps/Administration-FE/scripts/check-i18n.mjs +++ b/apps/Administration-FE/scripts/check-i18n.mjs @@ -16,6 +16,8 @@ const files = [ 'components/AuthorsList.tsx', 'components/AuthorForm.tsx', 'components/MediaPreview.tsx', + 'components/MediaField.tsx', + 'components/FormPageHeader.tsx', 'components/CategoriesList.tsx', 'components/CategoryForm.tsx', 'components/ClientLogosList.tsx', diff --git a/apps/Administration-FE/scripts/generate-client.mjs b/apps/Administration-FE/scripts/generate-client.mjs index 89dd09a..2302f96 100644 --- a/apps/Administration-FE/scripts/generate-client.mjs +++ b/apps/Administration-FE/scripts/generate-client.mjs @@ -30,6 +30,28 @@ const digitalTransformationContractsDir = join( repoRoot, 'specs/015-admin-digital-transformation/contracts', ); +const devopsConsultContractsDir = join(repoRoot, 'specs/016-admin-devops-consult/contracts'); +const infrastructureManagementContractsDir = join( + repoRoot, + 'specs/017-admin-infrastructure-management/contracts', +); +const applicationDevelopmentContractsDir = join( + repoRoot, + 'specs/018-admin-application-development/contracts', +); +const applicationModernizationContractsDir = join( + repoRoot, + 'specs/019-admin-application-modernization/contracts', +); +const mobileApplicationDevelopmentContractsDir = join( + repoRoot, + 'specs/020-admin-mobile-application-development/contracts', +); +const userCenteredDesignContractsDir = join( + repoRoot, + 'specs/021-admin-user-centered-design/contracts', +); +const overviewContractsDir = join(repoRoot, 'specs/022-admin-overview/contracts'); const contractSets = [ { @@ -90,6 +112,34 @@ const contractSets = [ dir: digitalTransformationContractsDir, files: ['admin-digital-transformation.v1.yaml'], }, + { + dir: devopsConsultContractsDir, + files: ['admin-devops-consult.v1.yaml'], + }, + { + dir: infrastructureManagementContractsDir, + files: ['admin-infrastructure-management.v1.yaml'], + }, + { + dir: applicationDevelopmentContractsDir, + files: ['admin-application-development.v1.yaml'], + }, + { + dir: applicationModernizationContractsDir, + files: ['admin-application-modernization.v1.yaml'], + }, + { + dir: mobileApplicationDevelopmentContractsDir, + files: ['admin-mobile-application-development.v1.yaml'], + }, + { + dir: userCenteredDesignContractsDir, + files: ['admin-user-centered-design.v1.yaml'], + }, + { + dir: overviewContractsDir, + files: ['admin-overview.v1.yaml'], + }, ]; mkdirSync(outDir, { recursive: true }); diff --git a/apps/Administration-FE/src/components/AdminShell.tsx b/apps/Administration-FE/src/components/AdminShell.tsx index 8675018..6b62367 100644 --- a/apps/Administration-FE/src/components/AdminShell.tsx +++ b/apps/Administration-FE/src/components/AdminShell.tsx @@ -55,6 +55,9 @@ import DataAnalyticsList from './DataAnalyticsList'; import DataAnalyticsForm from './DataAnalyticsForm'; import DigitalTransformationList from './DigitalTransformationList'; import DigitalTransformationForm from './DigitalTransformationForm'; +import LandingList from './LandingList'; +import LandingForm from './LandingForm'; +import { LANDING_SECTIONS, isLandingFormView, landingByView } from '../lib/landing-sections'; type View = AdminView; @@ -92,6 +95,7 @@ function applyRoute( setEditingCloudServiceId: (id: string | null) => void; setEditingDataAnalyticId: (id: string | null) => void; setEditingDigitalTransformationId: (id: string | null) => void; + setEditingLandingId: (id: string | null) => void; }, ) { setters.setView(route.view); @@ -121,6 +125,7 @@ function applyRoute( setters.setEditingDigitalTransformationId( route.view === 'digital_transformation_form' ? route.editingId : null, ); + setters.setEditingLandingId(isLandingFormView(route.view) ? route.editingId : null); } export default function AdminShell() { @@ -205,6 +210,10 @@ export default function AdminShell() { const route = readAdminLocation(); return route.view === 'digital_transformation_form' ? route.editingId : null; }); + const [editingLandingId, setEditingLandingId] = useState(() => { + const route = readAdminLocation(); + return isLandingFormView(route.view) ? route.editingId : null; + }); const [session, setSession] = useState(null); const [siteSettings, setSiteSettings] = useState | null>(null); const [message, setMessage] = useState(null); @@ -261,6 +270,7 @@ export default function AdminShell() { setEditingCloudServiceId, setEditingDataAnalyticId, setEditingDigitalTransformationId, + setEditingLandingId, }); }, []); @@ -358,6 +368,11 @@ export default function AdminShell() { document.title = t('admin.workspace.digital_transformation'); return; } + const landing = landingByView(view); + if (landing) { + document.title = t(`admin.workspace.${landing.resource}`); + return; + } if (view === 'blogs' || view === 'blog_form') { document.title = t('admin.workspace.blogs'); return; @@ -574,6 +589,15 @@ export default function AdminShell() { const canPublishCloudServices = hasPermission(session, 'cloud_services.publish'); const canPublishDataAnalytics = hasPermission(session, 'data_analytics.publish'); const canPublishDigitalTransformation = hasPermission(session, 'digital_transformation.publish'); + const canReadLandings = Object.fromEntries( + LANDING_SECTIONS.map((section) => [section.resource, canReadResource(session, section.resource)]), + ); + const canPublishLandings = Object.fromEntries( + LANDING_SECTIONS.map((section) => [ + section.resource, + hasPermission(session, `${section.resource}.publish`), + ]), + ); const canManageRoles = canManageRolesFrom(session); const settingsCurrent = view === 'roles' || view === 'role_form'; const initial = session.email.slice(0, 1).toUpperCase(); @@ -607,6 +631,7 @@ export default function AdminShell() { canReadCloudServices || canReadDataAnalytics || canReadDigitalTransformation || + LANDING_SECTIONS.some((section) => canReadLandings[section.resource]) || canReadBlogs || canReadCaseStudies || canReadIndustries || @@ -787,6 +812,25 @@ export default function AdminShell() { )} + {LANDING_SECTIONS.map( + (section) => + canReadLandings[section.resource] && ( +
  • + + onNavClick(event, adminListHref(section.listView as AdminView)) + } + > + {t(`admin.workspace.${section.resource}`)} + +
  • + ), + )} {canReadBlogs && (
  • )} + {LANDING_SECTIONS.map((section) => + view === section.listView ? ( + openForm(section.listView as AdminView, null)} + onEdit={(id) => openForm(section.listView as AdminView, id)} + /> + ) : view === section.formView ? ( + openList(section.listView as AdminView)} + onSaved={() => + afterListSave( + () => openList(section.listView as AdminView), + t(`${section.ns}.saved`), + ) + } + /> + ) : null, + )} {view === 'blogs' && ( -
    -

    {entryId ? t('admin.ai_services.edit') : t('admin.ai_services.add')}

    -
    +
    { event.preventDefault(); @@ -238,18 +241,16 @@ export default function AiServiceForm({ entryId, canPublish, onCancel, onSaved } autoComplete="off" /> - - { + setBannerFile(null); + setBannerKey(null); + }} /> - - + setIndustryItems((rows) => + rows.map((row, rowIndex) => + rowIndex === index ? { ...row, imageFile: file } : row, + ), + ) + } + onClear={() => + setIndustryItems((rows) => + rows.map((row, rowIndex) => + rowIndex === index ? { ...row, imageFile: null, image_key: null } : row, + ), + ) + } /> )} @@ -362,18 +362,16 @@ export default function AiServiceForm({ entryId, canPublish, onCancel, onSaved } autoComplete="off" /> - - { + setExpertiseFile(null); + setExpertiseKey(null); + }} /> -
    -

    {authorId ? t('admin.authors.edit') : t('admin.authors.add')}

    -
    +