From e7a1f3ef2716f0caf90a88ef8cce4116cd47ac18 Mon Sep 17 00:00:00 2001 From: tnagorra Date: Fri, 25 Oct 2024 16:11:45 +0545 Subject: [PATCH] Update labels and descriptions on the template export - Update styling of the export - Headers and description - Add support for rich text on label and description - Hide options sheet - Rename 'headingType' to 'rowType' - Append 4 new lines on text area to set initial height but also expand on content --- app/src/utils/importTemplate.ts | 60 ++- .../DownloadImportTemplateModal/index.tsx | 44 ++- .../useImportTemplateSchema.ts | 353 +++++++++++++++--- .../DownloadImportTemplateButton/utils.ts | 103 +++-- 4 files changed, 456 insertions(+), 104 deletions(-) diff --git a/app/src/utils/importTemplate.ts b/app/src/utils/importTemplate.ts index 0ef435cfc..a637c9c7e 100644 --- a/app/src/utils/importTemplate.ts +++ b/app/src/utils/importTemplate.ts @@ -7,6 +7,53 @@ import { mapToMap, randomString, } from '@togglecorp/fujs'; +import { type CellRichTextValue } from 'exceljs'; + +function parseRichText(value: undefined): undefined; +function parseRichText(value: string): string | CellRichTextValue +function parseRichText(value: string | undefined): string | CellRichTextValue | undefined +function parseRichText(value: string | undefined): string | CellRichTextValue | undefined { + if (isNotDefined(value)) { + return value; + } + + const tagRegex = /(<\/?[bui]>)/; + const tokens = value.split(tagRegex); + + if (tokens.length === 1) { + return value; + } + + const richText:CellRichTextValue['richText'] = []; + + const stack: string[] = []; + + const openTagRegex = /(<[bui]>)/; + const closeTagRegex = /(<\/[bui]>)/; + + tokens.forEach((token) => { + if (token.match(openTagRegex)) { + stack.push(token); + return; + } + if (token.match(closeTagRegex)) { + // TODO: Check correctness by checking closeTag with last openTag + stack.pop(); + return; + } + richText.push({ + font: { + bold: stack.includes(''), + italic: stack.includes(''), + underline: stack.includes(''), + }, + text: token, + }); + }); + // TODO: Check correctness to check that stack is empty + + return { richText }; +} type ValidationType = string | number | boolean | 'textArea'; type TypeToLiteral = T extends string @@ -99,6 +146,7 @@ export type TemplateSchema< | SelectField, OPTIONS_MAPPING>) ); +// NOTE: Not adding richtext support on heading interface HeadingTemplateField { type: 'heading'; name: string | number | boolean; @@ -112,9 +160,9 @@ type ObjectKey = string | number | symbol; type InputTemplateField = { type: 'input'; name: string | number | boolean; - label: string; + label: string | CellRichTextValue; outlineLevel: number; - description?: string; + description?: string | CellRichTextValue; headingBefore?: string; } & ({ dataValidation: 'list'; @@ -188,8 +236,8 @@ export function createImportTemplate< const field = { type: 'input', name: fieldName, - label: schema.label, - description: schema.description, + label: parseRichText(schema.label), + description: parseRichText(schema.description), dataValidation: (schema.validation === 'number' || schema.validation === 'date' || schema.validation === 'integer' || schema.validation === 'textArea') ? schema.validation : undefined, @@ -204,8 +252,8 @@ export function createImportTemplate< const field = { type: 'input', name: fieldName, - label: schema.label, - description: schema.description, + label: parseRichText(schema.label), + description: parseRichText(schema.description), outlineLevel, dataValidation: 'list', optionsKey: schema.optionsKey, diff --git a/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/index.tsx b/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/index.tsx index ff133e320..82d44dd44 100644 --- a/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/index.tsx +++ b/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/index.tsx @@ -13,6 +13,7 @@ import { stringValueSelector } from '@ifrc-go/ui/utils'; import { isDefined, isNotDefined, + isTruthyString, listToGroupList, listToMap, mapToList, @@ -139,7 +140,7 @@ async function generateTemplate( }; const optionsWorksheet = workbook.addWorksheet('options'); - // optionsWorksheet.state = 'veryHidden'; + optionsWorksheet.state = 'veryHidden'; const optionKeys = Object.keys(optionsMap) as (keyof (typeof optionsMap))[]; optionsWorksheet.columns = optionKeys.map((key) => ( @@ -191,26 +192,26 @@ async function generateTemplate( tabGroupedTemplateActions.forEach(({ actions, worksheet }) => { let lastHeadingIndex = 0; actions.forEach((templateAction, i) => { + const row = i + ROW_OFFSET; + if (templateAction.type === 'heading') { addHeadingRow( worksheet, - i + ROW_OFFSET, + row, templateAction.outlineLevel, String(templateAction.name), templateAction.label, templateAction.description, ); - worksheet.mergeCells(i + ROW_OFFSET, 1, i + ROW_OFFSET, 3); + worksheet.mergeCells(row, 1, row, 3); lastHeadingIndex = i + 1; } else if (templateAction.type === 'input') { - // NOTE: Determines the headingType based on the index - // position relative to the last heading, list heading is for list section. - const headingType = (i - lastHeadingIndex) % 2 === 0 ? 'listHeading' : 'heading'; + const rowType = (i - lastHeadingIndex) % 2 === 0 ? 'alt' : 'normal'; if (templateAction.dataValidation === 'list') { addInputRow( - headingType, + rowType, worksheet, - i + ROW_OFFSET, + row, templateAction.outlineLevel, String(templateAction.name), templateAction.label, @@ -220,23 +221,36 @@ async function generateTemplate( optionsWorksheet, ); } else if (templateAction.dataValidation === 'textArea') { + // NOTE: Adding 4 new-lines to add height while also + // supporting expand + let { label } = templateAction; + if (typeof label === 'string' && isTruthyString(label)) { + label += '\n\n\n\n'; + } else if (typeof label === 'object' && label.richText.length > 0) { + label = { + ...label, + richText: [ + ...label.richText, + { text: '\n\n\n\n' }, + ], + }; + } + addInputRow( - headingType, + rowType, worksheet, - i + ROW_OFFSET, + row, templateAction.outlineLevel, String(templateAction.name), - templateAction.label, + label, templateAction.description, 'text', ); - const row = worksheet.getRow(i + ROW_OFFSET); - row.height = 100; } else { addInputRow( - headingType, + rowType, worksheet, - i + ROW_OFFSET, + row, templateAction.outlineLevel, String(templateAction.name), templateAction.label, diff --git a/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/useImportTemplateSchema.ts b/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/useImportTemplateSchema.ts index aee63b583..7bf1717e7 100644 --- a/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/useImportTemplateSchema.ts +++ b/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/useImportTemplateSchema.ts @@ -87,19 +87,23 @@ function useImportTemplateSchema() { dref_dref_disaster_category, ]); + // FIXME: rename this to drefTemplateSchema const drefFormSchema: TemplateSchema = useMemo(() => ({ type: 'object', fields: { + // OPERATION OVERVIEW + national_society: { + // FIXME: We don't need this anymore? + // headingBefore: 'Essential Information', type: 'select', label: 'National society', validation: 'number', optionsKey: 'national_society', - headingBefore: 'Essential Information', + description: 'Indicate your National Society by selecting it from the drop-down list.', }, - // We're skipping type of DREF since we'll have separate - // template for each type of dref + // We're skipping type of DREF since we'll have separate template for each type of dref // type_of_dref: xxx disaster_type: { @@ -107,6 +111,7 @@ function useImportTemplateSchema() { label: 'Type of disaster', validation: 'number', optionsKey: 'disaster_type', + description: 'Select the relevant type of disaster from the drop-down list.', }, type_of_onset: { @@ -114,6 +119,11 @@ function useImportTemplateSchema() { label: 'Type of Onset', validation: 'number', optionsKey: 'type_of_onset', + description: ( + 'Select the type of onset from the drop-down list.\n' + + 'Sudden-onset disaster – a single, discrete event that occurs in a matter of days or even hours (e.g. earthquakes, flash floods).\n' + + 'Slow-onset disaster - slow onset events evolve gradually from incremental changes occurring over many months/years or from an increased frequency or intensity of recurring events (e.g. Droughts, Food insecurity, Epidemics, etc.)' + ), }, is_man_made_event: { @@ -121,6 +131,10 @@ function useImportTemplateSchema() { label: 'Is this a man made event?', validation: 'boolean', optionsKey: '__boolean', + description: ( + 'Select Yes or No from the drop-down list.\n' + + 'Is the event caused or triggered by human interventions?' + ), }, disaster_category: { @@ -150,16 +164,17 @@ function useImportTemplateSchema() { label: 'Emergency appeal planned', optionsKey: '__boolean', validation: 'boolean', + description: 'Select Yes or No from the drop-down list.', }, - // Event eventDetail - // Previous Operations + // EVENT DETAIL + did_it_affect_same_area: { + headingBefore: 'Previous Operation', type: 'select', - label: 'Has a similar event affected the same area(s) in the last 3 years?', + label: 'Has a similar event affected the same area(s) in the last 3 years?', optionsKey: '__boolean', validation: 'boolean', - headingBefore: 'Previous Operation', }, did_it_affect_same_population: { @@ -167,7 +182,11 @@ function useImportTemplateSchema() { label: 'Did it affect the same population groups?', optionsKey: '__boolean', validation: 'boolean', - description: 'Select only if you\'ve selected Yes for the above', + // FIXME: Cannot group the cells + description: ( + 'Indicate only if there was a similar event affecting the same area in the last 3 years.\n' + + 'Otherwise, leave the box empty.' + ), }, did_ns_respond: { @@ -175,7 +194,11 @@ function useImportTemplateSchema() { label: 'Did the National Society respond?', optionsKey: '__boolean', validation: 'boolean', - description: 'Select only if you\'ve selected Yes for the above', + // FIXME: Cannot group the cells + description: ( + 'Indicate only if there was a similar event affecting the same area in the last 3 years.\n' + + 'Otherwise, leave the box empty.' + ), }, did_ns_request_fund: { @@ -183,16 +206,25 @@ function useImportTemplateSchema() { label: 'Did the National Society request funding from DREF for that event(s)?', optionsKey: '__boolean', validation: 'boolean', - description: 'Select only if you\'ve selected Yes for the above', + // FIXME: Cannot group the cells + description: ( + 'Indicate only if there was a similar event affecting the same area in the last 3 years.\n' + + 'Otherwise, leave the box empty.' + ), }, ns_request_text: { type: 'input', - label: 'If yes, please specify which operations', + label: 'If yes, please specify which operations', validation: 'string', - description: 'Select only if you\'ve selected Yes for the above', + // FIXME: Cannot group the cells + description: ( + 'Indicate only if there was a similar event affecting the same area in the last 3 years.\n' + + 'Otherwise, leave the box empty.' + ), }, + // FIXME: remove this? dref_recurrent_text: { type: 'input', label: 'If you have answered yes to all questions above, justify why the use of DREF for a recurrent event, or how this event should not be considered recurrent', @@ -203,48 +235,91 @@ function useImportTemplateSchema() { type: 'input', label: 'Lessons Learned', validation: 'textArea', - description: 'Specify how the lessons learnt from these previous operations are being used to mitigate similar challenges in the current operation', + description: ( + 'Indicate only if there was a similar event affecting the same area in the last 3 years.\n' + + '\n' + + 'Specify how the lessons learnt from these previous operations are planned to be used to mitigate similar challenges in the current operation.\n' + + '\n' + + 'The format should be concise, actionable, and focused on a specific insight:\n' + + '[Action or Practice] + [Outcome/Result] + [Key Takeaway or Recommendation]\n' + + '\n' + + 'Example: Engaging local suppliers early in the procurement process led to faster delivery of relief items, highlighting the need to establish pre-agreed contracts with reliable vendors before emergencies.' + ), }, event_date: { + headingBefore: 'Description of the Event', type: 'input', - label: 'Date of the Event / Date when the trigger was met', + // FIXME: Is this label change correct? + label: 'Date of the Event', validation: 'date', - headingBefore: 'Description of the Event', - description: 'When the `Type of Onset` is Slow, we store the input date as `Date when the trigger was met. When the `Type of Onset` is Sudden, we store the input date as `Date of the Event`.', + // FIXME: Not sure if this is correct + description: 'DD/MM/YYY', }, num_affected: { type: 'input', validation: 'number', label: 'Total affected population', - description: 'People Affected include all those whose lives and livelihoods have been impacted as a direct result of the shock or stress.', + description: 'People Affected include all those whose lives and livelihoods have been impacted as a direct result of the event.', }, people_in_need: { type: 'input', validation: 'number', - label: 'People in need (Optional)', - description: 'People in Need (PIN) are those members whose physical security, basic rights, dignity, living conditions or livelihoods are threatened or have been disrupted, and whose current level of access to basic services, goods and social protection is inadequate to re-establish normal living conditions without additional assistance.', + label: 'People in need (Optional)', + description: 'People in Need are those members whose physical security, basic rights, dignity, living conditions or livelihoods are threatened or have been disrupted, and whose current level of access to basic services, goods and social protection is inadequate to re-establish normal living conditions without additional assistance.', }, event_description: { type: 'input', validation: 'textArea', label: 'What happened, where and when?', - }, - + description: ( + 'Clearly describe:\n' + + 'What happened:\n' + + 'Briefly explain the nature of the emergency (e.g., flood, earthquake, epidemic). Include key details such as the intensity, and any unique aspects of the event.\n' + + '\n' + + 'Where:\n' + + 'Specify the geographic location(s) affected. Be as precise as possible, including names of countries, regions, cities, or specific areas impacted by the event.\n' + + '\n' + + 'When:\n' + + 'Indicate the date and time when the event occurred or began. If the situation is ongoing, mention that and provide relevant updates on the timeframe.' + ), + }, + + // FIXME: Added bold on "Comparative Analysis" event_scope: { type: 'input', validation: 'textArea', label: 'Scope and scale of the event', - description: 'Describe the extent this hazard will produce negative impacts on lives, livelihoods, well-being and infrastructure. Explain which people are most likely to experience the impacts of this hazard? Where do they live, and why are they vulnerable? Please explain which groups (e.g elderly, children, people with disabilities, IDPs, Refugees, etc.) are most likely to be affected? Provide historic information on how communities have been affected by the magnitude of this hazard in the past?', + description: ( + 'Describe how the event produces negative impacts on lives, livelihoods, well-being and infrastructure.\n' + + 'Explain which people are most likely to experience the impacts of this hazard? Where do they live, and why are they vulnerable? Please explain which groups (e.g elderly, children, people with disabilities, IDPs, Refugees, etc.) are most likely to be affected? Provide historic information on how communities have been affected by the magnitude of this hazard in the past?\n' + + '\n' + + 'Consider the following:\n' + + 'Humanitarian Impact:\n' + + '- Are the statistics on damages and losses (e.g., number of people affected, casualties, displaced populations) clearly stated and supported by reliable data?\n' + + '- Is the scale of the disaster described in terms of its impact on different sectors (e.g., housing, health, water and sanitation, livelihoods)?\n' + + '- Are vulnerable groups (e.g., women, children, elderly, disabled) and their specific needs highlighted?\n' + + 'Extent of Damage:\n' + + '- Does the section include detailed information on the physical destruction (e.g., homes, infrastructure, public services)?\n' + + '- Are economic impacts (e.g., loss of livelihoods, market disruptions) discussed in detail?\n' + + 'Comparative Analysis:\n' + + '- If applicable, does the document compare the current disaster to previous similar events in the region to contextualize its scale?\n' + + '- Are there any predictions or projections about how the situation might develop, based on past experiences or current trends?\n' + + 'Sources and Validation:\n' + + '- Are the data and statistics provided in this subsection supported by credible sources (e.g., government reports, UN agencies, credible NGOs)?\n' + + '- Are sources cited appropriately, ensuring transparency and the ability to verify information?' + ), }, source_information: { type: 'list', label: 'Source Information', - description: 'Add the links and the name of the sources, the name will be shown in the export, as an hyperlink.', + // FIXME: description has been removed + // description: 'Add the links and the name of the sources, the + // name will be shown in the export, as an hyperlink.', optionsKey: 'source_information', children: { type: 'object', @@ -258,24 +333,29 @@ function useImportTemplateSchema() { type: 'input', validation: 'string', label: 'Link', - description: 'Add the links and the name of the sources, the name will be shown in the export, as an hyperlink.', + // FIXME: Cannot group the cells + // FIXME: This will be visible on all the repetitions + description: 'Specify the names (up to 5) of the key information sources. If possible, provide links.\nThe names will be shown in the export as an hyperlink.', }, }, }, }, did_national_society: { + headingBefore: 'Current National Society Actions', type: 'select', validation: 'boolean', optionsKey: '__boolean', label: 'Has the National Society started any actions?', - headingBefore: 'Current National Society Actions', + description: 'Select Yes or No from the drop-down list.', }, ns_respond_date: { type: 'input', validation: 'date', - label: 'Start date of National Society actions', + label: 'If yes, start date of National Society actions', + // FIXME: Not sure if this is correct + description: 'DD/MM/YYY', }, national_society_actions: { @@ -286,25 +366,32 @@ function useImportTemplateSchema() { hiddenLabel: true, keyFieldName: 'title', optionsKey: 'national_society_actions', - description: 'Please indicate a description of the ongoing response with if possible: Branches involved, number of volunteers/staff involved in actions, assets deployed/distributed, number of people reach. Impact/added value of the NS in the response already ongoing.', + // FIXME: description has been removed + // description: 'Please indicate a description of the ongoing + // response with if possible: Branches involved, number of + // volunteers/staff involved in actions, assets + // deployed/distributed, number of people reach. Impact/added + // value of the NS in the response already ongoing.', children: { type: 'object', fields: { description: { type: 'input', validation: 'textArea', - label: 'Description', + label: 'Description', + // FIXME: Add dynamic description + // FIXME: Does this need to be left aligned? }, }, }, }, ifrc: { + headingBefore: 'IFRC Network Actions Related To The Current Event', type: 'input', validation: 'textArea', label: 'IFRC', - description: 'Presence or not of IFRC in country (if not, indicate the cluster covering), support provided for this response, domestic coordination, technical, strategic, surge. Explain what support provided in terms of Secretariat services: PMER, Finance, Admin, HR, Security, logistics, NSD.', - headingBefore: 'IFRC Network Actions Related To The Current Event', + description: 'Presence or not of IFRC in country (if not, indicate the cluster covering). Support provided for this response, domestic coordination, technical, strategic, surge. Explain what support is being provided in terms of Secretariat services: PMER, Finance, Admin, HR, Security, logistics, NSD.', }, partner_national_society: { @@ -315,31 +402,35 @@ function useImportTemplateSchema() { }, icrc: { + headingBefore: 'ICRC Actions Related To The Current Event', type: 'input', validation: 'textArea', - label: 'ICRC', + // FIXME: Is this label change correct? + label: 'Description', description: 'Presence or not of ICRC in country, and support directly provided for this emergency response. Other programs and support provided outside of the scope of this emergency should not be indicated here.', - headingBefore: 'ICRC Actions Related To The Current Event', }, government_requested_assistance: { + headingBefore: 'Other Actors Actions Related To The Current Event', type: 'select', validation: 'boolean', optionsKey: '__boolean', label: 'Government has requested international assistance', - headingBefore: 'Other Actors Actions Related To The Current Event', + description: 'Select Yes or No from the drop-down list.', }, national_authorities: { type: 'input', validation: 'textArea', label: 'National authorities', + description: 'Brief description of actions taken by the national authorities.', }, un_or_other_actor: { type: 'input', validation: 'textArea', label: 'UN or other actors', + description: 'Brief description of actions taken by the UN or other actors.', }, is_there_major_coordination_mechanism: { @@ -347,8 +438,14 @@ function useImportTemplateSchema() { validation: 'boolean', optionsKey: '__boolean', label: 'Are there major coordination mechanisms in place?', + description: ( + '- Inform on the inter-agency / inter-governmental coordination that has been formally or informally set up.\n' + + '- If possible, include the methods of coordination / information sharing.\n' + + '- If possible, include brief information how task / locations / etc. are coordinated and divided.\n' + ), }, + // FIXME: remove this? major_coordination_mechanism: { type: 'input', validation: 'string', @@ -366,7 +463,9 @@ function useImportTemplateSchema() { description: { type: 'input', validation: 'textArea', - label: 'Description', + label: 'Description', + // FIXME: Add dynamic description + // FIXME: Does this need to be left aligned? }, }, }, @@ -376,28 +475,56 @@ function useImportTemplateSchema() { type: 'input', validation: 'textArea', label: 'Any identified gaps/limitations in the assessment', + description: ( + 'Consider the following:\n' + + '\n' + + '- Unmet needs: are there specific sectors (e.g., shelter, WASH, health) where needs remain unmet or only partially addressed?\n' + + '- Resource shortages: highlight any shortages in available resources (e.g., funding, personnel, supplies) that limit the ability to meet the identified needs.\n' + + '- Operational challenges: mention any operational constraints that are preventing a full response to the needs (e.g., logistical issues, insufficient capacity).\n' + + '- Coordination issues: note any challenges in coordinating with other actors or agencies that have resulted in gaps in service delivery or response coverage.\n' + + '- Vulnerable groups: identify any specific vulnerable groups whose needs may not have been fully captured or addressed during the assessment (e.g., displaced persons, elderly, people with disabilities).\n' + ), }, // Operation operation_objective: { + headingBefore: 'Objective and Strategy Rationale', type: 'input', validation: 'textArea', label: 'Overall objective of the operation', - headingBefore: 'Objective and Strategy Rationale', + description: ( + 'The objective statement should clearly and concisely describe the primary goal of the operation, focusing on the humanitarian impact and the specific needs the operation aims to address.\n' + + '\n' + + 'The IFRC-DREF operation aims to [primary action] in order to [desired impact] for [target population] affected by [event/disasted], by providing [key services/interventions] and ensuring [core outcomes such as protection, dignity, and resilience] over [operation period].' + ), }, response_strategy: { type: 'input', validation: 'textArea', label: 'Operation strategy rationale', + description: ( + 'Elaborate on the overall plan, strategy and approach of the operation; explain the reasoning behind the chosen strategy for the emergency operation.\n' + + '\n' + + 'Explain how the identified needs/gaps and actions taken/plannes are linked.\n' + + '\n' + + '- Highlight the most urgent needs the operation aims to address.\n' + + '- Describe the main priorities and explain why these priorities were chosed based on the specific context of the emergency.\n' + + '- Justify why particular methods and actions were selected and how they are expected to achieve the desired outcomes.\n' + + '- Include any key factors that influence the strategy; mention how these factors were considered in shaping the operation.' + ), }, people_assisted: { + headingBefore: 'Targeting Strategy', type: 'input', validation: 'textArea', label: 'Who will be targeted through this operation?', - description: 'Explain the logic behind our targets. Which groups are we targeting and why are we targeting these particular groups? Explain how you will target vulnerable groups (e.g., Migrants, refugees, etc.)', - headingBefore: 'Targeting Strategy', + description: ( + 'Explain the logic behind the targets. Which groups are targeted and why these particular groups? Explain how you will target vulnerable groups (e.g., Migrants, refugees, pregnant women, older people, etc.)\n' + + '\n' + + 'Mention if any data were used to identify who is most vulnerable.' + ), }, selection_criteria: { @@ -408,58 +535,76 @@ function useImportTemplateSchema() { }, women: { + headingBefore: 'Total Targeted Population', type: 'input', validation: 'number', - label: 'Targeted Population: Women', - headingBefore: 'Total Targeted Population', + label: 'Targeted Population: Women', + description: 'Number, e.g. XX,XXX.', + // FIXME: Does this need to be left aligned? }, men: { type: 'input', validation: 'number', - label: 'Targeted Population: Men', + label: 'Targeted Population: Men', + description: 'Number, e.g. XX,XXX.', + // FIXME: Does this need to be left aligned? }, girls: { type: 'input', validation: 'number', - label: 'Targeted Population: Girls (under 18)', + label: 'Targeted Population: Girls (under 18)', + description: 'Number, e.g. XX,XXX.', + // FIXME: Does this need to be left aligned? }, boys: { type: 'input', validation: 'number', - label: 'Targeted Population: Boys (under 18)', + label: 'Targeted Population: Boys (under 18)', + description: 'Number, e.g. XX,XXX.', + // FIXME: Does this need to be left aligned? }, total_targeted_population: { type: 'input', validation: 'number', - label: 'Targeted Population: Total', + label: 'Targeted Population: Total', + description: 'Number, e.g. XX,XXX.', + // FIXME: Does this need to be left aligned? }, disability_people_per: { type: 'input', validation: 'number', - label: 'Estimated Percentage: People with Disability', + label: 'Estimated Percentage: People with Disability', + description: 'Percentage, e.g. XX%.', + // FIXME: Does this need to be left aligned? }, people_per_urban: { type: 'input', validation: 'number', - label: 'Estimated Percentage: Urban', + label: 'Estimated Percentage: Urban', + description: 'Percentage, e.g. XX%.', + // FIXME: Does this need to be left aligned? }, people_per_local: { type: 'input', validation: 'number', - label: 'Estimated Percentage: Rural', + label: 'Estimated Percentage: Rural', + description: 'Percentage, e.g. XX%.', + // FIXME: Does this need to be left aligned? }, displaced_people: { type: 'input', validation: 'number', - label: 'Estimated number of People on the move (if any)', + label: 'Estimated number of People on the move (if any)', + description: 'Percentage, e.g. XX%.', + // FIXME: Does this need to be left aligned? }, risk_security: { @@ -472,12 +617,16 @@ function useImportTemplateSchema() { risk: { type: 'input', validation: 'string', - label: 'Risk', + label: 'Risk', + // FIXME: Add dynamic description + // FIXME: Does this need to be left aligned? }, mitigation: { type: 'input', validation: 'string', - label: 'Mitigation action', + label: 'Mitigation action', + // FIXME: Add dynamic description + // FIXME: Does this need to be left aligned? }, }, }, @@ -487,6 +636,13 @@ function useImportTemplateSchema() { type: 'input', validation: 'textArea', label: 'Please indicate any security and safety concerns for this operation', + description: ( + 'Describe any specific security or safety threats that could impact the safety of personnel, volunteers, and communities during the operation.\n' + + '\n' + + '- Are there any security concerns related to the areas where the operation will take place (e.g., conflict zones, high-crime areas)?\n' + + '- What safety risks could impact the well-being of staff, volunteers, or beneficiaries (e.g., dangerous terrain, health risks)?\n' + + '- Are there any specific security protocols or measures that need to be established or followed during the operation?' + ), }, has_child_safeguarding_risk_analysis_assessment: { @@ -494,18 +650,20 @@ function useImportTemplateSchema() { optionsKey: '__boolean', validation: 'boolean', label: 'Has the child safeguarding risk analysis assessment been completed?', - description: 'The IFRC Child Safeguarding Risk Analysis helps Operations quickly identify and rate their key child safeguarding risks in order to reduce the risk of harm against children, as outlined as a requirement in the IFRC Child Safeguarding Policy. Here are the link to the tool and Q&A', + description: 'The IFRC Child Safeguarding Risk Analysis helps Operations quickly identify and rate their key child safeguarding risks in order to reduce the risk of harm against children, as outlined as a requirement in the IFRC Child Safeguarding Policy.', }, amount_requested: { + headingBefore: 'Planned Interventions', type: 'input', validation: 'number', - label: 'Requested Amount in CHF', - headingBefore: 'Planned Interventions', + label: 'Requested Amount in CHF', + // FIXME: Does this need to be left aligned? }, planned_interventions: { type: 'list', + // FIXME: Pass label options label: 'Add the interventions that apply', hiddenLabel: true, optionsKey: 'planned_interventions', @@ -516,18 +674,36 @@ function useImportTemplateSchema() { budget: { type: 'input', validation: 'number', - label: 'Budget', + label: 'Budget', + description: 'Budget planned to be spent on the activities under this sector.', + // FIXME: This will be visible on all repeated fields + // FIXME: Does this need to be left aligned? }, person_targeted: { type: 'input', validation: 'number', - label: 'Person targeted', + label: 'Person targeted', + description: 'Number of people planned to be reached through the activities under this sector.', + // FIXME: This will be visible on all repeated fields + // FIXME: Does this need to be left aligned? }, description: { - description: 'A list should start with an \' * \' followed by a space. There are no limits to the number of lists that can be included. \n Eg: \n * Activity XYZ \n * Activity ABC', type: 'input', validation: 'string', - label: 'List of activities', + label: 'List of activities', + description: ( + 'Specific activities that will be carried out as part of the intervention in each sector. The activities should directly address the identified needs and align with the operation’s strategic objectives.\n' + + '\n' + + '- What specific actions will be taken in this sector to meet the identified needs?\n' + + '\n' + + 'A list should start with an \' * \' followed by a space. There are no limits to the number of lists that can be included.\n' + + '\n' + + 'Example:\n' + + '* Activity A\n' + + '* Activity B\n' + + '* Activity B\n' + ), + // FIXME: Does this need to be left aligned? }, indicators: { type: 'list', @@ -540,11 +716,25 @@ function useImportTemplateSchema() { type: 'input', validation: 'string', label: 'Title', + // FIXME: Does this need to be left aligned? + description: ( + 'Start by choosing indicators from the IFRC indicator databank whenever possible. This makes it easier to compare and analyse results across all emergency operations.\n' + + '\n' + + 'Pick indicators that align with the planned activities. This ensures that the indicators accurately reflect the actions being taken on the ground. Missing important indicators could mean missed chances to showcase the positive impact of the work being done.\n' + + '\n' + + 'Keep in mind that more indicators can mean more work. Each indicator selected requires monitoring, tracking, and reporting, so be sure to choose the ones that best reflect the key outcomes of your actions.\n' + + '\n' + + 'Consider the type of indicator unit. For example, it’s often simpler and clearer to track and report on the “number of people” rather than a “percentage of people.”' + ), + // FIXME: This will be visible on all the repetitions }, target: { type: 'input', validation: 'number', label: 'Target', + // FIXME: Does this need to be left aligned? + description: 'For each indicator, set a target. This helps track progress and measure whether the operation is achieving its key objectives, making it easier to report results later on.' + // FIXME: This will be visible on all the repetitions }, }, }, @@ -554,10 +744,17 @@ function useImportTemplateSchema() { }, human_resource: { + headingBefore: 'About Support Services', type: 'input', validation: 'textArea', label: 'How many staff and volunteers will be involved in this operation. Briefly describe their role.', - headingBefore: 'About Support Services', + description: ( + 'Overview of the human resources that will be engaged in the operation, including both staff and volunteers.\n' + + '\n' + + '- How many staff members and volunteers are expected to participate?\n' + + '- What specific roles or responsibilities will they have during the operation?\n' + + '- Are there any key leadership positions or coordinators overseeing the activities?' + ), }, is_surge_personnel_deployed: { @@ -565,48 +762,80 @@ function useImportTemplateSchema() { validation: 'boolean', optionsKey: '__boolean', label: 'Will be surge personnel be deployed?', + description: 'Select Yes or No from the drop-down list.', }, surge_personnel_deployed: { type: 'input', validation: 'string', label: 'Role profile of the deployed personnel', - description: 'Please provide the role profile needed.', + description: ( + 'Describe the skills and qualifications/profile of the surge personnel or any additional staff (to be) deployed specifically for the operation.\n' + + '\n' + + '- What expertise or skill sets are required for the personnel being deployed?\n' + + '- What roles will these personnel fulfil?\n' + + '- Are there specific tasks or sectors they will be managing?' + ), }, logistic_capacity_of_ns: { type: 'input', validation: 'textArea', label: 'If there is procurement, will be done by National Society or IFRC?', - description: 'Will it be for replenishment or for distribution? If for distribution, how long is the tendering expected to take? For Cash and Voucher Assistance, what is the status of the Financial Service Provider?', + description: ( + 'Explain the responsibility for procurement activities during the operation.\n' + + '\n' + + '- Who will be responsible for procurement (National Society or IFRC)?\n' + + '- Will procurement involve local or international suppliers?\n' + + '- Will it be for replenishment or for distribution? \n' + + '- If for distribution, how long is the tendering expected to take? \n' + + '- For Cash and Voucher Assistance, what is the status of the Financial Service Provider?' + ), }, pmer: { type: 'input', validation: 'textArea', label: 'How will this operation be monitored?', - description: 'Will there be IFRC monitoring visits? How will it be deployed?', + description: ( + 'Describe the mechanisms that will be used to track the progress and effectiveness of the operation.\n' + + '\n' + + '- What systems will be used to monitor the operation\'s activities and outcomes?\n' + + '- How will progress be tracked, and who will be responsible for monitoring?\n' + + '- What indicators or milestones will be used to assess the success of the operation?\n' + + '- Will there be IFRC monitoring visits? How will it be deployed?' + ), }, communication: { type: 'input', validation: 'textArea', label: 'Please briefly explain the National Societies communication strategy for this operation.', - description: 'Will the IFRC be supporting with communication? What roles will be involved?', + description: ( + 'Describe how the National Society will manage internal and external communication throughout the operation.\n' + + '\n' + + '- What communication channels will be used to share information internally among teams and externally with stakeholders, partners, and the public?\n' + + '- How will the National Society ensure transparent and effective communication with the affected communities?\n' + + '- Is there a media strategy in place for external communication, such as press releases or social media updates?\n' + + '- Will the IFRC be supporting with communication? What roles will be involved?' + ), }, // Submission ns_request_date: { + headingBefore: 'Operational Timeframes', type: 'input', validation: 'date', label: 'Date of National Society Application', - headingBefore: 'Operational Timeframes', + // FIXME: Not sure if this is correct + description: 'DD/MM/YYY', }, operation_timeframe: { type: 'input', validation: 'number', label: 'Operation timeframe', + description: 'Indicate the number of months, e.g. 4.', }, }, }), []); diff --git a/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/utils.ts b/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/utils.ts index 5a603d488..fd89e072b 100644 --- a/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/utils.ts +++ b/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/utils.ts @@ -3,6 +3,7 @@ import { isTruthyString, } from '@togglecorp/fujs'; import xlsx, { + type CellRichTextValue, type Row, type Style, type Workbook, @@ -11,7 +12,6 @@ import xlsx, { import ifrcLogoFile from '#assets/icons/ifrc-square.png'; import { - COLOR_LIGHT_GREY, COLOR_PRIMARY_BLUE, COLOR_PRIMARY_RED, COLOR_WHITE, @@ -27,6 +27,8 @@ export function hexToArgb(hexStr: string, alphaStr = 'ff') { export const headerRowStyle: Partial