Zimo 3 днів тому
батько
коміт
f79d9f543b

+ 105 - 150
src/views/pms/operation-meeting/components/meeting-detail-drawer.vue

@@ -2,7 +2,7 @@
 import type { FormInstance, FormRules } from 'element-plus'
 import type { DetailItem, ExtPropertyItem } from '../types'
 import { OperationMeetingApi } from '@/api/pms/meeting'
-import { useDebounceFn, useWindowSize } from '@vueuse/core'
+import { useWindowSize } from '@vueuse/core'
 
 interface Props {
   visible: boolean
@@ -28,8 +28,6 @@ const emits = defineEmits<{
 const { width } = useWindowSize()
 const detailFormRef = ref<FormInstance>()
 const detailForm = ref<DetailItem>(createDetailItem())
-const previousWorkPlanQueryKey = ref('')
-const equipmentUtilizationRateRequired = ref(true)
 
 const detailDrawerSize = computed(() => (width.value <= 768 ? '100%' : '50%'))
 const detailDrawerTitle = computed(() => {
@@ -219,32 +217,6 @@ const nonNegativeNumberRule = (requiredMessage: string, fieldLabel: string) => [
   }
 ]
 
-const numberRangeRule = (requiredMessage: string, fieldLabel: string, min: number, max: number) => [
-  ...requiredNumberRule(requiredMessage),
-  {
-    type: 'number' as const,
-    min,
-    max,
-    message: `${fieldLabel}需在${min}到${max}之间`,
-    trigger: ['blur', 'change']
-  }
-]
-
-const optionalNumberRangeRule = (fieldLabel: string, min: number, max: number) => [
-  {
-    type: 'number' as const,
-    min,
-    max,
-    message: `${fieldLabel}需在${min}到${max}之间`,
-    trigger: ['blur', 'change']
-  }
-]
-
-const getEquipmentUtilizationRateRules = () =>
-  equipmentUtilizationRateRequired.value
-    ? numberRangeRule('请输入设备利用率', '设备利用率', 0, 100)
-    : optionalNumberRangeRule('设备利用率', 0, 100)
-
 const detailRules = reactive<FormRules>({
   projectName: requiredTextRule('请选择或者输入项目名称'),
   currentRevenue: nonNegativeNumberRule('请输入收入-本期', '收入-本期'),
@@ -253,9 +225,6 @@ const detailRules = reactive<FormRules>({
   cumulativeOnAccount: nonNegativeNumberRule('请输入挂帐-累计', '挂帐-累计'),
   currentPayment: nonNegativeNumberRule('请输入回款-本期', '回款-本期'),
   cumulativePayment: nonNegativeNumberRule('请输入回款-累计', '回款-累计'),
-  plannedWorkload: requiredTextRule('请输入计划工作量'),
-  actualCompletion: requiredTextRule('请输入实际完成'),
-  equipmentUtilizationRate: getEquipmentUtilizationRateRules(),
   keyWorkCompletion: requiredTextRule('请输入重点工作及完成情况'),
   problemsAnalysis: requiredTextRule('请输入存在问题及分析'),
   qhse: requiredTextRule('请输入设备安全管理工作'),
@@ -263,27 +232,80 @@ const detailRules = reactive<FormRules>({
   priorityTasks: requiredTextRule('请输入重点工作事项')
 })
 
-const updateEquipmentUtilizationRateRule = (required: boolean) => {
-  equipmentUtilizationRateRequired.value = required
-  detailRules.equipmentUtilizationRate = getEquipmentUtilizationRateRules()
-  nextTick(() => detailFormRef.value?.clearValidate('equipmentUtilizationRate'))
+const isRequiredExtProperty = (item: ExtPropertyItem) =>
+  item.required === true || item.required === 1 || item.required === '1'
+
+const isDoubleExtProperty = (item: ExtPropertyItem) => item.dataType === 'double'
+
+const normalizeExtPropertyKeyword = (value: unknown) =>
+  String(value ?? '')
+    .toLowerCase()
+    .replace(/[\s%()()]/g, '')
+
+const getExtPropertySearchText = (item: ExtPropertyItem) =>
+  `${normalizeExtPropertyKeyword(item.identifier)}${normalizeExtPropertyKeyword(item.name)}`
+
+const isEquipmentUtilizationExtProperty = (item: ExtPropertyItem) => {
+  const text = getExtPropertySearchText(item)
+
+  return (
+    text.includes('设备利用率') ||
+    text.includes('equipmentutilization') ||
+    text.includes('utilizationrate')
+  )
 }
 
-const loadEquipmentUtilizationRateRule = async () => {
-  updateEquipmentUtilizationRateRule(true)
+const isConstructionEquipmentCountExtProperty = (item: ExtPropertyItem) => {
+  const text = getExtPropertySearchText(item)
 
-  try {
-    const data = await OperationMeetingApi.getMandatoryOrNot()
-    updateEquipmentUtilizationRateRule(data?.mandatory !== false)
-  } catch {
-    updateEquipmentUtilizationRateRule(true)
-  }
+  return (
+    text.includes('施工设备数量') ||
+    text.includes('施工设备数') ||
+    text.includes('constructionequipmentcount') ||
+    text.includes('constructiondevicecount')
+  )
 }
 
-const isRequiredExtProperty = (item: ExtPropertyItem) =>
-  item.required === true || item.required === 1 || item.required === '1'
+const isOperatingEquipmentCountExtProperty = (item: ExtPropertyItem) => {
+  const text = getExtPropertySearchText(item)
 
-const isDoubleExtProperty = (item: ExtPropertyItem) => item.dataType === 'double'
+  return (
+    text.includes('投运设备数量') ||
+    text.includes('投运设备数') ||
+    text.includes('operatingequipmentcount') ||
+    text.includes('operationequipmentcount') ||
+    text.includes('runningequipmentcount')
+  )
+}
+
+const equipmentUtilizationExtProperty = computed(() =>
+  currentPeriodExtProperties.value.find(isEquipmentUtilizationExtProperty)
+)
+
+const constructionEquipmentCountExtProperty = computed(() =>
+  currentPeriodExtProperties.value.find(isConstructionEquipmentCountExtProperty)
+)
+
+const operatingEquipmentCountExtProperty = computed(() =>
+  currentPeriodExtProperties.value.find(isOperatingEquipmentCountExtProperty)
+)
+
+const updateEquipmentUtilizationExtProperty = () => {
+  const utilizationItem = equipmentUtilizationExtProperty.value
+  const constructionItem = constructionEquipmentCountExtProperty.value
+  const operatingItem = operatingEquipmentCountExtProperty.value
+  if (!utilizationItem || !constructionItem || !operatingItem) return
+
+  const constructionCount = parseNumberValue(constructionItem.actualValue)
+  const operatingCount = parseNumberValue(operatingItem.actualValue)
+
+  utilizationItem.actualValue =
+    constructionCount === undefined || operatingCount === undefined || operatingCount === 0
+      ? undefined
+      : Number(((constructionCount / operatingCount) * 100).toFixed(2))
+
+  nextTick(() => detailFormRef.value?.clearValidate(getExtPropertyProp(utilizationItem)))
+}
 
 const hasExtPropertyActualValue = (item: ExtPropertyItem) => {
   const value = item.actualValue
@@ -334,6 +356,8 @@ const getExtPropertyAlternativeProps = (item: ExtPropertyItem) =>
     .map((property) => getExtPropertyProp(property))
 
 const handleExtPropertyValueChange = (item: ExtPropertyItem) => {
+  updateEquipmentUtilizationExtProperty()
+
   if (
     !isAlternativeExtProperty(item) ||
     !hasCompleteExtPropertyAlternativeGroup(getExtPropertyScope(item))
@@ -393,63 +417,22 @@ const queryProjectNameSearch = (
   cb(projectNameSuggestions.value.filter((item) => item.value.toLowerCase().includes(keyword)))
 }
 
-const getDetailRawQueryValue = (key: 'id' | 'meetingId') => {
-  const value = detailForm.value.raw?.[key]
-
-  if (value === undefined || value === null) {
-    return ''
-  }
-
-  return typeof value === 'string' || typeof value === 'number' ? value : String(value)
-}
-
-const updatePlannedWorkloadFromPreviousPlan = (data?: Record<string, unknown>) => {
-  detailForm.value.plannedWorkload = String(data?.nextPlannedWorkload || '')
-}
-
-const loadPreviousWorkPlan = async () => {
-  if (hasExtProperties.value) return
-
-  const projectName = detailForm.value.projectName.trim()
-
-  if (!projectName) {
-    previousWorkPlanQueryKey.value = ''
-    return
-  }
-
-  const params = {
-    projectName,
-    id: getDetailRawQueryValue('id'),
-    meetingId: getDetailRawQueryValue('meetingId')
-  }
-  const queryKey = JSON.stringify(params)
-
-  if (previousWorkPlanQueryKey.value === queryKey) return
-
-  previousWorkPlanQueryKey.value = queryKey
-
-  try {
-    const data = await OperationMeetingApi.getPreviousWorkPlan(params)
-    if (detailForm.value.projectName.trim() !== projectName) return
-    updatePlannedWorkloadFromPreviousPlan(data)
-  } catch {
-    previousWorkPlanQueryKey.value = ''
-  }
-}
-
-const handleProjectNameComplete = useDebounceFn(loadPreviousWorkPlan, 300)
-
-const handleProjectNameSelect = (item: ProjectNameSuggestion) => {
-  detailForm.value.projectName = item.value
-  void handleProjectNameComplete()
-}
+watch(
+  () =>
+    [
+      constructionEquipmentCountExtProperty.value?.actualValue,
+      operatingEquipmentCountExtProperty.value?.actualValue,
+      equipmentUtilizationExtProperty.value?.identifier
+    ] as const,
+  () => updateEquipmentUtilizationExtProperty(),
+  { immediate: true }
+)
 
 const handleVisibleChange = (visible: boolean) => {
   emits('update:visible', visible)
 
   if (!visible) {
     detailForm.value = createDetailItem()
-    previousWorkPlanQueryKey.value = ''
     nextTick(() => detailFormRef.value?.clearValidate())
   }
 }
@@ -479,7 +462,6 @@ watch(
     if (!visible) return
 
     detailForm.value = cloneDetailItem(props.detail)
-    previousWorkPlanQueryKey.value = ''
     nextTick(() => detailFormRef.value?.clearValidate())
   },
   { immediate: true }
@@ -487,7 +469,6 @@ watch(
 
 onMounted(() => {
   loadProjectNameOptions()
-  loadEquipmentUtilizationRateRule()
 })
 </script>
 
@@ -527,9 +508,7 @@ onMounted(() => {
               placeholder="请选择或输入项目名称"
               clearable
               :fetch-suggestions="queryProjectNameSearch"
-              :trigger-on-focus="true"
-              @change="handleProjectNameComplete"
-              @select="handleProjectNameSelect" />
+              :trigger-on-focus="true" />
           </el-form-item>
         </div>
       </section>
@@ -582,55 +561,31 @@ onMounted(() => {
         </div>
       </section>
 
-      <section class="detail-section">
+      <section v-if="currentPeriodExtProperties.length" class="detail-section">
         <h4 class="detail-section__title">本期生产运行情况</h4>
         <div class="detail-section__grid">
-          <template v-if="hasExtProperties">
-            <el-form-item
-              v-for="item in currentPeriodExtProperties"
-              :key="item.identifier"
-              :label="getExtPropertyLabel(item)"
-              :prop="getExtPropertyProp(item)"
-              :rules="getExtPropertyRules(item)">
-              <el-input-number
-                v-if="isDoubleExtProperty(item)"
-                v-model="item.actualValue"
-                class="w-full!"
-                :controls="false"
-                :precision="2"
-                @change="handleExtPropertyValueChange(item)" />
-              <el-input
-                v-else
-                v-model="item.actualValue"
-                type="textarea"
-                :rows="3"
-                :placeholder="`请输入${item.name}`"
-                @input="handleExtPropertyValueChange(item)" />
-            </el-form-item>
-          </template>
-          <template v-else>
-            <el-form-item label="计划工作量" prop="plannedWorkload">
-              <el-input
-                v-model="detailForm.plannedWorkload"
-                type="textarea"
-                :rows="3"
-                placeholder="请输入计划工作量" />
-            </el-form-item>
-            <el-form-item label="实际完成" prop="actualCompletion">
-              <el-input
-                v-model="detailForm.actualCompletion"
-                type="textarea"
-                :rows="3"
-                placeholder="请输入实际完成" />
-            </el-form-item>
-            <el-form-item label="设备利用率(%)" prop="equipmentUtilizationRate">
-              <el-input-number
-                v-model="detailForm.equipmentUtilizationRate"
-                class="w-full!"
-                :controls="false"
-                :precision="2" />
-            </el-form-item>
-          </template>
+          <el-form-item
+            v-for="item in currentPeriodExtProperties"
+            :key="item.identifier"
+            :label="getExtPropertyLabel(item)"
+            :prop="getExtPropertyProp(item)"
+            :rules="getExtPropertyRules(item)">
+            <el-input-number
+              v-if="isDoubleExtProperty(item)"
+              v-model="item.actualValue"
+              class="w-full!"
+              :controls="false"
+              :disabled="isEquipmentUtilizationExtProperty(item)"
+              :precision="2"
+              @change="handleExtPropertyValueChange(item)" />
+            <el-input
+              v-else
+              v-model="item.actualValue"
+              type="textarea"
+              :rows="3"
+              :placeholder="`请输入${item.name}`"
+              @input="handleExtPropertyValueChange(item)" />
+          </el-form-item>
         </div>
       </section>
 

+ 210 - 28
src/views/pms/operation-meeting/components/operation-meeting-content.vue

@@ -282,6 +282,9 @@ const formatSummaryNumber = (value: number) =>
     minimumFractionDigits: Number.isInteger(value) ? 0 : 2
   })
 
+const getCompareTrendIcon = (value: number) =>
+  value > 0 ? 'i-lucide:trending-up' : value < 0 ? 'i-lucide:trending-down' : 'i-lucide:minus'
+
 const getSummaryRows = () => {
   if (!isSummaryMode.value) return props.details
   if (props.companyFilterValue) return filteredSummaryDetailRows.value
@@ -324,12 +327,7 @@ const getDetailSummaryCompare = (field: DetailSummaryField) => {
   return {
     value: `${compareValue > 0 ? '+' : ''}${formatSummaryNumber(compareValue)}%`,
     trend: compareValue > 0 ? 'up' : compareValue < 0 ? 'down' : 'flat',
-    icon:
-      compareValue > 0
-        ? 'i-lucide:trending-up'
-        : compareValue < 0
-          ? 'i-lucide:trending-down'
-          : 'i-lucide:minus'
+    icon: getCompareTrendIcon(compareValue)
   }
 }
 
@@ -350,6 +348,9 @@ const showSupportPanel = computed(
 
 const isNextExtProperty = (item: ExtPropertyItem) => item.defaultValue === 'next'
 
+const isVisibleWorkloadExtProperty = (item: ExtPropertyItem) =>
+  item.required === true || item.required === 1 || item.required === '1'
+
 const getExtPropertyLabel = (item: ExtPropertyItem) =>
   item.unit ? `${item.name}(${item.unit})` : item.name
 
@@ -458,7 +459,7 @@ const appendWorkloadProperty = (
 ) => {
   const identifier = normalizeWorkloadPropertyKey(item.identifier)
 
-  if (!identifier || isNextExtProperty(item)) return
+  if (!identifier || isNextExtProperty(item) || !isVisibleWorkloadExtProperty(item)) return
 
   if (Array.from(propertyMap.values()).some((property) => isSameWorkloadProperty(property, item))) {
     return
@@ -469,10 +470,85 @@ const appendWorkloadProperty = (
 
 const currentSummaryWorkloadProperties = computed(() =>
   getSortedExtProperties(
-    (currentSummaryMeeting.value?.extProperty || []).filter((item) => !isNextExtProperty(item))
+    (currentSummaryMeeting.value?.extProperty || []).filter(
+      (item) => !isNextExtProperty(item) && isVisibleWorkloadExtProperty(item)
+    )
   )
 )
 
+const getWorkloadPropertyMetricText = (item: ExtPropertyItem) =>
+  `${normalizeWorkloadPropertyKey(item.identifier)}${normalizeWorkloadPropertyName(item.name)}`
+    .toLowerCase()
+    .replace(/[\s%]/g, '')
+
+const isEquipmentUtilizationWorkloadProperty = (item: ExtPropertyItem) => {
+  const text = getWorkloadPropertyMetricText(item)
+
+  return (
+    text.includes('设备利用率') ||
+    text.includes('equipmentutilization') ||
+    text.includes('utilizationrate')
+  )
+}
+
+const isCompareWorkloadProperty = (item: ExtPropertyItem) => {
+  const text = getWorkloadPropertyMetricText(item)
+
+  return (
+    text.includes('环比') ||
+    text.includes('monthonmonth') ||
+    text.includes('mom') ||
+    text.includes('chainratio')
+  )
+}
+
+const isSideWorkloadProperty = (item: ExtPropertyItem) =>
+  isEquipmentUtilizationWorkloadProperty(item) || isCompareWorkloadProperty(item)
+
+const primarySummaryWorkloadProperties = computed(() =>
+  currentSummaryWorkloadProperties.value.filter((item) => !isSideWorkloadProperty(item))
+)
+
+const sideSummaryWorkloadProperties = computed(() =>
+  currentSummaryWorkloadProperties.value.filter(isSideWorkloadProperty)
+)
+
+const splitSummaryWorkload = computed(
+  () =>
+    primarySummaryWorkloadProperties.value.length > 0 &&
+    sideSummaryWorkloadProperties.value.length > 0
+)
+
+const displayedSummaryWorkloadProperties = computed(() =>
+  splitSummaryWorkload.value
+    ? primarySummaryWorkloadProperties.value
+    : currentSummaryWorkloadProperties.value
+)
+
+const workloadToneList = ['blue', 'emerald', 'amber', 'violet', 'cyan', 'rose'] as const
+
+const getSummaryWorkloadTone = (item: ExtPropertyItem, index: number) => {
+  const text = getWorkloadPropertyMetricText(item)
+
+  if (isEquipmentUtilizationWorkloadProperty(item)) return 'cyan'
+  if (isCompareWorkloadProperty(item)) return 'rose'
+  if (text.includes('计划') || text.includes('plan')) return 'blue'
+  if (text.includes('实际') || text.includes('actual')) return 'emerald'
+  if (text.includes('完成') || text.includes('complete') || text.includes('finish')) return 'amber'
+  if (text.includes('累计') || text.includes('cumulative') || text.includes('total'))
+    return 'violet'
+
+  return workloadToneList[index % workloadToneList.length]
+}
+
+const getSummaryWorkloadIcon = (item: ExtPropertyItem) => {
+  if (!isCompareWorkloadProperty(item)) return 'i-lucide:chart-column'
+
+  const value = Number(String(item.actualValue ?? 0).replace('%', ''))
+
+  return getCompareTrendIcon(Number.isNaN(value) ? 0 : value)
+}
+
 const showSummaryWorkload = computed(
   () =>
     isSummaryMode.value &&
@@ -757,14 +833,40 @@ const getMeetingTableCellClassName = ({ column }: MeetingTableCellStyleProps) =>
           查看详情
         </el-button>
       </div>
-      <div class="meeting-workload-strip__grid">
-        <div
-          v-for="item in currentSummaryWorkloadProperties"
-          :key="item.identifier"
-          class="meeting-workload-strip__item">
-          <div class="i-lucide:chart-column size-5 icon"></div>
-          <span>{{ item.name }}</span>
-          <strong>{{ formatWorkloadPropertyValue(item) }}</strong>
+      <div
+        :class="[
+          'meeting-workload-strip__content',
+          { 'meeting-workload-strip__content--split': splitSummaryWorkload }
+        ]">
+        <div class="meeting-workload-strip__grid">
+          <div
+            v-for="(item, index) in displayedSummaryWorkloadProperties"
+            :key="item.identifier"
+            :class="[
+              'meeting-workload-strip__item',
+              `meeting-workload-strip__item--${getSummaryWorkloadTone(item, index)}`
+            ]">
+            <div :class="[getSummaryWorkloadIcon(item), 'size-5 icon']"></div>
+            <span>{{ item.name }}</span>
+            <strong>{{ formatWorkloadPropertyValue(item) }}</strong>
+          </div>
+        </div>
+        <div v-if="splitSummaryWorkload" class="meeting-workload-strip__side">
+          <div
+            v-for="(item, index) in sideSummaryWorkloadProperties"
+            :key="item.identifier"
+            :class="[
+              'meeting-workload-strip__item',
+              'meeting-workload-strip__item--side',
+              `meeting-workload-strip__item--${getSummaryWorkloadTone(
+                item,
+                index + primarySummaryWorkloadProperties.length
+              )}`
+            ]">
+            <div :class="[getSummaryWorkloadIcon(item), 'size-5 icon']"></div>
+            <span>{{ item.name }}</span>
+            <strong>{{ formatWorkloadPropertyValue(item) }}</strong>
+          </div>
         </div>
       </div>
     </section>
@@ -773,7 +875,7 @@ const getMeetingTableCellClassName = ({ column }: MeetingTableCellStyleProps) =>
       v-model="workloadDetailVisible"
       :title="`${currentSummaryScopeName}工作量完成情况详情`"
       class="meeting-workload-detail-dialog"
-      width="82%">
+      width="60%">
       <div class="meeting-workload-detail">
         <WorkloadZmTable
           class="meeting-workload-detail__table"
@@ -781,20 +883,15 @@ const getMeetingTableCellClassName = ({ column }: MeetingTableCellStyleProps) =>
           :loading="false"
           :stripe="false"
           :max-height="560"
-          align="left"
           :show-overflow-tooltip="false">
-          <WorkloadZmTableColumn label="项目名称" prop="label" min-width="140" fixed="left" />
+          <WorkloadZmTableColumn label="项目名称" prop="label" fixed="left" />
           <WorkloadZmTableColumn
             v-for="(detail, index) in workloadDetailColumns"
             :key="getWorkloadDetailColumnKey(detail, index)"
             :label="getWorkloadDetailColumnLabel(detail, index)"
             :prop="`values.${index}`"
-            min-width="160"
             :formatter="(row) => getWorkloadDetailProjectValue(row, index)" />
-          <WorkloadZmTableColumn
-            :label="`${currentSummaryScopeName}合计`"
-            prop="total"
-            min-width="160" />
+          <WorkloadZmTableColumn :label="`${currentSummaryScopeName}合计`" prop="total" />
         </WorkloadZmTable>
       </div>
     </el-dialog>
@@ -1189,26 +1286,56 @@ const getMeetingTableCellClassName = ({ column }: MeetingTableCellStyleProps) =>
   margin-top: 2px;
 }
 
+.meeting-workload-strip__content {
+  display: grid;
+  min-width: 0;
+  gap: 4px;
+}
+
+.meeting-workload-strip__content--split {
+  grid-template-columns: minmax(0, 1fr) minmax(180px, 240px);
+
+  .meeting-workload-strip__grid {
+    grid-template-columns: repeat(4, minmax(0, 1fr));
+  }
+}
+
 .meeting-workload-strip__grid {
   display: grid;
   grid-template-columns: repeat(4, minmax(0, 1fr));
   gap: 4px;
 }
 
+.meeting-workload-strip__side {
+  display: grid;
+  min-width: 0;
+  gap: 4px;
+}
+
 .meeting-workload-strip__item {
+  --workload-card-accent: #2563eb;
+  --workload-card-bg-end: #fff;
+  --workload-card-bg-start: #eff6ff;
+  --workload-card-border: #bfdbfe;
+  --workload-card-value: #1d4ed8;
+
   display: flex;
   min-width: 0;
   align-items: center;
   justify-content: space-between;
   gap: 4px;
   padding: 4px 6px;
-  background: linear-gradient(180deg, #ecfdf5 0%, #fff 100%);
-  border: 1px solid #bbf7d0;
+  background: linear-gradient(
+    180deg,
+    var(--workload-card-bg-start) 0%,
+    var(--workload-card-bg-end) 100%
+  );
+  border: 1px solid var(--workload-card-border);
   border-radius: 7px;
 
   .icon {
     flex-shrink: 0;
-    color: #10b981;
+    color: var(--workload-card-accent);
   }
 
   span {
@@ -1231,12 +1358,54 @@ const getMeetingTableCellClassName = ({ column }: MeetingTableCellStyleProps) =>
     font-size: 15px;
     font-weight: 800;
     line-height: 18px;
-    color: #059669;
+    color: var(--workload-card-value);
     text-overflow: ellipsis;
     white-space: nowrap;
   }
 }
 
+.meeting-workload-strip__item--blue {
+  --workload-card-accent: #2563eb;
+  --workload-card-bg-start: #eff6ff;
+  --workload-card-border: #bfdbfe;
+  --workload-card-value: #1d4ed8;
+}
+
+.meeting-workload-strip__item--emerald {
+  --workload-card-accent: #059669;
+  --workload-card-bg-start: #ecfdf5;
+  --workload-card-border: #a7f3d0;
+  --workload-card-value: #047857;
+}
+
+.meeting-workload-strip__item--amber {
+  --workload-card-accent: #d97706;
+  --workload-card-bg-start: #fffbeb;
+  --workload-card-border: #fde68a;
+  --workload-card-value: #b45309;
+}
+
+.meeting-workload-strip__item--violet {
+  --workload-card-accent: #7c3aed;
+  --workload-card-bg-start: #f5f3ff;
+  --workload-card-border: #ddd6fe;
+  --workload-card-value: #6d28d9;
+}
+
+.meeting-workload-strip__item--cyan {
+  --workload-card-accent: #0891b2;
+  --workload-card-bg-start: #ecfeff;
+  --workload-card-border: #a5f3fc;
+  --workload-card-value: #0e7490;
+}
+
+.meeting-workload-strip__item--rose {
+  --workload-card-accent: #e11d48;
+  --workload-card-bg-start: #fff1f2;
+  --workload-card-border: #fecdd3;
+  --workload-card-value: #be123c;
+}
+
 .meeting-workload-detail {
   width: 100%;
   overflow: auto;
@@ -1540,6 +1709,18 @@ const getMeetingTableCellClassName = ({ column }: MeetingTableCellStyleProps) =>
     border-bottom: 1px solid #d1fae5;
   }
 
+  .meeting-workload-strip__content--split {
+    grid-template-columns: 1fr;
+
+    .meeting-workload-strip__grid {
+      grid-template-columns: repeat(2, minmax(0, 1fr));
+    }
+  }
+
+  .meeting-workload-strip__side {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+
   .meeting-summary-strip__grid {
     grid-template-columns: repeat(3, minmax(0, 1fr));
   }
@@ -1563,6 +1744,7 @@ const getMeetingTableCellClassName = ({ column }: MeetingTableCellStyleProps) =>
 
   .meeting-summary-strip__grid,
   .meeting-workload-strip__grid,
+  .meeting-workload-strip__side,
   .meeting-detail-card__fields {
     grid-template-columns: 1fr;
   }