app/components/ExportOptions.vue

1674 lines
61 KiB
Vue

<template>
<div class="export-options" :class="containerClass">
<div class="export-content">
<div class="export-section">
<div class="export-buttons">
<UButton
@click="copyToClipboard"
class="export-btn"
:disabled="isProcessing"
ref="copyButton">
<UIcon name="i-heroicons-clipboard" />
<span>Copy as Text</span>
<UIcon
v-if="showCopySuccess"
name="i-heroicons-check"
class="success-icon" />
</UButton>
<UButton
@click="downloadAsMarkdown"
class="export-btn"
:disabled="isProcessing"
ref="downloadButton">
<UIcon name="i-heroicons-arrow-down-tray" />
<span>Download Markdown</span>
<UIcon
v-if="showDownloadSuccess"
name="i-heroicons-check"
class="success-icon" />
</UButton>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
interface Props {
exportData: any;
filename?: string;
title?: string;
containerClass?: string;
}
const props = withDefaults(defineProps<Props>(), {
filename: "export",
title: "Export Data",
containerClass: "centered",
});
const isProcessing = ref(false);
const showCopySuccess = ref(false);
const showDownloadSuccess = ref(false);
const copyButton = ref<HTMLButtonElement>();
const downloadButton = ref<HTMLButtonElement>();
// Success feedback animation
const showSuccessFeedback = (
buttonRef: Ref<HTMLButtonElement | undefined>,
successRef: Ref<boolean>
) => {
try {
successRef.value = true;
// Add checkmark overlay animation if button exists
if (
buttonRef?.value &&
typeof buttonRef.value.classList?.add === "function"
) {
const button = buttonRef.value;
button.classList.add("success-state");
setTimeout(() => {
try {
if (button && typeof button.classList?.remove === "function") {
button.classList.remove("success-state");
}
} catch (e) {
console.warn("Could not remove success-state class:", e);
}
}, 2000);
}
// Reset success state
setTimeout(() => {
try {
successRef.value = false;
} catch (e) {
console.warn("Could not reset success state:", e);
}
}, 2000);
} catch (error) {
console.warn("Success feedback failed:", error);
// Still show success state even if animation fails
successRef.value = true;
setTimeout(() => {
try {
successRef.value = false;
} catch (e) {
// Ignore
}
}, 2000);
}
};
const copyToClipboard = async () => {
if (isProcessing.value) return;
isProcessing.value = true;
try {
const textContent = extractTextContent();
// Modern clipboard API
if (navigator.clipboard && window.isSecureContext) {
try {
await navigator.clipboard.writeText(textContent);
try {
showSuccessFeedback(copyButton, showCopySuccess);
} catch (feedbackError) {
console.warn(
"Success feedback failed, but copy succeeded:",
feedbackError
);
}
return;
} catch (clipboardError) {
console.warn(
"Modern clipboard failed, trying fallback:",
clipboardError
);
}
}
// Fallback method
const textarea = document.createElement("textarea");
textarea.value = textContent;
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
textarea.style.top = "-9999px";
textarea.setAttribute("readonly", "");
document.body.appendChild(textarea);
// Select the text
textarea.select();
textarea.setSelectionRange(0, 99999); // For mobile devices
let copySucceeded = false;
try {
const successful = document.execCommand("copy");
copySucceeded = successful;
if (!successful) {
throw new Error("execCommand copy returned false");
}
} catch (execError) {
console.error("execCommand failed:", execError);
throw new Error("Both clipboard methods failed");
} finally {
try {
document.body.removeChild(textarea);
} catch (cleanupError) {
console.warn("Could not clean up textarea:", cleanupError);
}
}
// Show success feedback only if copy actually succeeded
if (copySucceeded) {
try {
showSuccessFeedback(copyButton, showCopySuccess);
} catch (feedbackError) {
console.warn(
"Success feedback failed, but copy succeeded:",
feedbackError
);
}
}
} catch (error) {
console.error("Copy failed:", error);
alert("Copy failed. Please try the Download Markdown button instead.");
} finally {
isProcessing.value = false;
}
};
const downloadAsMarkdown = () => {
if (isProcessing.value) return;
isProcessing.value = true;
try {
const content = convertToMarkdown();
downloadFile(content, `${props.filename}.md`, "text/markdown");
showSuccessFeedback(downloadButton, showDownloadSuccess);
} catch (error) {
console.error("Markdown download failed:", error);
alert("Download failed. Please try again.");
} finally {
isProcessing.value = false;
}
};
const extractTextContent = (): string => {
const today = new Date().toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
let content = `${props.title.toUpperCase()}\n${"=".repeat(
props.title.length
)}\n\nExported ${today}\n\n`;
// Convert data to readable text format
if (props.exportData) {
content += formatDataAsText(props.exportData);
}
return content;
};
const convertToMarkdown = (): string => {
const today = new Date().toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
let content = `# ${props.title}\n\n*Exported ${today}*\n\n`;
// Convert data to markdown format
if (props.exportData) {
content += formatDataAsMarkdown(props.exportData);
}
return content;
};
const formatDataAsText = (data: any): string => {
// Special handling for different template types
if (data.section === "tech-charter") {
return formatTechCharterAsText(data);
} else if (data.section === "membership-agreement") {
return formatMembershipAgreementAsText(data);
} else if (data.section === "conflict-resolution-framework") {
return formatConflictResolutionAsText(data);
} else if (data.section === "decision-framework") {
return formatDecisionFrameworkAsText(data);
}
if (Array.isArray(data)) {
return data
.map((item, index) => `${index + 1}. ${formatObjectAsText(item)}`)
.join("\n");
} else if (typeof data === "object" && data !== null) {
return formatObjectAsText(data);
}
return String(data);
};
const formatDataAsMarkdown = (data: any): string => {
// Special handling for different template types
if (data.section === "tech-charter") {
return formatTechCharterAsMarkdown(data);
} else if (data.section === "membership-agreement") {
return formatMembershipAgreementAsMarkdown(data);
} else if (data.section === "conflict-resolution-framework") {
return formatConflictResolutionAsMarkdown(data);
} else if (data.section === "decision-framework") {
return formatDecisionFrameworkAsMarkdown(data);
}
if (Array.isArray(data)) {
return data
.map((item, index) => `${index + 1}. ${formatObjectAsMarkdown(item)}`)
.join("\n\n");
} else if (typeof data === "object" && data !== null) {
return formatObjectAsMarkdown(data);
}
return String(data);
};
const formatTechCharterAsText = (data: any): string => {
let content = `PURPOSE\n-------\n\n${
data.charterPurpose || "[No purpose defined]"
}\n\n`;
const principleWeights = data.principleWeights || {};
const nonNegotiables = data.nonNegotiables || [];
const principles = data.principles || [];
const constraints = data.constraints || {};
const sortedWeights = data.sortedWeights || [];
const selectedPrinciples = Object.keys(principleWeights).filter(
(p) => principleWeights[p] > 0
);
if (
selectedPrinciples.filter((p) => !nonNegotiables.includes(p)).length > 0
) {
content += `CORE PRINCIPLES\n---------------\n\n`;
selectedPrinciples
.filter((p) => !nonNegotiables.includes(p))
.forEach((pId) => {
const principle = principles.find((p: any) => p.id === pId);
if (principle) {
content += `${principle.name}\n`;
}
});
content += "\n";
}
if (nonNegotiables.length > 0) {
content += `NON-NEGOTIABLE REQUIREMENTS\n---------------------------\n\nAny vendor failing these requirements is automatically disqualified.\n\n`;
nonNegotiables.forEach((pId: string) => {
const principle = principles.find((p: any) => p.id === pId);
if (principle) {
content += `${principle.name}\n`;
}
});
content += "\n";
}
content += `TECHNICAL CONSTRAINTS\n---------------------\n\n`;
content += `• Authentication: ${constraints.sso || "Not specified"}\n`;
content += `• Hosting: ${constraints.hosting || "Not specified"}\n`;
if (
constraints.integrations &&
Array.isArray(constraints.integrations) &&
constraints.integrations.length > 0
) {
content += `• Required Integrations: ${constraints.integrations.join(
", "
)}\n`;
}
content += `• Support Level: ${constraints.support || "Not specified"}\n`;
content += `• Migration Timeline: ${
constraints.timeline || "Not specified"
}\n\n`;
if (sortedWeights.length > 0) {
content += `EVALUATION RUBRIC\n-----------------\n\nScore each vendor option using these weighted criteria (0-5 scale):\n\n`;
sortedWeights.forEach((principle: any) => {
content += `${principle.name} (Weight: ${
principleWeights[principle.id] || 0
})\n${principle.rubricDescription || "No description provided"}\n\n`;
});
}
return content;
};
const formatTechCharterAsMarkdown = (data: any): string => {
let content = `## Purpose\n\n${
data.charterPurpose || "[No purpose defined]"
}\n\n`;
const principleWeights = data.principleWeights || {};
const nonNegotiables = data.nonNegotiables || [];
const principles = data.principles || [];
const constraints = data.constraints || {};
const sortedWeights = data.sortedWeights || [];
const selectedPrinciples = Object.keys(principleWeights).filter(
(p) => principleWeights[p] > 0
);
if (
selectedPrinciples.filter((p) => !nonNegotiables.includes(p)).length > 0
) {
content += `## Core Principles\n\n`;
selectedPrinciples
.filter((p) => !nonNegotiables.includes(p))
.forEach((pId) => {
const principle = principles.find((p: any) => p.id === pId);
if (principle) {
content += `- ${principle.name}\n`;
}
});
content += "\n";
}
if (nonNegotiables.length > 0) {
content += `## Non-Negotiable Requirements\n\n**Any vendor failing these requirements is automatically disqualified.**\n\n`;
nonNegotiables.forEach((pId: string) => {
const principle = principles.find((p: any) => p.id === pId);
if (principle) {
content += `- **${principle.name}**\n`;
}
});
content += "\n";
}
content += `## Technical Constraints\n\n`;
content += `- Authentication: ${constraints.sso || "Not specified"}\n`;
content += `- Hosting: ${constraints.hosting || "Not specified"}\n`;
if (
constraints.integrations &&
Array.isArray(constraints.integrations) &&
constraints.integrations.length > 0
) {
content += `- Required Integrations: ${constraints.integrations.join(
", "
)}\n`;
}
content += `- Support Level: ${constraints.support || "Not specified"}\n`;
content += `- Migration Timeline: ${
constraints.timeline || "Not specified"
}\n\n`;
if (sortedWeights.length > 0) {
content += `## Evaluation Rubric\n\nScore each vendor option using these weighted criteria (0-5 scale):\n\n`;
content += `| Criterion | Description | Weight |\n`;
content += `|-----------|-------------|--------|\n`;
sortedWeights.forEach((principle: any) => {
content += `| ${principle.name || "Unknown"} | ${
principle.rubricDescription || "No description"
} | ${principleWeights[principle.id] || 0} |\n`;
});
}
return content;
};
const formatObjectAsText = (obj: any): string => {
if (!obj || typeof obj !== "object") return String(obj);
return Object.entries(obj)
.filter(
([key, value]) => value !== null && value !== undefined && key !== "id"
)
.map(([key, value]) => {
const formattedKey = key
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase());
if (typeof value === "object") {
return `${formattedKey}: ${JSON.stringify(value)}`;
}
return `${formattedKey}: ${value}`;
})
.join("\n");
};
const formatObjectAsMarkdown = (obj: any): string => {
if (!obj || typeof obj !== "object") return String(obj);
return Object.entries(obj)
.filter(
([key, value]) => value !== null && value !== undefined && key !== "id"
)
.map(([key, value]) => {
const formattedKey = key
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase());
if (typeof value === "object") {
return `**${formattedKey}**: \`${JSON.stringify(value)}\``;
}
return `**${formattedKey}**: ${value}`;
})
.join(" \n");
};
// Membership Agreement formatting - Complete document with all static and dynamic content
const formatMembershipAgreementAsText = (data: any): string => {
const formData = data.formData || {};
const cooperativeName =
data.cooperativeName || formData.cooperativeName || "the cooperative";
let content = "";
// Section 1: Who We Are
content += `1. WHO WE ARE\n-------------\n\n`;
content += `Cooperative Name: ${
formData.cooperativeName || "[Enter cooperative name]"
}\n`;
content += `Date Established: ${
formData.dateEstablished || "[Enter date]"
}\n\n`;
content += `Our Purpose:\n${
formData.purpose ||
"[Write 1-2 sentences about what your cooperative does and why it exists]"
}\n\n`;
content += `Our Core Values:\n${
formData.coreValues ||
"[List 3-5 values that guide everything you do. Examples: mutual care, creative sustainability, economic justice, collective liberation]"
}\n\n`;
content += `Current Members:\n`;
if (formData.members && formData.members.length > 0) {
formData.members.forEach((member: any, index: number) => {
content += `${index + 1}. ${member.name || "[Name]"}`;
if (member.email) content += ` (${member.email})`;
if (member.joinDate) content += ` - Joined: ${member.joinDate}`;
if (member.role) content += ` - Role: ${member.role}`;
content += `\n`;
});
} else {
content += `1. [Member Name] ([Email]) - Joined: [Date] - Role: [Optional Role]\n`;
}
content += `\n`;
// Section 2: Membership
content += `2. MEMBERSHIP\n-------------\n\n`;
content += `Who Can Be a Member:\nAny person who:\n\n`;
content += `${
formData.memberRequirements ||
"Shares our values and purpose\nContributes labour to the cooperative (by doing actual work, not just investing money)\nCommits to collective decision-making\nParticipates in governance responsibilities"
}\n\n`;
content += `Becoming a Member:\n`;
content += `New members join through a consent process, which means existing members must agree that adding this person won't harm the cooperative.\n\n`;
content += `1. Trial period of ${
formData.trialPeriodMonths || 3
} months working together\n`;
content += `2. Values alignment conversation\n`;
content += `3. Optional - Equal buy-in contribution of $${
formData.buyInAmount || "[amount]"
} (can be paid over time or waived based on need)\n\n`;
content += `Leaving the Cooperative:\n`;
content += `Members can leave anytime with ${
formData.noticeDays || 30
} days notice. ${cooperativeName} will:\n\n`;
content += `• Pay out their share of any surplus within ${
formData.surplusPayoutDays || 30
} days\n`;
content += `• Return their buy-in contribution within ${
formData.buyInReturnDays || 90
} days\n`;
content += `• Maintain respectful ongoing relationships when possible\n\n`;
// Section 3: How We Make Decisions
content += `3. HOW WE MAKE DECISIONS\n------------------------\n\n`;
content += `Primary Decision Framework: ${
data.decisionFrameworkName ||
"Consent-Based - No one objects strongly enough to block"
}\n\n`;
const frameworkDetails = data.decisionFrameworkDetails || {};
if (frameworkDetails.practicalDescription) {
content += `${frameworkDetails.practicalDescription}\n\n`;
} else {
content += `We use consent, not consensus. This means we move forward when no one has a principled objection that would harm our organization. An objection must explain how the proposal would contradict our values or threaten our sustainability.\n\n`;
}
content += `Day-to-Day Decisions:\nDecisions under $${
formData.dayToDayLimit || 100
} can be made by any member. Just tell others what you did at the next meeting.\n\n`;
content += `Regular Decisions:\nDecisions between $${
formData.regularDecisionMin || 100
} and $${
formData.regularDecisionMax || 1000
} need consent from members present at a meeting (minimum 2 members).\n\n`;
content += `Major Decisions:\nThese require consent from all members:\n`;
content += `• Adding or removing members\n`;
content += `• Changing this agreement\n`;
content += `• Taking on debt over $${formData.majorDebtThreshold || 5000}\n`;
content += `• Fundamental changes to our purpose or structure\n`;
content += `• Dissolution of ${cooperativeName}\n\n`;
content += `Meeting Structure:\n`;
content += `• Regular meetings happen ${
formData.meetingFrequency || "weekly"
}\n`;
content += `• Emergency meetings need ${
formData.emergencyNoticeHours || 24
} hours notice\n`;
content += `• We rotate who facilitates meetings\n`;
content += `• Decisions and reasoning get documented in shared notes\n\n`;
// Section 4: Money and Labour
content += `4. MONEY AND LABOUR\n-------------------\n\n`;
content += `Equal Ownership:\nEach member owns an equal share of ${cooperativeName}, regardless of hours worked or tenure.\n\n`;
content += `Paying Ourselves:\n`;
const payPolicy = formData.payPolicy || "equal-pay";
const payPolicyName = payPolicy
.replace("-", " ")
.replace(/\b\w/g, (l: string) => l.toUpperCase());
content += `Pay Policy: ${data.payPolicyName || payPolicyName}\n\n`;
if (payPolicy === "equal-pay") {
content += `All members receive equal compensation regardless of role or hours worked.\n`;
if (formData.baseRate)
content += `• Base rate: $${formData.baseRate}/hour for all members\n`;
if (formData.monthlyDraw)
content += `• Equal monthly draw of $${formData.monthlyDraw} per member\n`;
} else if (payPolicy === "hours-weighted") {
content += `Compensation is proportional to hours worked by each member.\n`;
content += `• Hourly rate: $${formData.hourlyRate || 25}/hour\n`;
content += `• Members track their hours and are paid accordingly\n`;
content += `• Minimum hours commitment may apply\n`;
} else if (payPolicy === "needs-weighted") {
content += `Compensation is allocated based on each member's individual financial needs.\n`;
content += `• Members declare their minimum monthly needs\n`;
content += `• Available payroll is distributed proportionally to cover needs\n`;
content += `• Regular needs assessment and adjustment process\n`;
content += `• Minimum guaranteed amount: $${
formData.minGuaranteedPay || 1000
}/month\n`;
}
content += `\nPayment Schedule:\n`;
content += `• Paid on the ${
data.paymentDayLabel || formData.paymentDay || 15
} of each month\n`;
content += `• Surplus (profit) distributed equally every ${
formData.surplusFrequency || "quarter"
}\n\n`;
content += `Work Expectations:\n`;
content += `• Target hours per week: ${
formData.targetHours || 40
} (flexible based on capacity)\n`;
content += `• We explicitly reject crunch culture\n`;
content += `• Members communicate their capacity openly\n`;
content += `• We adjust workload collectively when someone needs reduced hours\n\n`;
content += `Financial Transparency:\n`;
content += `• All members can access all financial records anytime\n`;
content += `• Monthly financial check-ins at meetings\n`;
content += `• Quarterly reviews of our runway (how many months we can operate)\n\n`;
// Section 5: Roles and Responsibilities
content += `5. ROLES AND RESPONSIBILITIES\n-----------------------------\n\n`;
content += `Rotating Roles:\n`;
content += `We rotate operational roles every ${
formData.roleRotationMonths || 6
} months. Current roles include:\n\n`;
content += `${
formData.rotatingRoles ||
"Financial coordinator (handles bookkeeping, not financial decisions)\nMeeting facilitator\nExternal communications\nOthers"
}\n\n`;
content += `Shared Responsibilities:\n`;
content += `All members participate in:\n\n`;
content += `${
formData.sharedResponsibilities ||
"Governance and decision-making\nStrategic planning\nMutual support and care"
}\n\n`;
// Section 6: Conflict and Care
content += `6. CONFLICT AND CARE\n--------------------\n\n`;
content += `When Conflict Happens:\n`;
content += `1. Direct conversation between parties (if comfortable)\n`;
content += `2. Mediation with another member\n`;
content += `3. Full group discussion with structured process\n`;
content += `4. External mediation if needed\n\n`;
content += `Care Commitments:\n`;
content += `• We check in about capacity and wellbeing regularly\n`;
content += `• We honour diverse access needs\n`;
content += `• We maintain flexibility for life circumstances\n`;
content += `• We contribute to mutual aid when members face hardship\n\n`;
// Section 7: Changing This Agreement
content += `7. CHANGING THIS AGREEMENT\n--------------------------\n\n`;
const frameworkLabel =
data.decisionFrameworkLabel || "consent-based decision";
const structuralReq =
data.structuralChangeRequirement || "full member consent";
content += `This is a living document. We review it every ${
formData.reviewFrequency || "year"
} and update it through our ${frameworkLabel} process. Small clarifications can happen anytime; structural changes need ${structuralReq}.\n\n`;
// Section 8: If We Need to Close
content += `8. IF WE NEED TO CLOSE\n----------------------\n\n`;
content += `If ${cooperativeName} dissolves:\n`;
content += `1. Pay all debts and obligations\n`;
content += `2. Return member contributions\n`;
content += `3. Distribute remaining assets equally among members\n`;
if (formData.assetDonationTarget) {
content += `4. Or donate remaining assets to ${formData.assetDonationTarget}\n`;
}
content += `\n`;
// Section 9: Legal Registration
content += `9. LEGAL REGISTRATION\n---------------------\n\n`;
if (formData.isLegallyRegistered) {
content += `Legal Structure: ${
formData.legalStructure ||
"[Cooperative corporation, LLC, partnership, etc.]"
}\n`;
content += `Registered in: ${
formData.registeredLocation || "[State/Province]"
}\n`;
content += `Fiscal Year-End: ${formData.fiscalYearEndMonth || "December"} ${
formData.fiscalYearEndDay || 31
}\n\n`;
content += `This agreement works alongside but doesn't replace our legal incorporation documents. Where they conflict, we follow the law but work to align our legal structure with our values.\n\n`;
} else {
const thisCooperative =
cooperativeName === "the cooperative"
? "This cooperative"
: cooperativeName;
content += `${thisCooperative} operates as an informal collective. If we decide to register legally in the future, we'll update this section with our legal structure details.\n\n`;
}
return content;
};
const formatMembershipAgreementAsMarkdown = (data: any): string => {
const formData = data.formData || {};
const cooperativeName =
data.cooperativeName || formData.cooperativeName || "the cooperative";
let content = "";
// Section 1: Who We Are
content += `## 1. Who We Are\n\n`;
content += `**Cooperative Name:** ${
formData.cooperativeName || "[Enter cooperative name]"
} \n`;
content += `**Date Established:** ${
formData.dateEstablished || "[Enter date]"
} \n\n`;
content += `**Our Purpose:** \n${
formData.purpose ||
"[Write 1-2 sentences about what your cooperative does and why it exists]"
}\n\n`;
content += `**Our Core Values:** \n${
formData.coreValues ||
"[List 3-5 values that guide everything you do. Examples: mutual care, creative sustainability, economic justice, collective liberation]"
}\n\n`;
content += `**Current Members:**\n\n`;
if (formData.members && formData.members.length > 0) {
formData.members.forEach((member: any, index: number) => {
content += `${index + 1}. **${member.name || "[Name]"}**`;
if (member.email) content += ` (${member.email})`;
if (member.joinDate) content += ` - *Joined: ${member.joinDate}*`;
if (member.role) content += ` - *Role: ${member.role}*`;
content += `\n`;
});
} else {
content += `1. **[Member Name]** ([Email]) - *Joined: [Date]* - *Role: [Optional Role]*\n`;
}
content += `\n`;
// Section 2: Membership
content += `## 2. Membership\n\n`;
content += `### Who Can Be a Member\n\nAny person who:\n\n`;
content += `${
formData.memberRequirements ||
"Shares our values and purpose \nContributes labour to the cooperative (by doing actual work, not just investing money) \nCommits to collective decision-making \nParticipates in governance responsibilities"
}\n\n`;
content += `### Becoming a Member\n\n`;
content += `New members join through a consent process, which means existing members must agree that adding this person won't harm the cooperative.\n\n`;
content += `1. Trial period of **${
formData.trialPeriodMonths || 3
} months** working together\n`;
content += `2. Values alignment conversation\n`;
content += `3. Optional - Equal buy-in contribution of **$${
formData.buyInAmount || "[amount]"
}** (can be paid over time or waived based on need)\n\n`;
content += `### Leaving the Cooperative\n\n`;
content += `Members can leave anytime with **${
formData.noticeDays || 30
} days** notice. ${cooperativeName} will:\n\n`;
content += `- Pay out their share of any surplus within **${
formData.surplusPayoutDays || 30
} days**\n`;
content += `- Return their buy-in contribution within **${
formData.buyInReturnDays || 90
} days**\n`;
content += `- Maintain respectful ongoing relationships when possible\n\n`;
// Section 3: How We Make Decisions
content += `## 3. How We Make Decisions\n\n`;
content += `### Primary Decision Framework\n\n`;
content += `**${
data.decisionFrameworkName ||
"Consent-Based - No one objects strongly enough to block"
}**\n\n`;
const frameworkDetails = data.decisionFrameworkDetails || {};
if (frameworkDetails.practicalDescription) {
content += `${frameworkDetails.practicalDescription}\n\n`;
} else {
content += `We use consent, not consensus. This means we move forward when no one has a principled objection that would harm our organization. An objection must explain how the proposal would contradict our values or threaten our sustainability.\n\n`;
}
content += `### Day-to-Day Decisions\n\n`;
content += `Decisions under **$${
formData.dayToDayLimit || 100
}** can be made by any member. Just tell others what you did at the next meeting.\n\n`;
content += `### Regular Decisions\n\n`;
content += `Decisions between **$${
formData.regularDecisionMin || 100
}** and **$${
formData.regularDecisionMax || 1000
}** need consent from members present at a meeting (minimum 2 members).\n\n`;
content += `### Major Decisions\n\n`;
content += `These require consent from all members:\n\n`;
content += `- Adding or removing members\n`;
content += `- Changing this agreement\n`;
content += `- Taking on debt over **$${
formData.majorDebtThreshold || 5000
}**\n`;
content += `- Fundamental changes to our purpose or structure\n`;
content += `- Dissolution of ${cooperativeName}\n\n`;
content += `### Meeting Structure\n\n`;
content += `- Regular meetings happen **${
formData.meetingFrequency || "weekly"
}**\n`;
content += `- Emergency meetings need **${
formData.emergencyNoticeHours || 24
} hours** notice\n`;
content += `- We rotate who facilitates meetings\n`;
content += `- Decisions and reasoning get documented in shared notes\n\n`;
// Section 4: Money and Labour
content += `## 4. Money and Labour\n\n`;
content += `### Equal Ownership\n\n`;
content += `Each member owns an equal share of ${cooperativeName}, regardless of hours worked or tenure.\n\n`;
content += `### Paying Ourselves\n\n`;
const payPolicy = formData.payPolicy || "equal-pay";
const payPolicyName = payPolicy
.replace("-", " ")
.replace(/\b\w/g, (l: string) => l.toUpperCase());
content += `**Pay Policy:** ${data.payPolicyName || payPolicyName}\n\n`;
if (payPolicy === "equal-pay") {
content += `All members receive equal compensation regardless of role or hours worked.\n\n`;
if (formData.baseRate)
content += `- **Base rate:** $${formData.baseRate}/hour for all members\n`;
if (formData.monthlyDraw)
content += `- **Equal monthly draw:** $${formData.monthlyDraw} per member\n`;
} else if (payPolicy === "hours-weighted") {
content += `Compensation is proportional to hours worked by each member.\n\n`;
content += `- **Hourly rate:** $${formData.hourlyRate || 25}/hour\n`;
content += `- Members track their hours and are paid accordingly\n`;
content += `- Minimum hours commitment may apply\n`;
} else if (payPolicy === "needs-weighted") {
content += `Compensation is allocated based on each member's individual financial needs.\n\n`;
content += `- Members declare their minimum monthly needs\n`;
content += `- Available payroll is distributed proportionally to cover needs\n`;
content += `- Regular needs assessment and adjustment process\n`;
content += `- **Minimum guaranteed amount:** $${
formData.minGuaranteedPay || 1000
}/month\n`;
}
content += `\n**Payment Schedule:**\n\n`;
content += `- Paid on the **${
data.paymentDayLabel || formData.paymentDay || 15
}** of each month\n`;
content += `- Surplus (profit) distributed equally every **${
formData.surplusFrequency || "quarter"
}**\n\n`;
content += `### Work Expectations\n\n`;
content += `- Target hours per week: **${
formData.targetHours || 40
}** (flexible based on capacity)\n`;
content += `- We explicitly reject crunch culture\n`;
content += `- Members communicate their capacity openly\n`;
content += `- We adjust workload collectively when someone needs reduced hours\n\n`;
content += `### Financial Transparency\n\n`;
content += `- All members can access all financial records anytime\n`;
content += `- Monthly financial check-ins at meetings\n`;
content += `- Quarterly reviews of our runway (how many months we can operate)\n\n`;
// Section 5: Roles and Responsibilities
content += `## 5. Roles and Responsibilities\n\n`;
content += `### Rotating Roles\n\n`;
content += `We rotate operational roles every **${
formData.roleRotationMonths || 6
} months**. Current roles include:\n\n`;
content += `${
formData.rotatingRoles ||
"Financial coordinator (handles bookkeeping, not financial decisions) \nMeeting facilitator \nExternal communications \nOthers"
}\n\n`;
content += `### Shared Responsibilities\n\n`;
content += `All members participate in:\n\n`;
content += `${
formData.sharedResponsibilities ||
"Governance and decision-making \nStrategic planning \nMutual support and care"
}\n\n`;
// Section 6: Conflict and Care
content += `## 6. Conflict and Care\n\n`;
content += `### When Conflict Happens\n\n`;
content += `1. Direct conversation between parties (if comfortable)\n`;
content += `2. Mediation with another member\n`;
content += `3. Full group discussion with structured process\n`;
content += `4. External mediation if needed\n\n`;
content += `### Care Commitments\n\n`;
content += `- We check in about capacity and wellbeing regularly\n`;
content += `- We honour diverse access needs\n`;
content += `- We maintain flexibility for life circumstances\n`;
content += `- We contribute to mutual aid when members face hardship\n\n`;
// Section 7: Changing This Agreement
content += `## 7. Changing This Agreement\n\n`;
const frameworkLabel =
data.decisionFrameworkLabel || "consent-based decision";
const structuralReq =
data.structuralChangeRequirement || "full member consent";
content += `This is a living document. We review it every **${
formData.reviewFrequency || "year"
}** and update it through our ${frameworkLabel} process. Small clarifications can happen anytime; structural changes need ${structuralReq}.\n\n`;
// Section 8: If We Need to Close
content += `## 8. If We Need to Close\n\n`;
content += `If ${cooperativeName} dissolves:\n\n`;
content += `1. Pay all debts and obligations\n`;
content += `2. Return member contributions\n`;
content += `3. Distribute remaining assets equally among members\n`;
if (formData.assetDonationTarget) {
content += `4. Or donate remaining assets to **${formData.assetDonationTarget}**\n`;
}
content += `\n`;
// Section 9: Legal Registration
content += `## 9. Legal Registration\n\n`;
if (formData.isLegallyRegistered) {
content += `- **Legal Structure:** ${
formData.legalStructure ||
"[Cooperative corporation, LLC, partnership, etc.]"
}\n`;
content += `- **Registered in:** ${
formData.registeredLocation || "[State/Province]"
}\n`;
content += `- **Fiscal Year-End:** ${
formData.fiscalYearEndMonth || "December"
} ${formData.fiscalYearEndDay || 31}\n\n`;
content += `This agreement works alongside but doesn't replace our legal incorporation documents. Where they conflict, we follow the law but work to align our legal structure with our values.\n\n`;
} else {
const thisCooperative =
cooperativeName === "the cooperative"
? "This cooperative"
: cooperativeName;
content += `${thisCooperative} operates as an informal collective. If we decide to register legally in the future, we'll update this section with our legal structure details.\n\n`;
}
return content;
};
// Conflict Resolution Framework formatting - Complete document with all static and dynamic content
const formatConflictResolutionAsText = (data: any): string => {
const formData = data.formData || {};
const cooperativeName = formData.orgName || "[Cooperative Name]";
let content = `${cooperativeName.toUpperCase()} CONFLICT RESOLUTION POLICY\n${"=".repeat(
cooperativeName.length + 30
)}\n\n`;
content += `Framework Created: ${
formData.createdDate || new Date().toISOString().split("T")[0]
}\n`;
if (formData.reviewDate) {
content += `Next Review: ${formData.reviewDate}\n`;
}
content += `\n`;
// Core Values section (if enabled)
if (data.sectionsEnabled?.values !== false) {
content += `OUR VALUES\n----------\n\n`;
content += `This conflict resolution framework is guided by our core values:\n\n`;
if (formData.coreValuesList && formData.coreValuesList.length > 0) {
formData.coreValuesList.forEach((value: string) => {
content += `${value}\n`;
});
content += `\n`;
}
if (formData.customValues) {
content += `${formData.customValues}\n\n`;
}
}
// Resolution Philosophy
const approachDescriptions: { [key: string]: string } = {
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.approach && approachDescriptions[formData.approach]) {
content += `OUR APPROACH\n------------\n\n`;
content += `${approachDescriptions[formData.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 (data.sectionsEnabled?.reflection !== false) {
content += `REFLECTION\n----------\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.customReflectionPrompts) {
content += `Additional Reflection Prompts:\n${formData.customReflectionPrompts}\n\n`;
}
const reflectionTiming =
formData.reflectionPeriod || "Before any escalation";
content += `Reflection Timing: ${reflectionTiming}\n\n`;
}
// Direct Resolution (if enabled)
if (data.sectionsEnabled?.directResolution !== false) {
content += `DIRECT RESOLUTION\n-----------------\n\n`;
content += `A direct resolution process occurs when individuals communicate their concerns and work together to resolve disputes without filing an informal or formal complaint.\n\n`;
content += `Have a Conversation\n-------------------\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. This requires patience and willingness to listen without immediately dismissing the other person's perspective.\n`;
content += `4. Express thoughts and feelings directly without belittling or dismissing. Use "I" statements and active listening techniques.\n`;
content += `5. Communicate your wants and needs and make offers and requests.\n`;
content += `6. Learn for the future. Ask questions like, "If what I/you said or did came across that way, what can we do to prevent this from happening in the future?"\n`;
if (formData.documentDirectResolution) {
content += `7. Keep a written record of the resolution agreed to by both parties.\n\n`;
} else {
content += `\n`;
}
// Communication Channels
if (formData.channelsList && formData.channelsList.length > 0) {
content += `Escalating Communication Bandwidth\n-----------------------------------\n\n`;
content += `Whenever a misunderstanding or conflict arises, escalate the bandwidth of the channel:\n\n`;
formData.channelsList.forEach((channel: string, index: number) => {
content += `${index + 1}. ${channel}\n`;
});
content += `\n`;
}
if (formData.requireDirectAttempt) {
content += `NOTE: Direct resolution must be attempted before escalating to assisted resolution, unless safety concerns prevent this.\n\n`;
}
}
// Assisted Resolution
content += `ASSISTED RESOLUTION\n-------------------\n\n`;
content += `If talking things out doesn't work, you can ask a responsible contact person for help in writing.\n\n`;
// Responsible Contact People
if (formData.receiversList && formData.receiversList.length > 0) {
content += `Initial Contact Options:\n`;
formData.receiversList.forEach((receiver: string) => {
content += `${receiver}\n`;
});
content += `\n`;
}
// Mediator Structure
if (formData.mediatorType) {
content += `Mediation/Facilitation Structure: ${formData.mediatorType}\n\n`;
if (formData.supportPeople) {
content += `Support People: Parties may bring a trusted person for emotional support during mediation sessions.\n\n`;
}
}
// Timeline
content += `Response Times:\n`;
if (formData.initialResponse) {
content += `• Initial Response: ${formData.initialResponse}\n`;
}
if (formData.resolutionTarget) {
content += `• Target Resolution: ${formData.resolutionTarget}\n\n`;
}
// Formal Complaints
content += `FORMAL COMPLAINTS\n-----------------\n\n`;
content += `If assisted resolution efforts do not result in an acceptable outcome within a reasonable timeframe, a formal complaint may be filed in writing.\n\n`;
// Required Elements
if (
formData.complaintElementsList &&
formData.complaintElementsList.length > 0
) {
content += `Written Complaint Requirements:\n`;
formData.complaintElementsList.forEach((element: string, index: number) => {
content += `${index + 1}. ${element}\n`;
});
content += `\n`;
}
// Formal Process Timeline
content += `Formal Process Timeline:\n`;
if (formData.formalAcknowledgmentTime) {
content += `• Acknowledgment: ${formData.formalAcknowledgmentTime}\n`;
}
if (formData.formalReviewTime) {
content += `• Review Completion: ${formData.formalReviewTime}\n\n`;
}
if (formData.requireExternalAdvice) {
content += `External Expertise: For complex complaints involving multiple parties or organizational leaders, external legal advice will be sought.\n\n`;
}
// Settlement Documentation
if (formData.requireMinutesOfSettlement) {
content += `Reaching Agreement\n------------------\n\n`;
content += `Any resolution agreed upon must be documented in "Minutes of Settlement" signed by both parties. These agreements will be kept confidential according to our privacy standards.\n\n`;
}
// Consequences and Actions
if (formData.actionsList && formData.actionsList.length > 0) {
content += `POSSIBLE OUTCOMES\n-----------------\n\n`;
content += `Depending on the situation, resolution may include:\n\n`;
formData.actionsList.forEach((action: string) => {
content += `${action}\n`;
});
content += `\n`;
}
if (formData.appealProcess) {
content += `Appeals Process: Parties may request review of decisions through our appeals process.\n\n`;
}
// Documentation and Privacy
if (data.sectionsEnabled?.documentation !== false) {
content += `DOCUMENTATION & PRIVACY\n-----------------------\n\n`;
if (formData.docLevel) {
content += `Documentation Level: ${formData.docLevel}\n\n`;
}
if (formData.confidentiality) {
content += `Confidentiality: ${formData.confidentiality}\n\n`;
}
if (formData.retention) {
content += `Record Retention: ${formData.retention}\n\n`;
}
}
// External Resources (if enabled)
if (data.sectionsEnabled?.externalResources !== false) {
content += `EXTERNAL RESOURCES\n------------------\n\n`;
if (formData.includeHumanRights) {
content += `Individuals who are not satisfied with the outcome of a harassment or discrimination complaint may file a complaint with the Canadian Human Rights Commission or their provincial human rights tribunal.\n\n`;
}
if (formData.additionalResources) {
content += `Additional Resources:\n${formData.additionalResources}\n\n`;
}
}
// Implementation
content += `POLICY MANAGEMENT\n-----------------\n\n`;
if (formData.training) {
content += `Training Requirements:\n${formData.training}\n\n`;
}
content += `Review and Updates:\n`;
if (formData.reviewSchedule) {
content += `This policy will be reviewed ${formData.reviewSchedule.toLowerCase()}.\n\n`;
}
if (formData.amendments) {
content += `Amendment Process: ${formData.amendments}\n\n`;
}
// Acknowledgments
if (formData.acknowledgments) {
content += `Acknowledgments:\n${formData.acknowledgments}\n\n`;
}
return content;
};
const formatConflictResolutionAsMarkdown = (data: any): string => {
const formData = data.formData || {};
const cooperativeName = formData.orgName || "[Cooperative Name]";
let content = `# ${cooperativeName} Conflict Resolution Policy\n\n`;
content += `*Framework Created: ${
formData.createdDate || new Date().toISOString().split("T")[0]
}*\n`;
if (formData.reviewDate) {
content += `*Next Review: ${formData.reviewDate}*\n`;
}
content += `\n---\n\n`;
// Core Values section (if enabled)
if (data.sectionsEnabled?.values !== false) {
content += `## Our Values\n\n`;
content += `This conflict resolution framework is guided by our core values:\n\n`;
if (formData.coreValuesList && formData.coreValuesList.length > 0) {
formData.coreValuesList.forEach((value: string) => {
content += `- **${value}**\n`;
});
content += `\n`;
}
if (formData.customValues) {
content += `${formData.customValues}\n\n`;
}
}
// Resolution Philosophy
const approachDescriptions: { [key: string]: string } = {
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.approach && approachDescriptions[formData.approach]) {
content += `## Our Approach\n\n`;
content += `${approachDescriptions[formData.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 (data.sectionsEnabled?.reflection !== false) {
content += `## Reflection\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.customReflectionPrompts) {
content += `### Additional Reflection Prompts\n\n`;
content += `${formData.customReflectionPrompts}\n\n`;
}
const reflectionTiming =
formData.reflectionPeriod || "Before any escalation";
content += `**Reflection Timing:** ${reflectionTiming}\n\n`;
}
// Direct Resolution (if enabled)
if (data.sectionsEnabled?.directResolution !== false) {
content += `## Direct Resolution\n\n`;
content += `A *direct resolution* process occurs when individuals communicate their concerns and work together to resolve disputes without filing an informal or 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. This requires patience and willingness to listen without immediately dismissing the other person's perspective.\n`;
content += `4. **Express thoughts and feelings directly** without belittling or dismissing. Use "I" statements and active listening techniques.\n`;
content += `5. **Communicate your wants and needs** and make offers and requests.\n`;
content += `6. **Learn for the future.** Ask questions like, "If what I/you said or did came across that way, what can we do to prevent this from happening in the future?"\n`;
if (formData.documentDirectResolution) {
content += `7. **Keep a written record** of the resolution agreed to by both parties.\n\n`;
} else {
content += `\n`;
}
// Communication Channels
if (formData.channelsList && formData.channelsList.length > 0) {
content += `### Escalating Communication Bandwidth\n\n`;
content += `Whenever a misunderstanding or conflict arises, **escalate the bandwidth of the channel**:\n\n`;
formData.channelsList.forEach((channel: string, index: number) => {
content += `${index + 1}. ${channel}\n`;
});
content += `\n`;
}
if (formData.requireDirectAttempt) {
content += `> **Note:** Direct resolution must be attempted before escalating to assisted resolution, unless safety concerns prevent this.\n\n`;
}
}
// Assisted Resolution
content += `## Assisted Resolution\n\n`;
content += `If talking things out doesn't work, you can ask a responsible contact person for help in writing.\n\n`;
// Responsible Contact People
if (formData.receiversList && formData.receiversList.length > 0) {
content += `### Initial Contact Options\n\n`;
content += `You can report conflicts to any of the following:\n\n`;
formData.receiversList.forEach((receiver: string) => {
content += `- ${receiver}\n`;
});
content += `\n`;
}
// Mediator Structure
if (formData.mediatorType) {
content += `### Mediation/Facilitation\n\n`;
content += `**Structure:** ${formData.mediatorType}\n\n`;
if (formData.supportPeople) {
content += `**Support People:** Parties may bring a trusted person for emotional support during mediation sessions.\n\n`;
}
}
// Timeline
content += `### Response Times\n\n`;
if (formData.initialResponse) {
content += `- **Initial Response:** ${formData.initialResponse}\n`;
}
if (formData.resolutionTarget) {
content += `- **Target Resolution:** ${formData.resolutionTarget}\n\n`;
}
// Formal Complaints
content += `## Formal Complaints\n\n`;
content += `If assisted resolution efforts do not result in an acceptable outcome within a reasonable timeframe, a *formal complaint* may be filed in writing.\n\n`;
// Required Elements
if (
formData.complaintElementsList &&
formData.complaintElementsList.length > 0
) {
content += `### Written Complaint Requirements\n\n`;
content += `The formal complaint must include:\n\n`;
formData.complaintElementsList.forEach((element: string, index: number) => {
content += `${index + 1}. ${element}\n`;
});
content += `\n`;
}
// Formal Process Timeline
content += `### Formal Process Timeline\n\n`;
if (formData.formalAcknowledgmentTime) {
content += `- **Acknowledgment:** ${formData.formalAcknowledgmentTime}\n`;
}
if (formData.formalReviewTime) {
content += `- **Review Completion:** ${formData.formalReviewTime}\n\n`;
}
if (formData.requireExternalAdvice) {
content += `> **External Expertise:** For complex complaints involving multiple parties or organizational leaders, external legal advice will be sought.\n\n`;
}
// Settlement Documentation
if (formData.requireMinutesOfSettlement) {
content += `### Reaching Agreement\n\n`;
content += `Any resolution agreed upon must be documented in "Minutes of Settlement" signed by both parties. These agreements will be kept confidential according to our privacy standards.\n\n`;
}
// Consequences and Actions
if (formData.actionsList && formData.actionsList.length > 0) {
content += `## Possible Outcomes\n\n`;
content += `Depending on the situation, resolution may include:\n\n`;
formData.actionsList.forEach((action: string) => {
content += `- ${action}\n`;
});
content += `\n`;
}
if (formData.appealProcess) {
content += `### Appeals Process\n\n`;
content += `Parties may request review of decisions through our appeals process.\n\n`;
}
// Documentation and Privacy
if (data.sectionsEnabled?.documentation !== false) {
content += `## Documentation & Privacy\n\n`;
if (formData.docLevel) {
content += `**Documentation Level:** ${formData.docLevel}\n\n`;
}
if (formData.confidentiality) {
content += `**Confidentiality:** ${formData.confidentiality}\n\n`;
}
if (formData.retention) {
content += `**Record Retention:** ${formData.retention}\n\n`;
}
}
// External Resources (if enabled)
if (data.sectionsEnabled?.externalResources !== false) {
content += `## External Resources\n\n`;
if (formData.includeHumanRights) {
content += `Individuals who are not satisfied with the outcome of a harassment or discrimination complaint may file a complaint with the [Canadian Human Rights Commission](https://www.chrc-ccdp.gc.ca/eng) or their provincial human rights tribunal.\n\n`;
}
if (formData.additionalResources) {
content += `### Additional Resources\n\n`;
content += `${formData.additionalResources}\n\n`;
}
}
// Implementation
content += `## Policy Management\n\n`;
if (formData.training) {
content += `### Training Requirements\n\n`;
content += `${formData.training}\n\n`;
}
content += `### Review and Updates\n\n`;
if (formData.reviewSchedule) {
content += `This policy will be reviewed ${formData.reviewSchedule.toLowerCase()}.\n\n`;
}
if (formData.amendments) {
content += `**Amendment Process:** ${formData.amendments}\n\n`;
}
// Acknowledgments
if (formData.acknowledgments) {
content += `### Acknowledgments\n\n`;
content += `${formData.acknowledgments}\n\n`;
}
return content;
};
// Decision Framework formatting
const formatDecisionFrameworkAsText = (data: any): string => {
let content = `DECISION FRAMEWORK RESULTS\n=========================\n\n`;
if (data.surveyResponses && typeof data.surveyResponses === "object") {
content += `SURVEY RESPONSES\n----------------\n`;
content += `Urgency: ${data.surveyResponses.urgency || "[Not answered]"}\n`;
content += `Reversible: ${
data.surveyResponses.reversible || "[Not answered]"
}\n`;
content += `Expertise: ${
data.surveyResponses.expertise || "[Not answered]"
}\n`;
content += `Impact: ${data.surveyResponses.impact || "[Not answered]"}\n`;
content += `Options: ${data.surveyResponses.options || "[Not answered]"}\n`;
content += `Investment: ${
data.surveyResponses.investment || "[Not answered]"
}\n`;
content += `Team Size: ${
data.surveyResponses.teamSize || "[Not answered]"
}\n\n`;
}
if (
data.recommendedFramework &&
typeof data.recommendedFramework === "object"
) {
const framework = data.recommendedFramework;
content += `RECOMMENDED FRAMEWORK\n--------------------\n`;
content += `Method: ${framework.method || "[No method specified]"}\n`;
content += `Tagline: ${framework.tagline || "[No tagline]"}\n\n`;
content += `Reasoning: ${
framework.reasoning || "[No reasoning provided]"
}\n\n`;
if (
framework.steps &&
Array.isArray(framework.steps) &&
framework.steps.length > 0
) {
content += `IMPLEMENTATION STEPS\n-------------------\n`;
framework.steps.forEach((step: string, index: number) => {
content += `${index + 1}. ${step || "[Step not specified]"}\n`;
});
content += "\n";
}
if (
framework.tips &&
Array.isArray(framework.tips) &&
framework.tips.length > 0
) {
content += `PRO TIPS\n--------\n`;
framework.tips.forEach((tip: string, index: number) => {
content += `${index + 1}. ${tip || "[Tip not specified]"}\n`;
});
content += "\n";
}
}
return content;
};
const formatDecisionFrameworkAsMarkdown = (data: any): string => {
let content = `## Decision Framework Results\n\n`;
if (data.surveyResponses && typeof data.surveyResponses === "object") {
content += `### Survey Responses\n\n`;
content += `**Urgency:** ${
data.surveyResponses.urgency || "[Not answered]"
} \n`;
content += `**Reversible:** ${
data.surveyResponses.reversible || "[Not answered]"
} \n`;
content += `**Expertise:** ${
data.surveyResponses.expertise || "[Not answered]"
} \n`;
content += `**Impact:** ${
data.surveyResponses.impact || "[Not answered]"
} \n`;
content += `**Options:** ${
data.surveyResponses.options || "[Not answered]"
} \n`;
content += `**Investment:** ${
data.surveyResponses.investment || "[Not answered]"
} \n`;
content += `**Team Size:** ${
data.surveyResponses.teamSize || "[Not answered]"
} \n\n`;
}
if (
data.recommendedFramework &&
typeof data.recommendedFramework === "object"
) {
const framework = data.recommendedFramework;
content += `### Recommended Framework\n\n`;
content += `**Method:** ${framework.method || "[No method specified]"} \n`;
content += `**Tagline:** ${framework.tagline || "[No tagline]"} \n\n`;
content += `**Reasoning:** ${
framework.reasoning || "[No reasoning provided]"
}\n\n`;
if (
framework.steps &&
Array.isArray(framework.steps) &&
framework.steps.length > 0
) {
content += `#### Implementation Steps\n\n`;
framework.steps.forEach((step: string, index: number) => {
content += `${index + 1}. ${step || "[Step not specified]"}\n`;
});
content += "\n";
}
if (
framework.tips &&
Array.isArray(framework.tips) &&
framework.tips.length > 0
) {
content += `#### Pro Tips\n\n`;
framework.tips.forEach((tip: string, index: number) => {
content += `${index + 1}. ${tip || "[Tip not specified]"}\n`;
});
content += "\n";
}
}
return content;
};
const downloadFile = (content: string, filename: string, type: string) => {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
</script>
<style scoped>
@reference "tailwindcss";
.export-content {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
padding: 1.5rem;
gap: 1rem;
}
.export-section {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
}
.export-buttons {
@apply font-mono;
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.export-btn {
@apply bg-neutral-100 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 text-neutral-900 dark:text-white;
}
.export-btn:hover:not(:disabled) {
background: #000;
color: #fff;
transform: translateY(-2px) translateX(-2px);
box-shadow: 4px 4px 0px rgba(0, 0, 0, 0.3);
}
.export-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.export-btn.success-state {
background: #10b981 !important;
color: white !important;
border-color: #10b981 !important;
}
.success-icon {
position: absolute;
top: 50%;
right: 0.5rem;
transform: translateY(-50%);
animation: successPulse 2s ease-out;
color: #10b981;
}
.export-btn.success-state .success-icon {
color: white;
}
@keyframes successPulse {
0% {
opacity: 0;
transform: translateY(-50%) scale(0.5);
}
20% {
opacity: 1;
transform: translateY(-50%) scale(1.2);
}
40% {
transform: translateY(-50%) scale(1);
}
100% {
opacity: 0;
transform: translateY(-50%) scale(1);
}
}
/* Mobile responsive */
@media (max-width: 768px) {
.export-content {
flex-direction: column;
align-items: stretch;
}
.export-section {
justify-content: center;
}
.export-buttons {
justify-content: center;
}
.export-btn {
flex: 1;
justify-content: center;
min-width: 140px;
}
}
</style>