app/pages/tools/templates/conflict-resolution-framework.vue

2171 lines
76 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<!-- Export Options - Top -->
<ExportOptions
:export-data="exportData"
filename="conflict-resolution-framework"
title="Conflict Resolution" />
<div
class="template-wrapper bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100">
<!-- Document Container -->
<div class="document-page">
<div class="template-content">
<!-- Document Header -->
<div class="text-center mb-8">
<h1
class="text-3xl md:text-5xl font-bold uppercase m-0 py-4 border-t-2 border-b-2 text-black dark:text-white border-black dark:border-white"
:data-org-name="getOrgName()">
CONFLICT RESOLUTION
</h1>
</div>
<!-- Section 1: Cooperative Information -->
<div class="section-card">
<div class="space-y-6">
<UFormField label="Cooperative Name" class="form-group-large">
<UInput
v-model="formData.orgName"
placeholder="Enter your cooperative name"
size="xl"
class="w-full"
:error="validationErrors.orgName"
@input="debouncedAutoSave" />
</UFormField>
</div>
</div>
<!-- Section 2: Core Values -->
<div class="section-card">
<div class="flex flex-row justify-between items-center">
<h2 class="section-title">2. Values</h2>
<div class="flex flex-row gap-2 items-center no-print no-pdf">
<USwitch
v-model="sectionsEnabled.values"
size="sm"
label="Include this section"
:ui="{
label: 'text-xs text-neutral-700 dark:text-neutral-300',
}" />
</div>
</div>
<div class="space-y-6" v-show="sectionsEnabled.values">
<UFormField
label="Select core values (check all that apply)"
class="form-group-large">
<div class="values-grid">
<div
v-for="(value, index) in coreValues"
:key="index"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="value.checked"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<div
:class="[
'checkbox-item p-3 border border-black dark:border-white transition-all',
value.checked
? 'item-selected bg-white dark:bg-neutral-950'
: 'bg-transparent',
]">
<UCheckbox
v-model="value.checked"
:id="`core-value-${index}`"
:label="value.label"
class="w-full"
@change="autoSave" />
</div>
</div>
</div>
</UFormField>
<UFormField
label="Additional values or principles"
class="form-group-large">
<UTextarea
v-model="formData.customValues"
:rows="3"
placeholder="Add any additional values specific to your cooperative..."
size="xl"
class="w-full"
@input="debouncedAutoSave" />
</UFormField>
</div>
</div>
<!-- Section 3: Conflict Types -->
<div class="section-card">
<h2 class="section-title">3. Types of Conflicts Covered</h2>
<p class="mb-4">
Select which types of conflicts this framework will address:
</p>
<div class="checkbox-group space-y-3">
<div
v-for="(conflict, index) in conflictTypes"
:key="index"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="conflict.checked"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<div
:class="[
'checkbox-item p-3 border border-black dark:border-white transition-all',
conflict.checked
? 'item-selected bg-white dark:bg-neutral-950'
: 'bg-transparent',
]">
<UCheckbox
v-model="conflict.checked"
:id="`conflict-type-${index}`"
:label="conflict.label"
class="w-full"
@change="autoSave" />
</div>
</div>
</div>
<div v-if="validationErrors.conflictTypes" class="validation-error">
{{ validationErrors.conflictTypes }}
</div>
</div>
<!-- Section 4: Resolution Approach -->
<div class="section-card">
<h2 class="section-title">4. Primary Resolution Approach</h2>
<div class="space-y-6">
<UFormField
label="Choose your primary conflict resolution philosophy:">
<URadioGroup
v-model="formData.approach"
:items="approachOptions"
variant="card"
size="lg"
class="mt-2" />
<div v-if="validationErrors.approach" class="validation-error">
{{ validationErrors.approach }}
</div>
</UFormField>
<UFormField class="form-group-large">
<UCheckbox
v-model="formData.anonymousReporting"
id="anonymous-reporting"
label="Allow anonymous reporting"
help="Members can report issues without revealing their identity"
class="w-full"
@change="autoSave" />
</UFormField>
</div>
</div>
<!-- Section 5: Roles & Responsibilities -->
<div class="section-card">
<h2 class="section-title">5. Roles & Responsibilities</h2>
<div class="space-y-6">
<UFormField
label="Who can receive initial conflict reports? (check all that apply)"
class="form-group-large">
<div class="checkbox-group space-y-2 mt-4">
<div
v-for="(receiver, index) in reportReceivers"
:key="index"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="receiver.checked"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<div
:class="[
'checkbox-item p-3 border border-black dark:border-white transition-all',
receiver.checked
? 'item-selected bg-white dark:bg-neutral-950'
: 'bg-transparent',
]">
<UCheckbox
v-model="receiver.checked"
:id="`report-receiver-${index}`"
:label="receiver.label"
class="w-full"
@change="autoSave" />
</div>
</div>
</div>
<div
v-if="validationErrors.reportReceivers"
class="validation-error">
{{ validationErrors.reportReceivers }}
</div>
</UFormField>
<UFormField
label="Mediator/facilitator structure"
class="form-group-large">
<USelect
v-model="formData.mediatorType"
:items="mediatorTypeOptions"
placeholder="Select mediator structure..."
size="xl"
class="w-full"
:error="validationErrors.mediatorType"
@change="autoSave" />
</UFormField>
<UFormField class="form-group-large">
<UCheckbox
v-model="formData.supportPeople"
id="support-people"
label="Allow support people in mediation sessions"
help="Parties can bring a trusted person for emotional support"
class="w-full"
@change="autoSave" />
</UFormField>
</div>
</div>
<!-- Section 6: Timeline & Process -->
<div class="section-card">
<h2 class="section-title">6. Timeline & Process Steps</h2>
<div class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<UFormField
label="Initial Response Time"
class="form-group-large">
<USelect
v-model="formData.initialResponse"
:items="responseTimeOptions"
placeholder="Select response time..."
size="xl"
class="w-full"
:error="validationErrors.initialResponse"
@change="autoSave" />
</UFormField>
<UFormField
label="Target Resolution Time"
class="form-group-large">
<USelect
v-model="formData.resolutionTarget"
:items="resolutionTimeOptions"
placeholder="Select target time..."
size="xl"
class="w-full"
:error="validationErrors.resolutionTarget"
@change="autoSave" />
</UFormField>
</div>
<UFormField
label="Process Steps (select applicable steps)"
class="form-group-large">
<div class="checkbox-group mt-4 space-y-3">
<div
v-for="(step, index) in processSteps"
:key="index"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="step.checked"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<div
:class="[
'checkbox-item p-3 border border-black dark:border-white transition-all',
step.checked
? 'item-selected bg-white dark:bg-neutral-950'
: 'bg-transparent',
]">
<UCheckbox
v-model="step.checked"
:id="`process-step-${index}`"
:label="`${index + 1}. ${step.label}`"
class="w-full"
@change="autoSave" />
</div>
</div>
</div>
<div
v-if="validationErrors.processSteps"
class="validation-error">
{{ validationErrors.processSteps }}
</div>
</UFormField>
</div>
</div>
<!-- Section 7: Documentation & Privacy -->
<div class="section-card">
<div
class="section-header flex flex-row justify-between items-center">
<h2 class="section-title">7. Documentation & Privacy</h2>
<div class="flex flex-row gap-2 items-center no-print no-pdf">
<USwitch
v-model="sectionsEnabled.documentation"
size="sm"
label="Include this section"
:ui="{
label: 'text-xs text-neutral-700 dark:text-neutral-300',
}" />
</div>
</div>
<div class="space-y-6" v-show="sectionsEnabled.documentation">
<UFormField label="Documentation Level" class="form-group-large">
<USelect
v-model="formData.docLevel"
:items="docLevelOptions"
placeholder="Select documentation level..."
size="xl"
class="w-full"
@change="autoSave" />
</UFormField>
<UFormField
label="Confidentiality Level"
class="form-group-large">
<USelect
v-model="formData.confidentiality"
:items="confidentialityOptions"
placeholder="Select confidentiality level..."
size="xl"
class="w-full"
@change="autoSave" />
</UFormField>
<UFormField
label="Record Retention Period"
class="form-group-large">
<USelect
v-model="formData.retention"
:items="retentionOptions"
placeholder="Select retention period..."
size="xl"
class="w-full"
@change="autoSave" />
</UFormField>
</div>
</div>
<!-- Section 8: Consequences & Actions -->
<div class="section-card">
<h2 class="section-title">8. Consequences & Remedial Actions</h2>
<div class="space-y-6">
<UFormField
label="Available actions (check all that apply)"
class="form-group-large">
<div class="checkbox-group mt-4 space-y-3">
<div
v-for="(action, index) in availableActions"
:key="index"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="action.checked"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<div
:class="[
'checkbox-item p-3 border border-black dark:border-white transition-all',
action.checked
? 'item-selected bg-white dark:bg-neutral-950'
: 'bg-transparent',
]">
<UCheckbox
v-model="action.checked"
:id="`available-action-${index}`"
:label="action.label"
class="w-full"
@change="autoSave" />
</div>
</div>
</div>
<div
v-if="validationErrors.availableActions"
class="validation-error">
{{ validationErrors.availableActions }}
</div>
</UFormField>
<UFormField
class="form-group-large border-t border-neutral-200 dark:border-neutral-800 pt-4">
<UCheckbox
v-model="formData.appealProcess"
id="appeal-process"
label="Include appeals process"
help="Parties can request review of decisions"
class="w-full"
@change="autoSave" />
</UFormField>
</div>
</div>
<!-- Section 9: Special Circumstances -->
<div class="section-card">
<div
class="section-header flex flex-row justify-between items-center">
<h2 class="section-title">9. Special Circumstances</h2>
<div class="flex flex-row gap-2 items-center no-print no-pdf">
<USwitch
v-model="sectionsEnabled.special"
size="sm"
label="Include this section"
:ui="{
label: 'text-xs text-neutral-700 dark:text-neutral-300',
}" />
</div>
</div>
<div class="space-y-6" v-show="sectionsEnabled.special">
<div class="checkbox-group space-y-3">
<div
v-for="(circumstance, index) in specialCircumstances"
:key="index"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="circumstance.checked"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<div
:class="[
'checkbox-item p-3 border border-black dark:border-white transition-all',
circumstance.checked
? 'item-selected bg-white dark:bg-neutral-950'
: 'bg-transparent',
]">
<UCheckbox
v-model="circumstance.checked"
:id="`special-circumstance-${index}`"
:label="circumstance.label"
class="w-full"
@change="autoSave" />
</div>
</div>
</div>
</div>
</div>
<!-- Section 10: Implementation -->
<div class="section-card">
<h2 class="section-title">10. Implementation Details</h2>
<div class="space-y-6">
<UFormField
label="Training Requirements"
class="form-group-large">
<UTextarea
v-model="formData.training"
:rows="3"
class="w-full"
placeholder="Describe any training needed for member-workers, facilitators, or committee members..."
size="xl"
@input="debouncedAutoSave" />
</UFormField>
<div class="flex flex-row space-x-4">
<UFormField
label="Policy Review Schedule"
class="form-group-large">
<USelect
v-model="formData.reviewSchedule"
:items="reviewScheduleOptions"
placeholder="Select review schedule..."
size="xl"
class="w-full"
:error="validationErrors.reviewSchedule"
@change="autoSave" />
</UFormField>
<UFormField
label="How can this policy be amended?"
class="form-group-large">
<USelect
v-model="formData.amendments"
:items="amendmentOptions"
placeholder="Select amendment process..."
size="xl"
class="w-full"
:error="validationErrors.amendments"
@change="autoSave" />
</UFormField>
</div>
<div class="p-6 bg-neutral-50 dark:bg-neutral-900 rounded-lg">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<UFormField label="Framework Created Date">
<UInput
v-model="formData.createdDate"
type="date"
size="xl"
class="w-full mb-0"
@change="autoSave" />
</UFormField>
<UFormField label="Next Review Date">
<UInput
v-model="formData.reviewDate"
type="date"
size="xl"
class="w-full mb-0"
@change="autoSave" />
</UFormField>
</div>
</div>
</div>
</div>
<!-- Section 11: Reflection Process -->
<div class="section-card">
<div
class="section-header flex flex-row justify-between items-center">
<h2 class="section-title">11. Reflection Process</h2>
<div class="flex flex-row gap-2 items-center no-print no-pdf">
<USwitch
v-model="sectionsEnabled.reflection"
size="sm"
label="Include detailed reflection guidance"
:ui="{
label: 'text-xs text-neutral-700 dark:text-neutral-300',
}" />
</div>
</div>
<div class="space-y-6" v-show="sectionsEnabled.reflection">
<UFormField
label="Reflection Period Timeframe"
class="form-group-large">
<USelect
v-model="formData.reflectionPeriod"
:items="reflectionPeriodOptions"
placeholder="Select reflection timeframe..."
size="xl"
class="w-full md:w-1/2 mt-2"
@change="autoSave" />
</UFormField>
<UFormField
label="Additional Reflection Prompts"
class="form-group-large">
<UTextarea
v-model="formData.customReflectionPrompts"
:rows="4"
placeholder="Add any organization-specific reflection questions or prompts..."
size="xl"
class="w-full mt-2"
@input="debouncedAutoSave" />
</UFormField>
</div>
</div>
<!-- Section 12: Direct Resolution Guidelines -->
<div class="section-card">
<div
class="section-header flex flex-row justify-between items-center">
<h2 class="section-title">12. Direct Resolution Guidelines</h2>
<div class="flex flex-row gap-2 items-center no-print no-pdf">
<USwitch
v-model="sectionsEnabled.directResolution"
size="sm"
label="Include detailed conversation guidance"
:ui="{
label: 'text-xs text-neutral-700 dark:text-neutral-300',
}" />
</div>
</div>
<div class="space-y-6" v-show="sectionsEnabled.directResolution">
<UFormField
label="Communication Channels (select preferred escalation order)"
class="form-group-large">
<div class="checkbox-group space-y-3 mt-4">
<div
v-for="(channel, index) in communicationChannels"
:key="index"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="channel.checked"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<div
:class="[
'checkbox-item p-3 border border-black dark:border-white transition-all',
channel.checked
? 'item-selected bg-white dark:bg-neutral-950'
: 'bg-transparent',
]">
<UCheckbox
v-model="channel.checked"
:id="`comm-channel-${index}`"
:label="channel.label"
class="w-full"
@change="autoSave" />
</div>
</div>
</div>
</UFormField>
<UFormField
class="form-group-large border-t border-neutral-200 dark:border-neutral-800 pt-4">
<UCheckbox
v-model="formData.requireDirectAttempt"
id="require-direct-attempt"
label="Require direct resolution attempt before escalation"
help="Parties must try to resolve directly before filing complaints"
class="w-full"
@change="autoSave" />
</UFormField>
<UFormField
class="form-group-large border-t border-neutral-200 dark:border-neutral-800 pt-4">
<UCheckbox
v-model="formData.documentDirectResolution"
id="document-direct-resolution"
label="Require written record of direct resolution attempts"
help="Parties should document outcomes of direct conversations"
class="w-full"
@change="autoSave" />
</UFormField>
</div>
</div>
<!-- Section 13: Responsible Contact People -->
<div class="section-card">
<h2 class="section-title">
13. Responsible Contact People Structure
</h2>
<div class="space-y-6">
<UFormField
label="Internal Advisor Designation"
class="form-group-large">
<USelect
v-model="formData.internalAdvisorType"
:items="internalAdvisorOptions"
placeholder="Select internal advisor structure..."
class="w-full md:w-1/2"
size="xl"
@change="autoSave" />
</UFormField>
<UFormField
label="Member Liaison for Conflict Resolution Committee"
class="form-group-large">
<UInput
v-model="formData.staffLiaison"
placeholder="Title/role of designated member liaison"
size="xl"
class="w-full md:w-1/2"
@input="debouncedAutoSave" />
</UFormField>
<UFormField
label="Elected Board Chair Role in Conflict Resolution"
class="form-group-large">
<USelect
v-model="formData.boardChairRole"
:items="boardChairRoleOptions"
placeholder="Select board chair involvement..."
size="xl"
class="w-full md:w-1/2"
@change="autoSave" />
</UFormField>
</div>
</div>
<!-- Section 14: Formal Complaint Process -->
<div class="section-card">
<h2 class="section-title">14. Formal Complaint Requirements</h2>
<div class="space-y-6">
<UFormField
label="Required Complaint Elements"
class="form-group-large">
<div class="checkbox-group space-y-3 mt-4">
<div
v-for="(element, index) in formalComplaintElements"
:key="index"
class="relative">
<!-- Dithered shadow for selected items -->
<div
v-if="element.checked"
class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
<div
:class="[
'checkbox-item p-3 border border-black dark:border-white transition-all',
element.checked
? 'item-selected bg-white dark:bg-neutral-950'
: 'bg-transparent',
]">
<UCheckbox
v-model="element.checked"
:id="`complaint-element-${index}`"
:label="element.label"
class="w-full"
@change="autoSave" />
</div>
</div>
</div>
</UFormField>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<UFormField
label="Formal Complaint Acknowledgment Time"
class="form-group-large">
<USelect
v-model="formData.formalAcknowledgmentTime"
:items="acknowledgmentTimeOptions"
placeholder="Select acknowledgment timeframe..."
class="w-full"
size="xl"
@change="autoSave" />
</UFormField>
<UFormField
label="Formal Review Completion Time"
class="form-group-large">
<USelect
v-model="formData.formalReviewTime"
:items="reviewTimeOptions"
placeholder="Select review timeframe..."
size="xl"
class="w-full"
@change="autoSave" />
</UFormField>
</div>
<UFormField class="form-group-large">
<UCheckbox
v-model="formData.requireExternalAdvice"
id="require-external-advice"
label="Require external legal advice for complex complaints"
help="Seek external expertise for multi-party or member-coordinator complaints"
class="w-full"
@change="autoSave" />
</UFormField>
</div>
</div>
<!-- Section 15: Settlement & Documentation -->
<div class="section-card">
<h2 class="section-title">15. Settlement & Documentation</h2>
<div class="space-y-6">
<UFormField class="form-group-large">
<UCheckbox
v-model="formData.requireMinutesOfSettlement"
id="minutes-of-settlement"
label="Require 'Minutes of Settlement' for resolved complaints"
help="Agreements must be documented in writing and signed by both parties"
class="w-full"
@change="autoSave" />
</UFormField>
<div class="flex flex-row space-x-4">
<UFormField
label="Settlement Confidentiality Level"
class="form-group-large">
<USelect
v-model="formData.settlementConfidentiality"
:items="confidentialityOptions"
placeholder="Select confidentiality level..."
size="xl"
class="w-full"
@change="autoSave" />
</UFormField>
<UFormField
label="Conflict Resolution File Retention"
class="form-group-large">
<USelect
v-model="formData.conflictFileRetention"
:items="retentionOptions"
placeholder="Select retention period..."
size="xl"
class="w-full"
@change="autoSave" />
</UFormField>
</div>
</div>
</div>
<!-- Section 16: External Resources -->
<div class="section-card">
<div
class="section-header flex flex-row justify-between items-center">
<h2 class="section-title">16. External Resources & Redress</h2>
<div class="flex flex-row gap-2 items-center no-print no-pdf">
<USwitch
v-model="sectionsEnabled.externalResources"
size="sm"
label="Include external resource information"
:ui="{
label: 'text-xs text-neutral-700 dark:text-neutral-300',
}" />
</div>
</div>
<div class="space-y-6" v-show="sectionsEnabled.externalResources">
<UFormField class="form-group-large">
<UCheckbox
v-model="formData.includeHumanRights"
id="include-human-rights"
label="Include Human Rights Commission information"
help="Reference external discrimination complaint options"
class="w-full"
@change="autoSave" />
</UFormField>
<UFormField
label="Additional External Resources"
class="form-group-large">
<UTextarea
v-model="formData.additionalResources"
:rows="3"
class="w-full"
placeholder="List any other external resources, legal aid contacts, or specialized support services..."
size="xl"
@input="debouncedAutoSave" />
</UFormField>
<UFormField
label="Acknowledgment/Attribution"
class="form-group-large">
<UTextarea
v-model="formData.acknowledgments"
:rows="2"
placeholder="Credit any sources used in developing this policy..."
class="w-full"
size="xl"
@input="debouncedAutoSave" />
</UFormField>
</div>
</div>
<!-- Document Preview Section -->
<div class="no-print" v-if="showPreview">
<div
class="border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-950 p-5 rounded-lg shadow-sm">
<div class="flex flex-row justify-between items-center mb-4">
<h2 class="section-title m-0">📄 Policy Document Preview</h2>
<UButton
size="sm"
variant="ghost"
@click="showPreview = false"
title="Hide preview">
</UButton>
</div>
<div
class="policy-preview prose prose-neutral dark:prose-invert max-w-none px-6 py-5 bg-neutral-50 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-900 rounded-md max-h-[600px] overflow-y-auto text-left"
v-html="markdownToHtml(generatePolicyDocument())"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Export Options - Bottom -->
<ExportOptions
:export-data="exportData"
filename="conflict-resolution-framework"
title="Conflict Resolution Framework" />
</div>
</template>
<script setup>
import { ref, watch, computed } from "vue";
// Import centralized coop info
const { coopInfo, updateCoopInfo, getOrgName } = useCoopInfo();
definePageMeta({
layout: false,
});
useHead({
title: "Conflict Resolution Framework - Co-op Pay & Value Tool",
meta: [
{
name: "description",
content:
"Create a customized conflict resolution framework for your cooperative or organization with restorative justice principles.",
},
],
});
const showPreview = ref(false);
const approachOptions = [
{
value: "restorative",
label: "Restorative/Loving Justice",
description:
"Focus on healing, understanding root causes, and repairing relationships",
},
{
value: "mediation",
label: "Mediation-First",
description: "Neutral third-party facilitates dialogue between parties",
},
{
value: "progressive",
label: "Progressive Discipline",
description: "Clear escalation steps with defined consequences",
},
{
value: "hybrid",
label: "Hybrid Approach",
description: "Combines multiple approaches based on conflict type",
},
];
const mediatorTypeOptions = [
"Internal trained mediators",
"External professional mediators",
"Rotating member facilitators",
"Standing committee",
"Decided case-by-case",
];
const responseTimeOptions = [
"Within 24 hours",
"Within 48 hours",
"Within 72 hours",
"Within 1 week",
"Within 2 weeks",
];
const resolutionTimeOptions = [
"1 week",
"2 weeks",
"30 days",
"60 days",
"90 days",
];
const docLevelOptions = [
"Minimal - outcomes only",
"Standard - key points and decisions",
"Comprehensive - detailed records",
];
const confidentialityOptions = [
"Strict - only parties and facilitators",
"Need-to-know basis",
"Transparent to membership",
];
const retentionOptions = ["1 year", "3 years", "5 years", "Permanent"];
const reviewScheduleOptions = [
"Every 6 months",
"Annually",
"Every 2 years",
"As needed",
];
const amendmentOptions = [
"Full consensus required",
"Consent process (no objections)",
"2/3 majority vote",
"Simple majority vote",
];
const reflectionPeriodOptions = [
"Before any escalation",
"24-48 hours before complaint",
"1 week before formal process",
"Optional but encouraged",
"Not required",
];
const internalAdvisorOptions = [
"Single elected advisor",
"Rotating member representatives",
"External neutral advisor",
"Committee-designated advisor",
"Trained member facilitator",
];
const boardChairRoleOptions = [
"First contact for coordinator complaints",
"Appeals reviewer",
"Participates in collective decision",
"Advisory role only",
"Not involved in conflicts",
];
const acknowledgmentTimeOptions = [
"Within 24 hours",
"Within 48 hours",
"Within 1 week",
"Within 2 weeks",
];
const reviewTimeOptions = [
"2 weeks",
"1 month",
"6 weeks",
"2 months",
"3 months",
];
const sectionsEnabled = ref({
values: true,
documentation: true,
special: true,
reflection: true,
directResolution: true,
externalResources: true,
});
const communicationChannels = ref([
{ label: "Asynchronous text (Slack, email)", checked: true },
{ label: "Synchronous text (planned chat session)", checked: true },
{ label: "Audio call or huddle", checked: true },
{ label: "Video conference", checked: true },
{ label: "In-person meeting", checked: false },
]);
const formalComplaintElements = ref([
{ label: "The complainant's name", checked: true },
{ label: "The respondent's name", checked: true },
{
label: "Detailed information about the issue (what, where, when)",
checked: true,
},
{ label: "Details of all prior resolution attempts", checked: true },
{
label: "The specific outcome(s) the complainant is seeking",
checked: true,
},
{ label: "Supporting documentation or evidence", checked: false },
{ label: "Names of potential witnesses", checked: false },
]);
const coreValues = ref([
{ label: "Mutual Care", checked: true },
{ label: "Transparency", checked: true },
{ label: "Accountability", checked: false },
{ label: "Consent", checked: false },
{ label: "Anti-Oppression", checked: false },
{ label: "Restorative Justice", checked: false },
{ label: "Collective Liberation", checked: false },
{ label: "Accessibility", checked: false },
]);
const conflictTypes = ref([
{ label: "Interpersonal disputes between member-workers", checked: true },
{ label: "Code of Conduct violations", checked: true },
{ label: "Work allocation and responsibility disagreements", checked: true },
{ label: "Decision-making process conflicts", checked: true },
{ label: "Harassment or discrimination", checked: false },
{ label: "Member-owner responsibility disputes", checked: false },
{ label: "Collective ownership tensions", checked: false },
{ label: "External organization disputes", checked: false },
{ label: "Financial disagreements", checked: false },
]);
const reportReceivers = ref([
{ label: "Designated conflict resolution committee", checked: true },
{ label: "Any elected board member", checked: false },
{ label: "Administrative Coordinator(s)", checked: false },
{ label: "Designated member liaison", checked: false },
{ label: "Any member-worker", checked: false },
]);
const processSteps = ref([
{ label: "Initial report/complaint received", checked: true },
{ label: "Acknowledgment sent to complainant", checked: true },
{ label: "Initial assessment by designated party", checked: true },
{ label: "Informal resolution attempted", checked: true },
{ label: "Formal investigation if needed", checked: false },
{ label: "Mediation/facilitated dialogue", checked: true },
{ label: "Agreement/resolution documented", checked: true },
{ label: "Follow-up check-in", checked: false },
]);
const availableActions = ref([
{ label: "Verbal warning", checked: true },
{ label: "Written warning", checked: true },
{ label: "Required training/education", checked: true },
{ label: "Temporary suspension", checked: true },
{ label: "Role/responsibility changes", checked: false },
{ label: "Mediated agreement", checked: false },
{ label: "Removal from the cooperative", checked: true },
{ label: "Restorative circle/process", checked: false },
]);
const specialCircumstances = ref([
{
label: "Include immediate removal protocol for safety threats",
checked: true,
},
{
label: "Reference external reporting options (Human Rights Tribunal, etc.)",
checked: true,
},
{ label: "Include anti-retaliation provisions", checked: true },
{ label: "Include trauma-informed approach language", checked: false },
]);
const formData = ref({
orgName: "",
orgType: "",
memberCount: "",
customValues: "",
approach: "restorative",
anonymousReporting: true,
mediatorType: "Internal trained mediators",
supportPeople: true,
initialResponse: "Within 1 week",
resolutionTarget: "30 days",
docLevel: "Standard - key points and decisions",
confidentiality: "Need-to-know basis",
retention: "5 years",
appealProcess: true,
training: "",
reviewSchedule: "Annually",
amendments: "Consent process (no objections)",
createdDate: new Date().toISOString().split("T")[0],
reviewDate: "",
// New fields for enhanced sections
reflectionPeriod: "Before any escalation",
customReflectionPrompts: "",
requireDirectAttempt: true,
documentDirectResolution: true,
internalAdvisorType: "Single Board-appointed advisor",
staffLiaison: "",
boardChairRole: "First contact for coordinator complaints",
formalAcknowledgmentTime: "Within 1 week",
formalReviewTime: "1 month",
requireExternalAdvice: true,
requireMinutesOfSettlement: true,
settlementConfidentiality: "Need-to-know basis",
conflictFileRetention: "5 years",
includeHumanRights: true,
additionalResources: "",
acknowledgments: "",
});
// Validation logic
const validationErrors = ref({});
// Auto-save functionality
const autoSave = () => {
// Clear validation errors when users start correcting fields
clearValidationErrors();
if (typeof window !== "undefined") {
const dataToSave = {
formData: formData.value,
coreValues: coreValues.value,
conflictTypes: conflictTypes.value,
reportReceivers: reportReceivers.value,
processSteps: processSteps.value,
availableActions: availableActions.value,
specialCircumstances: specialCircumstances.value,
communicationChannels: communicationChannels.value,
formalComplaintElements: formalComplaintElements.value,
sectionsEnabled: sectionsEnabled.value,
};
localStorage.setItem(
"conflict-resolution-framework-data",
JSON.stringify(dataToSave)
);
}
};
const clearValidationErrors = () => {
validationErrors.value = {};
};
// Simple Markdown to HTML converter for preview
const markdownToHtml = (markdown) => {
return (
markdown
// Headers
.replace(/^### (.*$)/gm, "<h3>$1</h3>")
.replace(/^## (.*$)/gm, "<h2>$1</h2>")
.replace(/^# (.*$)/gm, "<h1>$1</h1>")
// Bold and italic
.replace(/\*\*\*(.*?)\*\*\*/g, "<strong><em>$1</em></strong>")
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
.replace(/\*(.*?)\*/g, "<em>$1</em>")
// Lists
.replace(/^- (.*$)/gm, "<li>$1</li>")
.replace(/(<li>.*<\/li>)/s, "<ul>$1</ul>")
.replace(/<\/li>\s*<ul>/g, "</li>")
.replace(/<\/ul>\s*<li>/g, "<li>")
// Tables (basic support)
.replace(/^\|(.+)\|$/gm, (_, content) => {
const cells = content.split("|").map((cell) => cell.trim());
if (cells.every((cell) => cell.match(/^-+$/))) {
return ""; // Skip separator rows
}
const cellTags = cells
.map((cell) =>
cell.startsWith("**") && cell.endsWith("**")
? `<th>${cell.slice(2, -2)}</th>`
: `<td>${cell}</td>`
)
.join("");
return `<tr>${cellTags}</tr>`;
})
.replace(/(<tr>.*<\/tr>)/s, "<table>$1</table>")
.replace(/<\/tr>\s*<table>/g, "</tr>")
.replace(/<\/table>\s*<tr>/g, "<tr>")
// Line breaks
.replace(/\n\n/g, "</p><p>")
.replace(/^(.+)$/gm, "<p>$1</p>")
// Horizontal rules
.replace(/^---$/gm, "<hr>")
// Clean up extra paragraphs around headers and lists
.replace(/<p><h([1-6])>/g, "<h$1>")
.replace(/<\/h([1-6])><\/p>/g, "</h$1>")
.replace(/<p><ul>/g, "<ul>")
.replace(/<\/ul><\/p>/g, "</ul>")
.replace(/<p><table>/g, "<table>")
.replace(/<\/table><\/p>/g, "</table>")
.replace(/<p><hr><\/p>/g, "<hr>")
.replace(/<p><\/p>/g, "")
);
};
// Debounced auto-save
const debouncedAutoSave = debounce(autoSave, 300);
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Sync with centralized coop info
watch(
() => coopInfo.value,
(newCoopInfo) => {
if (newCoopInfo.cooperativeName) {
formData.value.orgName = newCoopInfo.cooperativeName;
}
},
{ deep: true, immediate: true }
);
// Update centralized store when org name changes
watch(
() => formData.value.orgName,
(newOrgName) => {
if (newOrgName && newOrgName !== coopInfo.value.cooperativeName) {
updateCoopInfo({ cooperativeName: newOrgName });
}
}
);
// Watch for changes and auto-save
watch(
[
formData,
coreValues,
conflictTypes,
reportReceivers,
processSteps,
availableActions,
specialCircumstances,
communicationChannels,
formalComplaintElements,
sectionsEnabled,
],
autoSave,
{ deep: true }
);
// Comprehensive generatePolicyDocument function with procedural structure
const generatePolicyDocument = () => {
const cooperativeName = formData.value.orgName || "[Cooperative Name]";
let content = `# ${cooperativeName} Conflict Resolution Policy\n\n`;
content += `*Framework Created: ${
formData.value.createdDate || new Date().toISOString().split("T")[0]
}*\n`;
if (formData.value.reviewDate) {
content += `*Next Review: ${formData.value.reviewDate}*\n`;
}
content += `\n---\n\n`;
// PURPOSE SECTION
content += `## Purpose\n\n`;
content += `Disagreements in groups are par for the course. But ignoring conflicts, or managing them poorly, can deeply harm individuals and our whole community.\n\n`;
content += `Addressing conflict head-on is **a way of caring for each other**.\n\n`;
content += `This policy aims to offer a straightforward, consistently enforced, and transparent approach to resolving conflicts and disputes that may emerge in relation to ${cooperativeName}'s programs, governance, or the actions of its members.\n\n`;
// GUIDING PRINCIPLES
content += `## Guiding Principles\n\n`;
content += `- All parties to a complaint will **actively participate** and strive to achieve a **collaborative** outcome at the earliest possible stage of the process\n`;
content += `- Information about a complaint will only be given to parties directly involved and others on a need-to-know basis\n`;
content += `- Parties will be provided with clear and understandable reasons for complaint decisions\n`;
content += `- Complaints will be dealt with promptly and resolved as quickly as possible\n`;
content += `- Review of complaints will be fair, impartial, and respectful, allowing all parties to have their perspectives heard\n`;
content += `- The review will be thorough and as detailed as possible based on the information provided\n`;
content += `- The process will be accessible and clearly communicated to all members\n\n`;
// Add selected values if enabled
if (sectionsEnabled.value.values) {
const selectedValues = coreValues.value.filter((v) => v.checked);
if (selectedValues.length > 0) {
content += `Additionally, this framework is guided by our core values:\n\n`;
selectedValues.forEach((value) => {
content += `- **${value.label}**\n`;
});
content += `\n`;
}
if (formData.value.customValues) {
content += `${formData.value.customValues}\n\n`;
}
}
// DEFINITIONS SECTION
content += `## Definitions\n\n`;
content += `- **Conflict/Dispute**: Ongoing experiences of tension and misunderstandings, often leading to interpersonal discord. These terms are used interchangeably.\n`;
content += `- **Complainant**: The individual lodging a complaint against another party, policy, or practice.\n`;
content += `- **Respondent**: An individual against whom a complaint has been made.\n`;
// Add definitions based on selections
const selectedReceivers = reportReceivers.value.filter((r) => r.checked);
if (selectedReceivers.length > 0) {
content += `- **Responsible Contact People**: Those accountable for assisting in conflict resolution (${selectedReceivers
.map((r) => r.label)
.join(
", "
)}). They act as neutral implementers of this policy, not advocates.\n`;
}
if (formData.value.internalAdvisorType) {
content += `- **Internal Advisor**: ${formData.value.internalAdvisorType} who facilitates the conflict resolution process as a neutral intermediary.\n`;
}
if (formData.value.supportPeople) {
content += `- **Support People**: Individuals not connected to the conflict whom parties may choose to have present for emotional support during mediation.\n`;
}
content += `\n`;
// POLICY ROUTING TABLE
content += `## Which Policy Applies?\n\n`;
content += `| **Who Can File** | **Type of Complaint** | **Policy to Use** | **Initial Contact** |\n`;
content += `|------------------|----------------------|-------------------|--------------------|\n`;
const selectedConflictTypes = conflictTypes.value.filter((c) => c.checked);
selectedConflictTypes.forEach((conflict) => {
let policy = "This policy";
let contact = selectedReceivers[0]?.label || "Designated contact";
if (
conflict.label.includes("Harassment") ||
conflict.label.includes("discrimination")
) {
policy = "Code of Conduct / Human Rights";
if (formData.value.includeHumanRights) {
contact += " or Human Rights Tribunal";
}
} else if (conflict.label.includes("Code of Conduct")) {
policy = "Code of Conduct";
}
content += `| Members | ${conflict.label} | ${policy} | ${contact} |\n`;
});
if (formData.value.anonymousReporting) {
content += `| Any party | Anonymous reports | This policy | Anonymous reporting system |\n`;
}
content += `\n`;
// RESOLUTION APPROACH
const approachDescriptions = {
restorative:
"We use a **restorative/loving justice** approach that focuses on healing, understanding root causes, and repairing relationships rather than punishment.",
mediation:
"We use a **mediation-first** approach where neutral third-party facilitators help parties dialogue and find solutions.",
progressive:
"We use **progressive discipline** with clear escalation steps and defined consequences for violations.",
hybrid:
"We use a **hybrid approach** that combines multiple methods based on the type and severity of conflict.",
};
if (
formData.value.approach &&
approachDescriptions[formData.value.approach]
) {
content += `## Our Approach\n\n`;
content += `${approachDescriptions[formData.value.approach]}\n\n`;
content += `We do our best to resolve conflicts at the lowest possible escalation step (direct resolution), but agree to escalate conflicts (to assisted resolution) if they are not resolved.\n\n`;
}
// REFLECTION PROCESS (if enabled)
if (sectionsEnabled.value.reflection) {
content += `## Reflection Process\n\n`;
content += `Before engaging in direct resolution, we encourage taking time for reflection:\n\n`;
content += `1. **Set aside time to think** through what happened. What was the other person's behaviour? How did it affect you? *Distinguish other people's **actions** from your **feelings** about them.*\n`;
content += `2. **Consider uncertainties** or misunderstandings that may have occurred.\n`;
content += `3. **Distinguish disagreement from personal hostility.** Disagreement and dissent are part of healthy discussion. Hostility is not.\n`;
content += `4. **Use your personal support system** (friends, family, therapist, etc.) to work through and clarify your perspective.\n`;
content += `5. **Ask yourself** what part you played, how you could have behaved differently, and what your needs are.\n\n`;
if (formData.value.customReflectionPrompts) {
content += `### Additional Reflection Prompts\n\n`;
content += `${formData.value.customReflectionPrompts}\n\n`;
}
const reflectionTiming =
formData.value.reflectionPeriod || "Before any escalation";
content += `**Reflection Timing:** ${reflectionTiming}\n\n`;
}
// DIRECT RESOLUTION (if enabled)
if (sectionsEnabled.value.directResolution) {
content += `## Direct Resolution\n\n`;
content += `A *direct resolution* process occurs when individuals communicate their concerns and work together to resolve disputes without filing a formal complaint.\n\n`;
content += `### Have a Conversation\n\n`;
content += `When there is a disagreement, the involved people should first **communicate with each other** about their concerns.\n\n`;
content += `1. **Choose a time and place** to meet that is private and agreeable to both.\n`;
content += `2. **Allow reasonable time** for the conversation.\n`;
content += `3. **The point is mutual understanding**, not determining who is right or wrong.\n`;
content += `4. **Express thoughts and feelings directly** using "I" statements and active listening.\n`;
content += `5. **Communicate your wants and needs** and make offers and requests.\n`;
content += `6. **Learn for the future** ask what can be done to prevent this from recurring.\n`;
if (formData.value.documentDirectResolution) {
content += `7. **Keep a written record** of the resolution agreed to by both parties.\n\n`;
} else {
content += `\n`;
}
// Communication Channels
const selectedChannels = communicationChannels.value.filter(
(c) => c.checked
);
if (selectedChannels.length > 0) {
content += `### Escalating Communication Bandwidth\n\n`;
content += `Whenever a misunderstanding or conflict arises, **escalate the bandwidth of the channel**:\n\n`;
selectedChannels.forEach((channel, index) => {
content += `${index + 1}. ${channel.label}\n`;
});
content += `\n`;
}
if (formData.value.requireDirectAttempt) {
content += `> **Note:** Direct resolution must be attempted before escalating to assisted resolution, unless safety concerns prevent this.\n\n`;
}
}
// RECEIVING REPORTS SECTION
content += `## Receiving Reports\n\n`;
content += `### Document the Initial Incident Report\n\n`;
content += `Collect the following information and enter it in the Incident Log:\n\n`;
content += `| Field | Information to Collect |\n`;
content += `|-------|------------------------|\n`;
content += `| **Participant Name** | Name of individual(s) involved |\n`;
content += `| **Issue/Violation** | Brief description of the behavior or conflict |\n`;
content += `| **Date & Time** | When the incident occurred |\n`;
content += `| **Circumstances** | Context or situation surrounding the incident |\n`;
content += `| **Others Involved** | Names of any witnesses or additional participants |\n`;
content += `| **Conversation Notes** | Summary of discussion with the complainant |\n\n`;
content += `*Gather this information from the complainant do not "interview" witnesses unless they approach staff.*\n\n`;
// SUPPORTING THE COMPLAINANT
content += `### Supporting the Complainant\n\n`;
content += `Follow these steps to help the complainant feel safe:\n\n`;
content += `1. **Provide private space** for discussion (in digital spaces, use DM/private channels)\n`;
content += `2. **Allow the complainant to decide** if further action should be taken\n`;
content += `3. **Explain the process** walk them through next steps per this policy\n`;
content += `4. **Assure confidentiality** their identity will not be disclosed without permission\n`;
content += `5. **Confirm follow-up** they will be informed about any actions taken\n\n`;
// ASSISTED RESOLUTION
content += `## Assisted Resolution\n\n`;
content += `If direct resolution doesn't work, parties can request assistance from a responsible contact person.\n\n`;
// Process Steps
const selectedSteps = processSteps.value.filter((s) => s.checked);
if (selectedSteps.length > 0) {
content += `### Resolution Process Steps\n\n`;
selectedSteps.forEach((step, index) => {
content += `${index + 1}. ${step.label}\n`;
});
content += `\n`;
}
// Responsible Contact People
content += `### Responsible Contact People\n\n`;
if (selectedReceivers.length > 0) {
content += `**Initial Contact Options:**\n`;
selectedReceivers.forEach((receiver) => {
content += `- ${receiver.label}\n`;
});
content += `\n`;
}
// Contact People Structure
if (formData.value.internalAdvisorType) {
content += `**Internal Advisor:** ${formData.value.internalAdvisorType}\n`;
}
if (formData.value.staffLiaison) {
content += `**Member Liaison:** ${formData.value.staffLiaison}\n`;
}
if (formData.value.boardChairRole) {
content += `**Board Chair Role:** ${formData.value.boardChairRole}\n`;
}
if (
formData.value.internalAdvisorType ||
formData.value.staffLiaison ||
formData.value.boardChairRole
) {
content += `\n`;
}
// Mediator Structure
if (formData.value.mediatorType) {
content += `**Mediation Structure:** ${formData.value.mediatorType}\n`;
if (formData.value.supportPeople) {
content += `**Support People:** Parties may bring a trusted person for emotional support during mediation sessions.\n`;
}
content += `\n`;
}
// Timeline
content += `### Response Timeline\n\n`;
content += `| Stage | Timeframe |\n`;
content += `|-------|----------|\n`;
if (formData.value.initialResponse) {
content += `| Initial Response | ${formData.value.initialResponse} |\n`;
}
if (formData.value.resolutionTarget) {
content += `| Target Resolution | ${formData.value.resolutionTarget} |\n`;
}
content += `\n`;
// COMMITTEE MEETING PROCEDURES (if committee-based)
if (
formData.value.mediatorType &&
formData.value.mediatorType.toLowerCase().includes("committee")
) {
content += `## Committee Meeting Procedures\n\n`;
content += `### Before the Meeting\n`;
content += `- Notify respondent of complaint\n`;
content += `- Allow respondent to provide their perspective\n`;
content += `- Schedule meeting within ${
formData.value.initialResponse || "specified timeframe"
}\n\n`;
content += `### During the Meeting\n`;
content += `Committee members should review the incident report and discuss:\n`;
content += `- What happened?\n`;
content += `- What are we doing about it?\n`;
content += `- Who is implementing the decision?\n`;
content += `- When will it be implemented?\n\n`;
content += `*Neither the complainant nor respondent should attend the deliberation.*\n\n`;
content += `### After the Meeting\n`;
content += `- Communicate decision to all parties\n`;
content += `- Document all communications\n`;
content += `- Follow up with complainant about outcomes\n`;
content += `- Prepare report for organizational records\n\n`;
}
// RESPONSE PROCEDURES MATRIX
content += `## Response Procedures\n\n`;
const selectedActions = availableActions.value.filter((a) => a.checked);
if (selectedActions.length > 0) {
content += `### Response Matrix\n\n`;
content += `| Issue Severity | Possible Response | Documentation Required |\n`;
content += `|----------------|-------------------|------------------------|\n`;
// Create severity-based responses
const hasVerbal = selectedActions.some((a) => a.label.includes("Verbal"));
const hasWritten = selectedActions.some((a) => a.label.includes("Written"));
const hasSuspension = selectedActions.some((a) =>
a.label.includes("suspension")
);
const hasRemoval = selectedActions.some(
(a) => a.label.includes("Removal") || a.label.includes("removal")
);
if (hasVerbal) {
content += `| First occurrence, minor | Verbal warning | Update incident log |\n`;
}
if (hasWritten) {
content += `| Repeated behavior | Written warning | Formal documentation |\n`;
}
if (hasSuspension) {
content += `| Serious violation | Temporary suspension | Full investigation report |\n`;
}
if (hasRemoval) {
content += `| Severe/safety threat | Immediate removal | Complete documentation + notifications |\n`;
}
content += `\n`;
content += `### Available Remedial Actions\n\n`;
selectedActions.forEach((action) => {
content += `- ${action.label}\n`;
});
content += `\n`;
}
if (formData.value.appealProcess) {
content += `### Appeals Process\n\n`;
content += `Parties may request review of decisions through our appeals process. Appeals must be submitted in writing within 30 days of the original decision.\n\n`;
}
// FORMAL COMPLAINTS
content += `## Formal Complaints\n\n`;
content += `If assisted resolution does not result in an acceptable outcome, a formal complaint may be filed in writing.\n\n`;
// Required Elements
const selectedElements = formalComplaintElements.value.filter(
(e) => e.checked
);
if (selectedElements.length > 0) {
content += `### Written Complaint Requirements\n\n`;
content += `The formal complaint must include:\n\n`;
selectedElements.forEach((element, index) => {
content += `${index + 1}. ${element.label}\n`;
});
content += `\n`;
}
// Formal Process Timeline
content += `### Formal Process Timeline\n\n`;
content += `| Stage | Timeframe |\n`;
content += `|-------|----------|\n`;
if (formData.value.formalAcknowledgmentTime) {
content += `| Acknowledgment of complaint | ${formData.value.formalAcknowledgmentTime} |\n`;
}
if (formData.value.formalReviewTime) {
content += `| Review completion | ${formData.value.formalReviewTime} |\n`;
}
content += `\n`;
if (formData.value.requireExternalAdvice) {
content += `> **External Expertise:** For complex complaints involving multiple parties or organizational leaders, external legal advice will be sought.\n\n`;
}
// PREVENTING RETALIATION (if anti-retaliation is selected)
const hasAntiRetaliation = specialCircumstances.value.some(
(c) => c.checked && c.label.toLowerCase().includes("retaliation")
);
if (hasAntiRetaliation) {
content += `## Preventing Retaliation\n\n`;
content += `**CRITICAL:** The privacy and safety of the complainant is paramount.\n\n`;
content += `- **DO NOT** share details of the incident without express permission from the complainant\n`;
content += `- **DO NOT** reveal the complainant's identity to the respondent or others\n`;
content += `- **MONITOR** for any retaliatory behavior following a complaint\n`;
content += `- **DOCUMENT** any instances of suspected retaliation\n`;
content += `- **TREAT** retaliation as a separate, serious violation requiring immediate action\n\n`;
}
// SETTLEMENT & DOCUMENTATION
content += `## Settlement & Documentation\n\n`;
if (formData.value.requireMinutesOfSettlement) {
content += `### Minutes of Settlement\n`;
content += `Any resolution must be documented in "Minutes of Settlement" that:\n`;
content += `- Clearly state the agreed-upon resolution\n`;
content += `- Include commitments from all parties\n`;
content += `- Are signed by both complainant and respondent\n`;
content += `- Are kept according to our confidentiality standards\n\n`;
}
// REGARDING APOLOGIES
content += `### Regarding Apologies\n\n`;
content += `We do not require or facilitate apologies unless explicitly requested by the complainant.\n\n`;
content += `- Forced apologies can constitute continued harassment\n`;
content += `- If offered, apologies should be brief and relayed through the mediator\n`;
content += `- Apologies should not require a response from the recipient\n`;
content += `- Pressing unwanted apologies may result in further disciplinary action\n\n`;
// DOCUMENTATION & PRIVACY
if (sectionsEnabled.value.documentation) {
content += `## Documentation & Privacy\n\n`;
content += `### Record Management\n\n`;
content += `| Record Type | Retention Period | Access Level | Storage Location |\n`;
content += `|-------------|------------------|--------------|------------------|\n`;
content += `| Initial incident reports | Permanent | Committee only | Secure database |\n`;
content += `| Investigation notes | ${
formData.value.retention || "5 years"
} | Designated roles | Confidential files |\n`;
content += `| Resolution agreements | ${
formData.value.conflictFileRetention ||
formData.value.retention ||
"5 years"
} | Parties + committee | Secure archive |\n`;
content += `| Committee meeting minutes | ${
formData.value.retention || "5 years"
} | Committee members | Meeting records |\n\n`;
if (formData.value.docLevel) {
content += `**Documentation Level:** ${formData.value.docLevel}\n`;
}
if (formData.value.confidentiality) {
content += `**General Confidentiality:** ${formData.value.confidentiality}\n`;
}
if (formData.value.settlementConfidentiality) {
content += `**Settlement Confidentiality:** ${formData.value.settlementConfidentiality}\n`;
}
content += `\n`;
}
// SPECIAL CIRCUMSTANCES (if enabled)
if (sectionsEnabled.value.special) {
const selectedCircumstances = specialCircumstances.value.filter(
(c) => c.checked
);
if (selectedCircumstances.length > 0) {
content += `## Special Circumstances\n\n`;
const hasImmediateRemoval = selectedCircumstances.some(
(c) =>
c.label.toLowerCase().includes("immediate removal") ||
c.label.toLowerCase().includes("safety")
);
if (hasImmediateRemoval) {
content += `### Immediate Safety Threats\n`;
content += `When anyone's physical safety is threatened:\n`;
content += `1. Immediately remove the offender from the space\n`;
content += `2. Implement permanent ban if warranted\n`;
content += `3. Notify relevant authorities if required\n`;
content += `4. Document all actions taken\n`;
content += `5. Inform stakeholders as appropriate while protecting victim privacy\n\n`;
}
const hasTraumaInformed = selectedCircumstances.some((c) =>
c.label.toLowerCase().includes("trauma")
);
if (hasTraumaInformed) {
content += `### Trauma-Informed Approach\n`;
content += `All conflict resolution processes will incorporate trauma-informed principles:\n`;
content += `- Recognize the impact of trauma on behavior\n`;
content += `- Prioritize physical and emotional safety\n`;
content += `- Provide choices and restore control\n`;
content += `- Collaborate rather than prescribe solutions\n`;
content += `- Build on strengths and resilience\n\n`;
}
}
}
// EXTERNAL RESOURCES (if enabled)
if (sectionsEnabled.value.externalResources) {
content += `## External Resources & Redress\n\n`;
if (formData.value.includeHumanRights) {
content += `### Human Rights Complaints\n`;
content += `Individuals who are not satisfied with the outcome of a harassment or discrimination complaint may file a complaint with:\n`;
content += `- [Canadian Human Rights Commission](https://www.chrc-ccdp.gc.ca/eng)\n`;
content += `- Provincial human rights tribunal\n`;
content += `- Other relevant regulatory bodies\n\n`;
}
if (formData.value.additionalResources) {
content += `### Additional Resources\n\n`;
content += `${formData.value.additionalResources}\n\n`;
}
}
// IMPLEMENTATION & TRAINING
content += `## Implementation\n\n`;
if (formData.value.training) {
content += `### Training Requirements\n\n`;
content += `${formData.value.training}\n\n`;
}
content += `### Policy Management\n\n`;
content += `| Aspect | Details |\n`;
content += `|--------|----------|\n`;
if (formData.value.reviewSchedule) {
content += `| Review Schedule | ${formData.value.reviewSchedule} |\n`;
}
if (formData.value.amendments) {
content += `| Amendment Process | ${formData.value.amendments} |\n`;
}
content += `| Last Updated | ${
formData.value.createdDate || new Date().toISOString().split("T")[0]
} |\n`;
if (formData.value.reviewDate) {
content += `| Next Review | ${formData.value.reviewDate} |\n`;
}
content += `\n`;
// Acknowledgments
if (formData.value.acknowledgments) {
content += `### Acknowledgments\n\n`;
content += `${formData.value.acknowledgments}\n\n`;
}
return content;
};
// Export data for the ExportOptions component - structured to match ExportOptions expectations
const exportData = computed(() => {
// Get selected values for arrays
const selectedCoreValues = coreValues.value
.filter((v) => v.checked)
.map((v) => v.label);
const selectedConflictTypes = conflictTypes.value
.filter((c) => c.checked)
.map((c) => c.label);
const selectedProcessSteps = processSteps.value
.filter((s) => s.checked)
.map((s) => s.label);
const selectedActions = availableActions.value
.filter((a) => a.checked)
.map((a) => a.label);
const selectedReceivers = reportReceivers.value
.filter((r) => r.checked)
.map((r) => r.label);
const selectedChannels = communicationChannels.value
.filter((c) => c.checked)
.map((c) => c.label);
const selectedComplaintElements = formalComplaintElements.value
.filter((e) => e.checked)
.map((e) => e.label);
const selectedCircumstances = specialCircumstances.value
.filter((c) => c.checked)
.map((c) => c.label);
return {
section: "conflict-resolution-framework",
// Add the generated policy document content for exports
content: generatePolicyDocument(),
// Enhanced formData with processed arrays
formData: {
...formData.value,
// Add processed arrays as lists for the formatter
coreValuesList: selectedCoreValues,
conflictTypesList: selectedConflictTypes,
processStepsList: selectedProcessSteps,
actionsList: selectedActions,
receiversList: selectedReceivers,
channelsList: selectedChannels,
complaintElementsList: selectedComplaintElements,
circumstancesList: selectedCircumstances,
},
sectionsEnabled: sectionsEnabled.value,
reportReceivers: reportReceivers.value,
coreValues: coreValues.value,
conflictTypes: conflictTypes.value,
processSteps: processSteps.value,
availableActions: availableActions.value,
specialCircumstances: specialCircumstances.value,
communicationChannels: communicationChannels.value,
formalComplaintElements: formalComplaintElements.value,
exportedAt: new Date().toISOString(),
};
});
</script>
<style scoped>
@reference "tailwindcss";
/* Template-specific styles that aren't in main.css */
/* Progress bar */
.progress-bar {
width: 100%;
height: 6px;
background: #e0e0e0;
border-radius: 3px;
margin-bottom: 30px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4caf50, #66bb6a);
border-radius: 3px;
transition: width 0.3s ease;
}
/* Quick start section */
.quick-start {
background: #f0f7ff;
border: 1px solid #d0e3ff;
border-radius: 8px;
padding: 20px;
margin-bottom: 30px;
}
.quick-start h3 {
margin: 0 0 10px 0;
font-size: 1.2rem;
color: #333;
}
.preset-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 15px;
}
.preset-btn {
padding: 8px 16px;
border: 2px solid #0066cc;
background: white;
color: #0066cc;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
font-size: 0.9rem;
}
.preset-btn:hover {
background: #0066cc;
color: white;
}
.preset-btn.selected {
background: #0066cc;
color: white;
}
/* Styling for sections when toggled off */
.section-card:has(.space-y-6[style*="display: none"]) {
opacity: 0.7;
}
.section-card:has(.space-y-6[style*="display: none"]) .section-title {
color: #666;
}
/* Toggle styling */
.toggle-section {
display: flex;
align-items: center;
gap: 8px;
}
.toggle {
position: relative;
width: 48px;
height: 24px;
background: #ccc;
border-radius: 24px;
cursor: pointer;
transition: background 0.3s;
}
.toggle.active {
background: #4caf50;
}
.toggle-slider {
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: transform 0.3s;
}
.toggle.active .toggle-slider {
transform: translateX(24px);
}
/* Moved validation-error to main.css for consistency */
/* Preview styling - template-specific */
.preview-controls {
text-align: center;
margin: 2rem 0;
}
.preview-btn {
background: #6366f1;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: background 0.2s;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.preview-btn:hover {
background: #5856eb;
}
.close-preview-btn {
background: #ef4444;
color: white;
border: none;
padding: 0.25rem 0.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1.2rem;
line-height: 1;
transition: background 0.2s;
}
.close-preview-btn:hover {
background: #dc2626;
}
.policy-preview {
line-height: 1.6;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji" !important;
font-size: 0.95rem;
}
.policy-preview h1 {
font-size: 2rem;
font-weight: bold;
color: #111827;
margin-bottom: 1.5rem;
border-bottom: 2px solid #e5e7eb;
padding-bottom: 0.5rem;
}
.policy-preview h2 {
font-size: 1.5rem;
font-weight: 600;
color: #374151;
margin-top: 2rem;
margin-bottom: 1rem;
}
.policy-preview h3 {
font-size: 1.25rem;
font-weight: 600;
color: #4b5563;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.policy-preview p {
margin-bottom: 1.25rem;
color: #374151;
}
.policy-preview p:last-child {
margin-bottom: 0;
}
.policy-preview :is(p, span, div, li, ol, ul, table, th, td, blockquote) {
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji" !important;
}
.policy-preview a {
color: #2563eb;
text-decoration: underline;
}
html.dark .policy-preview a {
color: #93c5fd;
}
.policy-preview blockquote {
border-left: 4px solid #e5e7eb;
margin: 1rem 0;
padding: 0.5rem 1rem;
color: #4b5563;
background: #f9fafb;
}
html.dark .policy-preview blockquote {
border-left-color: #374151;
background: #0f1115;
color: #9ca3af;
}
.policy-preview code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace !important;
background: #f3f4f6;
padding: 0.1rem 0.35rem;
}
html.dark .policy-preview code {
background: #111827;
}
.policy-preview ul {
margin: 1rem 0;
padding-left: 1.5rem;
}
.policy-preview li {
margin-bottom: 0.5rem;
color: #374151;
}
.policy-preview table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
background: white;
}
.policy-preview th,
.policy-preview td {
border: 1px solid #d1d5db;
padding: 0.75rem;
text-align: left;
}
.policy-preview th {
background: #f3f4f6;
font-weight: 600;
color: #374151;
}
.policy-preview hr {
border: none;
border-top: 1px solid #e5e7eb;
margin: 2rem 0;
}
.policy-preview strong {
font-weight: 600;
color: #111827;
}
.policy-preview em {
font-style: italic;
}
</style>