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

1751 lines
57 KiB
Vue

<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="formData.orgName || 'Organization'">
CONFLICT RESOLUTION
</h1>
</div>
<!-- Section 1: Organization Information -->
<div class="section-card">
<h2 class="section-title">1. Organization Information</h2>
<div class="space-y-6">
<UFormField label="Organization Name" class="form-group-large">
<UInput
v-model="formData.orgName"
placeholder="Enter your organization name"
size="xl"
class="w-full"
:error="validationErrors.orgName"
@input="debouncedAutoSave" />
</UFormField>
<div class="flex flex-row gap-4 space-x-4">
<UFormField label="Organization Type" class="form-group-large">
<USelect
v-model="formData.orgType"
:items="orgTypeOptions"
placeholder="Select organization type..."
size="xl"
class="w-full"
:error="validationErrors.orgType"
@change="autoSave" />
</UFormField>
<UFormField
label="Number of Members/Staff"
class="form-group-large">
<UInput
v-model="formData.memberCount"
type="number"
min="2"
placeholder="e.g., 5"
size="xl"
:error="validationErrors.memberCount"
@change="autoSave" />
</UFormField>
</div>
</div>
</div>
<!-- Section 2: Core Values -->
<div class="section-card">
<div class="flex flex-row justify-between items-center">
<h2 class="section-title">2. Guiding Principles & 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"
@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 organization..."
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"
@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"
@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"
@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"
: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"
@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}`"
@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"
@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"
@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"
@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 members, 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"
@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"
@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"
@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="Staff Liaison for Conflict Resolution Committee"
class="form-group-large">
<UInput
v-model="formData.staffLiaison"
placeholder="Title/role of designated staff liaison"
size="xl"
class="w-full md:w-1/2"
@input="debouncedAutoSave" />
</UFormField>
<UFormField
label="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"
@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 staff/director complaints"
@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"
@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"
@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, onMounted } from "vue";
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.",
},
],
});
// Import PDF export composable
const { exportToPDF } = usePdfExportBasic();
const showPreview = ref(false);
const copySuccess = ref(false);
// Options for dropdowns (using simple string arrays like the working membership template)
const orgTypeOptions = [
"Worker Cooperative",
"Consumer Cooperative",
"Nonprofit",
"Collective",
"Community Group",
"Other",
];
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 Board-appointed advisor",
"Rotating Board members",
"External neutral advisor",
"Committee-designated advisor",
"Staff member with training",
];
const boardChairRoleOptions = [
"First contact for ED complaints",
"Appeals reviewer",
"Final decision maker",
"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-Based", 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 members", checked: true },
{ label: "Code of Conduct violations", checked: true },
{ label: "Harassment or discrimination", checked: false },
{ label: "Work performance issues", checked: false },
{ label: "Conflicts of interest", 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 board member", checked: false },
{ label: "Executive Director(s)", checked: false },
{ label: "Designated staff liaison", checked: false },
{ label: "Any member", 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 organization", 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 ED 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({});
const validateForm = () => {
const errors = {};
// Required text fields
if (!formData.value.orgName?.trim()) {
errors.orgName = "Organization name is required";
}
if (!formData.value.orgType?.trim()) {
errors.orgType = "Organization type is required";
}
if (!formData.value.memberCount?.toString().trim()) {
errors.memberCount = "Number of members/staff is required";
}
if (!formData.value.approach?.trim()) {
errors.approach = "Primary resolution approach is required";
}
if (!formData.value.mediatorType?.trim()) {
errors.mediatorType = "Mediator/facilitator structure is required";
}
if (!formData.value.initialResponse?.trim()) {
errors.initialResponse = "Initial response time is required";
}
if (!formData.value.resolutionTarget?.trim()) {
errors.resolutionTarget = "Target resolution time is required";
}
if (!formData.value.reviewSchedule?.trim()) {
errors.reviewSchedule = "Policy review schedule is required";
}
if (!formData.value.amendments?.trim()) {
errors.amendments = "Amendment process is required";
}
// Required checkbox groups (must have at least one checked)
const checkedConflictTypes = conflictTypes.value.filter(
(item) => item.checked
);
if (checkedConflictTypes.length === 0) {
errors.conflictTypes = "Please select at least one type of conflict";
}
// Note: Guiding Principles & Values section is optional - no validation needed
const checkedReportReceivers = reportReceivers.value.filter(
(item) => item.checked
);
if (checkedReportReceivers.length === 0) {
errors.reportReceivers = "Please select at least one report receiver";
}
const checkedProcessSteps = processSteps.value.filter((item) => item.checked);
if (checkedProcessSteps.length === 0) {
errors.processSteps = "Please select at least one process step";
}
const checkedAvailableActions = availableActions.value.filter(
(item) => item.checked
);
if (checkedAvailableActions.length === 0) {
errors.availableActions = "Please select at least one available action";
}
// Note: Special circumstances section is optional - no validation needed
validationErrors.value = errors;
const isValid = Object.keys(errors).length === 0;
// Provide user feedback
if (isValid) {
alert("✅ Form is complete and ready for export!");
} else {
const errorCount = Object.keys(errors).length;
alert(
`❌ Please complete ${errorCount} required field${
errorCount > 1 ? "s" : ""
} before exporting.`
);
}
return isValid;
};
// Completion percentage computation
const completionPercentage = computed(() => {
const allInputs = [
formData.value.orgName,
formData.value.orgType,
formData.value.memberCount,
formData.value.approach,
formData.value.mediatorType,
formData.value.initialResponse,
formData.value.resolutionTarget,
formData.value.reviewSchedule,
formData.value.amendments,
];
const checkboxInputs = [
...coreValues.value,
...conflictTypes.value,
...reportReceivers.value,
...processSteps.value,
...availableActions.value,
...specialCircumstances.value,
];
const filledInputs = allInputs.filter(
(val) => val && val.toString().trim() !== ""
).length;
const checkedBoxes = checkboxInputs.filter((item) => item.checked).length;
const totalFields = allInputs.length + checkboxInputs.length;
const completedFields = filledInputs + checkedBoxes;
return Math.round((completedFields / totalFields) * 100);
});
// Load saved data
const loadSavedData = () => {
if (process.client) {
const saved = localStorage.getItem("conflict-resolution-framework-data");
if (saved) {
try {
const parsedData = JSON.parse(saved);
// Load form data
if (parsedData.formData) {
formData.value = { ...formData.value, ...parsedData.formData };
}
// Load checkbox arrays
if (parsedData.coreValues) coreValues.value = parsedData.coreValues;
if (parsedData.conflictTypes)
conflictTypes.value = parsedData.conflictTypes;
if (parsedData.reportReceivers)
reportReceivers.value = parsedData.reportReceivers;
if (parsedData.processSteps)
processSteps.value = parsedData.processSteps;
if (parsedData.availableActions)
availableActions.value = parsedData.availableActions;
if (parsedData.specialCircumstances)
specialCircumstances.value = parsedData.specialCircumstances;
if (parsedData.communicationChannels)
communicationChannels.value = parsedData.communicationChannels;
if (parsedData.formalComplaintElements)
formalComplaintElements.value = parsedData.formalComplaintElements;
if (parsedData.sectionsEnabled)
sectionsEnabled.value = parsedData.sectionsEnabled;
} catch (error) {
console.error("Error loading saved data:", error);
}
}
}
};
// Auto-save functionality
const autoSave = () => {
// Clear validation errors when users start correcting fields
clearValidationErrors();
if (process.client) {
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, (match, 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);
};
}
// Watch for changes and auto-save
watch(
[
formData,
coreValues,
conflictTypes,
reportReceivers,
processSteps,
availableActions,
specialCircumstances,
communicationChannels,
formalComplaintElements,
sectionsEnabled,
],
autoSave,
{ deep: true }
);
// Export data for the ExportOptions component
const exportData = computed(() => ({
formData: formData.value,
orgName: formData.value.orgName || "Organization",
orgType: formData.value.orgType,
memberCount: formData.value.memberCount,
sectionsEnabled: sectionsEnabled.value,
coreValues: formData.value.coreValues,
principles: formData.value.principles,
policies: {
memberInvolvement: formData.value.memberInvolvement,
communicationGuidelines: formData.value.communicationGuidelines,
processSteps: formData.value.processSteps,
escalationCriteria: formData.value.escalationCriteria,
mediation: formData.value.mediation,
finalDecision: formData.value.finalDecision,
learning: formData.value.learning,
emergencyProcedures: formData.value.emergencyProcedures,
annualReview: formData.value.annualReview,
},
exportedAt: new Date().toISOString(),
section: "conflict-resolution-framework",
}));
</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>