@wix/ditto-codegen-public 1.0.179 → 1.0.180
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/examples-apps/survey-manager/src/dashboard/pages/apis.ts +116 -0
- package/dist/examples-apps/survey-manager/src/dashboard/pages/components/PageLoader.tsx +23 -0
- package/dist/examples-apps/survey-manager/src/dashboard/pages/components/QuestionModal.tsx +84 -0
- package/dist/examples-apps/survey-manager/src/dashboard/pages/components/QuestionsTable.tsx +139 -0
- package/dist/examples-apps/survey-manager/src/dashboard/pages/components/SurveyPageLayout.tsx +99 -0
- package/dist/examples-apps/survey-manager/src/dashboard/pages/components/SurveyStats.tsx +50 -0
- package/dist/examples-apps/survey-manager/src/dashboard/pages/page.tsx +73 -453
- package/dist/examples-apps/survey-manager/src/dashboard/pages/types.ts +28 -0
- package/dist/out.js +8 -1
- package/package.json +2 -2
- package/dist/examples-apps/survey-manager/src/dashboard/pages/wix_logo.svg +0 -18
- package/dist/examples-apps/survey-manager/src/extensions.ts +0 -10
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { items } from '@wix/data';
|
|
2
|
+
import type { SurveyQuestion, SurveyResponse, QuestionStats } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new survey question
|
|
6
|
+
*/
|
|
7
|
+
export async function createQuestion(
|
|
8
|
+
questionData: Omit<SurveyQuestion, '_id' | '_createdDate' | '_updatedDate' | '_owner'>
|
|
9
|
+
): Promise<SurveyQuestion> {
|
|
10
|
+
try {
|
|
11
|
+
const result = await items.insert('survey-questions', questionData);
|
|
12
|
+
return result as SurveyQuestion;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('Error creating survey question:', error);
|
|
15
|
+
throw new Error(
|
|
16
|
+
error instanceof Error ? error.message : 'Failed to create survey question'
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Retrieves all survey questions
|
|
23
|
+
*/
|
|
24
|
+
export async function getAllQuestions(): Promise<items.WixDataResult<SurveyQuestion>> {
|
|
25
|
+
try {
|
|
26
|
+
const result = await items.query('survey-questions')
|
|
27
|
+
.descending('_createdDate')
|
|
28
|
+
.find();
|
|
29
|
+
return result as items.WixDataResult<SurveyQuestion>;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Error fetching survey questions:', error);
|
|
32
|
+
throw new Error(
|
|
33
|
+
error instanceof Error ? error.message : 'Failed to fetch survey questions'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Updates an existing survey question
|
|
40
|
+
*/
|
|
41
|
+
export async function updateQuestion(questionData: SurveyQuestion): Promise<SurveyQuestion> {
|
|
42
|
+
try {
|
|
43
|
+
if (!questionData._id) {
|
|
44
|
+
throw new Error('Question ID is required for update');
|
|
45
|
+
}
|
|
46
|
+
const result = await items.update('survey-questions', questionData);
|
|
47
|
+
return result as SurveyQuestion;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('Error updating survey question:', error);
|
|
50
|
+
throw new Error(
|
|
51
|
+
error instanceof Error ? error.message : 'Failed to update survey question'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Deletes a survey question by ID
|
|
58
|
+
*/
|
|
59
|
+
export async function deleteQuestion(questionId: string): Promise<SurveyQuestion> {
|
|
60
|
+
try {
|
|
61
|
+
if (!questionId) {
|
|
62
|
+
throw new Error('Question ID is required for deletion');
|
|
63
|
+
}
|
|
64
|
+
const result = await items.remove('survey-questions', questionId);
|
|
65
|
+
return result as SurveyQuestion;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('Error deleting survey question:', error);
|
|
68
|
+
throw new Error(
|
|
69
|
+
error instanceof Error ? error.message : 'Failed to delete survey question'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Gets statistics for a specific question
|
|
76
|
+
*/
|
|
77
|
+
export async function getQuestionStats(questionId: string): Promise<QuestionStats> {
|
|
78
|
+
try {
|
|
79
|
+
const responses = await items.query('survey-responses')
|
|
80
|
+
.eq('questionId', questionId)
|
|
81
|
+
.find();
|
|
82
|
+
|
|
83
|
+
const responseItems = responses.items as SurveyResponse[];
|
|
84
|
+
const totalResponses = responseItems.length;
|
|
85
|
+
|
|
86
|
+
if (totalResponses === 0) {
|
|
87
|
+
return {
|
|
88
|
+
totalResponses: 0,
|
|
89
|
+
averageRating: 0,
|
|
90
|
+
ratingDistribution: {}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const ratings = responseItems.map(r => r.rating);
|
|
95
|
+
const averageRating = ratings.reduce((sum, rating) => sum + rating, 0) / totalResponses;
|
|
96
|
+
|
|
97
|
+
const ratingDistribution: { [rating: number]: number } = {};
|
|
98
|
+
ratings.forEach(rating => {
|
|
99
|
+
ratingDistribution[rating] = (ratingDistribution[rating] || 0) + 1;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
totalResponses,
|
|
104
|
+
averageRating: Math.round(averageRating * 100) / 100,
|
|
105
|
+
ratingDistribution
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('Error fetching question stats:', error);
|
|
109
|
+
return {
|
|
110
|
+
totalResponses: 0,
|
|
111
|
+
averageRating: 0,
|
|
112
|
+
ratingDistribution: {}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
import { Page, WixDesignSystemProvider, Box, Loader } from '@wix/design-system';
|
|
3
|
+
|
|
4
|
+
interface PageLoaderProps {
|
|
5
|
+
title: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const PageLoader: FC<PageLoaderProps> = ({ title, message = 'Loading...' }) => {
|
|
10
|
+
return (
|
|
11
|
+
<WixDesignSystemProvider features={{ newColorsBranding: true }}>
|
|
12
|
+
<Page height="100vh">
|
|
13
|
+
<Page.Header title={title} />
|
|
14
|
+
<Page.Content>
|
|
15
|
+
<Box align="center" verticalAlign="middle" height="50vh">
|
|
16
|
+
<Loader text={message} />
|
|
17
|
+
</Box>
|
|
18
|
+
</Page.Content>
|
|
19
|
+
</Page>
|
|
20
|
+
</WixDesignSystemProvider>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
CustomModalLayout,
|
|
5
|
+
FormField,
|
|
6
|
+
Input,
|
|
7
|
+
ToggleSwitch,
|
|
8
|
+
Layout,
|
|
9
|
+
Cell,
|
|
10
|
+
} from '@wix/design-system';
|
|
11
|
+
import type { SurveyQuestion, QuestionFormData } from '../types';
|
|
12
|
+
|
|
13
|
+
interface QuestionModalProps {
|
|
14
|
+
isOpen: boolean;
|
|
15
|
+
editingQuestion: SurveyQuestion | null;
|
|
16
|
+
formData: QuestionFormData;
|
|
17
|
+
onFormChange: (data: QuestionFormData) => void;
|
|
18
|
+
onSave: () => void;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const QuestionModal: FC<QuestionModalProps> = ({
|
|
23
|
+
isOpen,
|
|
24
|
+
editingQuestion,
|
|
25
|
+
formData,
|
|
26
|
+
onFormChange,
|
|
27
|
+
onSave,
|
|
28
|
+
onClose,
|
|
29
|
+
}) => {
|
|
30
|
+
return (
|
|
31
|
+
<Modal isOpen={isOpen} onRequestClose={onClose}>
|
|
32
|
+
<CustomModalLayout
|
|
33
|
+
primaryButtonText="Save"
|
|
34
|
+
secondaryButtonText="Cancel"
|
|
35
|
+
onCloseButtonClick={onClose}
|
|
36
|
+
primaryButtonOnClick={onSave}
|
|
37
|
+
secondaryButtonOnClick={onClose}
|
|
38
|
+
title={`${editingQuestion ? 'Edit' : 'Add'} Survey Question`}
|
|
39
|
+
content={
|
|
40
|
+
<Layout gap="24px">
|
|
41
|
+
<Cell span={12}>
|
|
42
|
+
<FormField label="Question Text" required>
|
|
43
|
+
<Input
|
|
44
|
+
value={formData.questionText}
|
|
45
|
+
onChange={(e) =>
|
|
46
|
+
onFormChange({ ...formData, questionText: e.target.value })
|
|
47
|
+
}
|
|
48
|
+
placeholder="How would you rate our service?"
|
|
49
|
+
/>
|
|
50
|
+
</FormField>
|
|
51
|
+
</Cell>
|
|
52
|
+
<Cell span={6}>
|
|
53
|
+
<FormField label="Maximum Rating">
|
|
54
|
+
<Input
|
|
55
|
+
type="number"
|
|
56
|
+
value={formData.maxRating.toString()}
|
|
57
|
+
onChange={(e) =>
|
|
58
|
+
onFormChange({
|
|
59
|
+
...formData,
|
|
60
|
+
maxRating: Math.max(1, Math.min(10, Number(e.target.value))),
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
min={1}
|
|
64
|
+
max={10}
|
|
65
|
+
/>
|
|
66
|
+
</FormField>
|
|
67
|
+
</Cell>
|
|
68
|
+
<Cell span={6}>
|
|
69
|
+
<FormField label="Active" labelPlacement="right" stretchContent={false}>
|
|
70
|
+
<ToggleSwitch
|
|
71
|
+
checked={formData.isActive}
|
|
72
|
+
onChange={() =>
|
|
73
|
+
onFormChange({ ...formData, isActive: !formData.isActive })
|
|
74
|
+
}
|
|
75
|
+
/>
|
|
76
|
+
</FormField>
|
|
77
|
+
</Cell>
|
|
78
|
+
</Layout>
|
|
79
|
+
}
|
|
80
|
+
/>
|
|
81
|
+
</Modal>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Table,
|
|
4
|
+
TableActionCell,
|
|
5
|
+
TableToolbar,
|
|
6
|
+
Badge,
|
|
7
|
+
Box,
|
|
8
|
+
Text,
|
|
9
|
+
} from '@wix/design-system';
|
|
10
|
+
import * as Icons from '@wix/wix-ui-icons-common';
|
|
11
|
+
import type { SurveyQuestion, QuestionStats } from '../types';
|
|
12
|
+
|
|
13
|
+
interface QuestionsTableProps {
|
|
14
|
+
questions: SurveyQuestion[];
|
|
15
|
+
questionStats: Record<string, QuestionStats>;
|
|
16
|
+
onEdit: (question: SurveyQuestion) => void;
|
|
17
|
+
onDelete: (questionId: string, questionText: string) => void;
|
|
18
|
+
onToggleStatus: (question: SurveyQuestion) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const QuestionsTable: FC<QuestionsTableProps> = ({
|
|
22
|
+
questions,
|
|
23
|
+
questionStats,
|
|
24
|
+
onEdit,
|
|
25
|
+
onDelete,
|
|
26
|
+
onToggleStatus,
|
|
27
|
+
}) => {
|
|
28
|
+
const columns = [
|
|
29
|
+
{
|
|
30
|
+
title: 'Question',
|
|
31
|
+
render: (question: SurveyQuestion) => (
|
|
32
|
+
<Box direction="vertical" gap="3px">
|
|
33
|
+
<Text size="medium" weight="normal">
|
|
34
|
+
{question.questionText}
|
|
35
|
+
</Text>
|
|
36
|
+
<Box gap="6px">
|
|
37
|
+
<Badge
|
|
38
|
+
skin={question.isActive ? 'success' : 'neutral'}
|
|
39
|
+
size="small"
|
|
40
|
+
>
|
|
41
|
+
{question.isActive ? 'Active' : 'Inactive'}
|
|
42
|
+
</Badge>
|
|
43
|
+
<Text size="tiny" secondary>
|
|
44
|
+
Max Rating: {question.maxRating}
|
|
45
|
+
</Text>
|
|
46
|
+
</Box>
|
|
47
|
+
</Box>
|
|
48
|
+
),
|
|
49
|
+
width: 'auto',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: 'Responses',
|
|
53
|
+
render: (question: SurveyQuestion) => {
|
|
54
|
+
const stats = question._id ? questionStats[question._id] : null;
|
|
55
|
+
return (
|
|
56
|
+
<Box direction="vertical" gap="3px">
|
|
57
|
+
<Text size="medium" weight="bold">
|
|
58
|
+
{stats?.totalResponses || 0}
|
|
59
|
+
</Text>
|
|
60
|
+
<Text size="tiny" secondary>
|
|
61
|
+
Total responses
|
|
62
|
+
</Text>
|
|
63
|
+
</Box>
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
width: '120px',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
title: 'Average Rating',
|
|
70
|
+
render: (question: SurveyQuestion) => {
|
|
71
|
+
const stats = question._id ? questionStats[question._id] : null;
|
|
72
|
+
return (
|
|
73
|
+
<Box direction="vertical" gap="3px">
|
|
74
|
+
<Text size="medium" weight="bold">
|
|
75
|
+
{stats?.averageRating || 0}
|
|
76
|
+
</Text>
|
|
77
|
+
<Text size="tiny" secondary>
|
|
78
|
+
out of {question.maxRating}
|
|
79
|
+
</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
width: '120px',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
title: 'Created',
|
|
87
|
+
render: (question: SurveyQuestion) => (
|
|
88
|
+
<Text size="small">
|
|
89
|
+
{question._createdDate
|
|
90
|
+
? new Date(question._createdDate).toLocaleDateString()
|
|
91
|
+
: ''}
|
|
92
|
+
</Text>
|
|
93
|
+
),
|
|
94
|
+
width: '100px',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
title: 'Actions',
|
|
98
|
+
render: (question: SurveyQuestion) => (
|
|
99
|
+
<TableActionCell
|
|
100
|
+
primaryAction={{
|
|
101
|
+
text: 'Edit',
|
|
102
|
+
onClick: () => onEdit(question),
|
|
103
|
+
}}
|
|
104
|
+
secondaryActions={[
|
|
105
|
+
{
|
|
106
|
+
text: question.isActive ? 'Deactivate' : 'Activate',
|
|
107
|
+
icon: question.isActive ? <Icons.Hidden /> : <Icons.Visible />,
|
|
108
|
+
onClick: () => onToggleStatus(question),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
text: 'Delete',
|
|
112
|
+
icon: <Icons.Delete />,
|
|
113
|
+
onClick: () => {
|
|
114
|
+
if (question._id) {
|
|
115
|
+
onDelete(question._id, question.questionText);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
]}
|
|
120
|
+
/>
|
|
121
|
+
),
|
|
122
|
+
width: '120px',
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<Table data={questions} columns={columns}>
|
|
128
|
+
<TableToolbar>
|
|
129
|
+
<TableToolbar.ItemGroup position="start">
|
|
130
|
+
<TableToolbar.Item>
|
|
131
|
+
<TableToolbar.Title>Survey Questions</TableToolbar.Title>
|
|
132
|
+
</TableToolbar.Item>
|
|
133
|
+
</TableToolbar.ItemGroup>
|
|
134
|
+
</TableToolbar>
|
|
135
|
+
<Table.Content />
|
|
136
|
+
</Table>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Page,
|
|
5
|
+
WixDesignSystemProvider,
|
|
6
|
+
EmptyState,
|
|
7
|
+
} from '@wix/design-system';
|
|
8
|
+
import * as Icons from '@wix/wix-ui-icons-common';
|
|
9
|
+
|
|
10
|
+
import type { SurveyQuestion, QuestionStats, QuestionFormData } from '../types';
|
|
11
|
+
import { SurveyStats } from './SurveyStats';
|
|
12
|
+
import { QuestionModal } from './QuestionModal';
|
|
13
|
+
import { QuestionsTable } from './QuestionsTable';
|
|
14
|
+
|
|
15
|
+
interface SurveyPageLayoutProps {
|
|
16
|
+
questions: SurveyQuestion[];
|
|
17
|
+
questionStats: Record<string, QuestionStats>;
|
|
18
|
+
activeQuestions: number;
|
|
19
|
+
totalResponses: number;
|
|
20
|
+
isModalOpen: boolean;
|
|
21
|
+
editingQuestion: SurveyQuestion | null;
|
|
22
|
+
formData: QuestionFormData;
|
|
23
|
+
onOpenModal: (question?: SurveyQuestion) => void;
|
|
24
|
+
onCloseModal: () => void;
|
|
25
|
+
onFormChange: (data: QuestionFormData) => void;
|
|
26
|
+
onSave: () => void;
|
|
27
|
+
onDelete: (questionId: string, questionText: string) => void;
|
|
28
|
+
onToggleStatus: (question: SurveyQuestion) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const SurveyPageLayout: FC<SurveyPageLayoutProps> = ({
|
|
32
|
+
questions,
|
|
33
|
+
questionStats,
|
|
34
|
+
activeQuestions,
|
|
35
|
+
totalResponses,
|
|
36
|
+
isModalOpen,
|
|
37
|
+
editingQuestion,
|
|
38
|
+
formData,
|
|
39
|
+
onOpenModal,
|
|
40
|
+
onCloseModal,
|
|
41
|
+
onFormChange,
|
|
42
|
+
onSave,
|
|
43
|
+
onDelete,
|
|
44
|
+
onToggleStatus,
|
|
45
|
+
}) => {
|
|
46
|
+
return (
|
|
47
|
+
<WixDesignSystemProvider features={{ newColorsBranding: true }}>
|
|
48
|
+
<Page height="100vh">
|
|
49
|
+
<Page.Header
|
|
50
|
+
title="Survey Manager"
|
|
51
|
+
subtitle="Create and manage rating survey questions, view aggregated results"
|
|
52
|
+
actionsBar={
|
|
53
|
+
<Button onClick={() => onOpenModal()} prefixIcon={<Icons.Add />}>
|
|
54
|
+
Add Question
|
|
55
|
+
</Button>
|
|
56
|
+
}
|
|
57
|
+
/>
|
|
58
|
+
<Page.Content>
|
|
59
|
+
{questions.length === 0 ? (
|
|
60
|
+
<EmptyState
|
|
61
|
+
title="No survey questions yet"
|
|
62
|
+
subtitle="Create your first rating question to start collecting feedback from visitors"
|
|
63
|
+
skin="page"
|
|
64
|
+
>
|
|
65
|
+
<Button onClick={() => onOpenModal()} prefixIcon={<Icons.Add />}>
|
|
66
|
+
Create First Question
|
|
67
|
+
</Button>
|
|
68
|
+
</EmptyState>
|
|
69
|
+
) : (
|
|
70
|
+
<>
|
|
71
|
+
<SurveyStats
|
|
72
|
+
totalQuestions={questions.length}
|
|
73
|
+
activeQuestions={activeQuestions}
|
|
74
|
+
totalResponses={totalResponses}
|
|
75
|
+
/>
|
|
76
|
+
<QuestionsTable
|
|
77
|
+
questions={questions}
|
|
78
|
+
questionStats={questionStats}
|
|
79
|
+
onEdit={onOpenModal}
|
|
80
|
+
onDelete={onDelete}
|
|
81
|
+
onToggleStatus={onToggleStatus}
|
|
82
|
+
/>
|
|
83
|
+
</>
|
|
84
|
+
)}
|
|
85
|
+
</Page.Content>
|
|
86
|
+
</Page>
|
|
87
|
+
|
|
88
|
+
<QuestionModal
|
|
89
|
+
isOpen={isModalOpen}
|
|
90
|
+
editingQuestion={editingQuestion}
|
|
91
|
+
formData={formData}
|
|
92
|
+
onFormChange={onFormChange}
|
|
93
|
+
onSave={onSave}
|
|
94
|
+
onClose={onCloseModal}
|
|
95
|
+
/>
|
|
96
|
+
</WixDesignSystemProvider>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
import { Box, Card, Heading, Text } from '@wix/design-system';
|
|
3
|
+
|
|
4
|
+
interface SurveyStatsProps {
|
|
5
|
+
totalQuestions: number;
|
|
6
|
+
activeQuestions: number;
|
|
7
|
+
totalResponses: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const SurveyStats: FC<SurveyStatsProps> = ({
|
|
11
|
+
totalQuestions,
|
|
12
|
+
activeQuestions,
|
|
13
|
+
totalResponses,
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<Box gap="24px" marginBottom="24px">
|
|
17
|
+
<Card>
|
|
18
|
+
<Card.Content>
|
|
19
|
+
<Box direction="vertical" gap="6px">
|
|
20
|
+
<Heading size="small">Total Questions</Heading>
|
|
21
|
+
<Text size="medium" weight="bold">
|
|
22
|
+
{totalQuestions}
|
|
23
|
+
</Text>
|
|
24
|
+
</Box>
|
|
25
|
+
</Card.Content>
|
|
26
|
+
</Card>
|
|
27
|
+
<Card>
|
|
28
|
+
<Card.Content>
|
|
29
|
+
<Box direction="vertical" gap="6px">
|
|
30
|
+
<Heading size="small">Active Questions</Heading>
|
|
31
|
+
<Text size="medium" weight="bold">
|
|
32
|
+
{activeQuestions}
|
|
33
|
+
</Text>
|
|
34
|
+
</Box>
|
|
35
|
+
</Card.Content>
|
|
36
|
+
</Card>
|
|
37
|
+
<Card>
|
|
38
|
+
<Card.Content>
|
|
39
|
+
<Box direction="vertical" gap="6px">
|
|
40
|
+
<Heading size="small">Total Responses</Heading>
|
|
41
|
+
<Text size="medium" weight="bold">
|
|
42
|
+
{totalResponses}
|
|
43
|
+
</Text>
|
|
44
|
+
</Box>
|
|
45
|
+
</Card.Content>
|
|
46
|
+
</Card>
|
|
47
|
+
</Box>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
@@ -1,184 +1,39 @@
|
|
|
1
1
|
import React, { useState, useEffect, type FC } from 'react';
|
|
2
2
|
import { dashboard } from '@wix/dashboard';
|
|
3
|
-
import { items } from '@wix/data';
|
|
4
|
-
import {
|
|
5
|
-
Button,
|
|
6
|
-
Page,
|
|
7
|
-
WixDesignSystemProvider,
|
|
8
|
-
Table,
|
|
9
|
-
TableActionCell,
|
|
10
|
-
TableToolbar,
|
|
11
|
-
Modal,
|
|
12
|
-
CustomModalLayout,
|
|
13
|
-
FormField,
|
|
14
|
-
Input,
|
|
15
|
-
ToggleSwitch,
|
|
16
|
-
Layout,
|
|
17
|
-
Cell,
|
|
18
|
-
Badge,
|
|
19
|
-
Box,
|
|
20
|
-
Text,
|
|
21
|
-
EmptyState,
|
|
22
|
-
Loader,
|
|
23
|
-
Card,
|
|
24
|
-
Heading,
|
|
25
|
-
} from '@wix/design-system';
|
|
26
3
|
import '@wix/design-system/styles.global.css';
|
|
27
|
-
import * as Icons from '@wix/wix-ui-icons-common';
|
|
28
|
-
|
|
29
|
-
export type WixDataItem = items.WixDataItem;
|
|
30
|
-
|
|
31
|
-
// Type definitions based on the CMS collections
|
|
32
|
-
interface SurveyQuestion extends WixDataItem {
|
|
33
|
-
questionText: string;
|
|
34
|
-
maxRating: number;
|
|
35
|
-
isActive: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface SurveyResponse extends WixDataItem {
|
|
39
|
-
questionId: string;
|
|
40
|
-
rating: number;
|
|
41
|
-
submittedAt: Date;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface QuestionStats {
|
|
45
|
-
totalResponses: number;
|
|
46
|
-
averageRating: number;
|
|
47
|
-
ratingDistribution: { [rating: number]: number };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Creates a new survey question
|
|
52
|
-
*/
|
|
53
|
-
async function createQuestion(questionData: Omit<SurveyQuestion, '_id' | '_createdDate' | '_updatedDate' | '_owner'>): Promise<SurveyQuestion> {
|
|
54
|
-
try {
|
|
55
|
-
const result = await items.insert('survey-questions', questionData);
|
|
56
|
-
return result as SurveyQuestion;
|
|
57
|
-
} catch (error) {
|
|
58
|
-
console.error('Error creating survey question:', error);
|
|
59
|
-
throw new Error(
|
|
60
|
-
error instanceof Error ? error.message : 'Failed to create survey question'
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Retrieves all survey questions
|
|
67
|
-
*/
|
|
68
|
-
async function getAllQuestions(): Promise<items.WixDataResult<SurveyQuestion>> {
|
|
69
|
-
try {
|
|
70
|
-
const result = await items.query('survey-questions')
|
|
71
|
-
.descending('_createdDate')
|
|
72
|
-
.find();
|
|
73
|
-
return result as items.WixDataResult<SurveyQuestion>;
|
|
74
|
-
} catch (error) {
|
|
75
|
-
console.error('Error fetching survey questions:', error);
|
|
76
|
-
throw new Error(
|
|
77
|
-
error instanceof Error ? error.message : 'Failed to fetch survey questions'
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
4
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Deletes a survey question by ID
|
|
102
|
-
*/
|
|
103
|
-
async function deleteQuestion(questionId: string): Promise<SurveyQuestion> {
|
|
104
|
-
try {
|
|
105
|
-
if (!questionId) {
|
|
106
|
-
throw new Error('Question ID is required for deletion');
|
|
107
|
-
}
|
|
108
|
-
const result = await items.remove('survey-questions', questionId);
|
|
109
|
-
return result as SurveyQuestion;
|
|
110
|
-
} catch (error) {
|
|
111
|
-
console.error('Error deleting survey question:', error);
|
|
112
|
-
throw new Error(
|
|
113
|
-
error instanceof Error ? error.message : 'Failed to delete survey question'
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Gets statistics for a specific question
|
|
120
|
-
*/
|
|
121
|
-
async function getQuestionStats(questionId: string): Promise<QuestionStats> {
|
|
122
|
-
try {
|
|
123
|
-
const responses = await items.query('survey-responses')
|
|
124
|
-
.eq('questionId', questionId)
|
|
125
|
-
.find();
|
|
126
|
-
|
|
127
|
-
const responseItems = responses.items as SurveyResponse[];
|
|
128
|
-
const totalResponses = responseItems.length;
|
|
129
|
-
|
|
130
|
-
if (totalResponses === 0) {
|
|
131
|
-
return {
|
|
132
|
-
totalResponses: 0,
|
|
133
|
-
averageRating: 0,
|
|
134
|
-
ratingDistribution: {}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const ratings = responseItems.map(r => r.rating);
|
|
139
|
-
const averageRating = ratings.reduce((sum, rating) => sum + rating, 0) / totalResponses;
|
|
140
|
-
|
|
141
|
-
const ratingDistribution: { [rating: number]: number } = {};
|
|
142
|
-
ratings.forEach(rating => {
|
|
143
|
-
ratingDistribution[rating] = (ratingDistribution[rating] || 0) + 1;
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
totalResponses,
|
|
148
|
-
averageRating: Math.round(averageRating * 100) / 100,
|
|
149
|
-
ratingDistribution
|
|
150
|
-
};
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error('Error fetching question stats:', error);
|
|
153
|
-
return {
|
|
154
|
-
totalResponses: 0,
|
|
155
|
-
averageRating: 0,
|
|
156
|
-
ratingDistribution: {}
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
}
|
|
5
|
+
import type { SurveyQuestion, QuestionStats, QuestionFormData } from './types';
|
|
6
|
+
import {
|
|
7
|
+
createQuestion,
|
|
8
|
+
getAllQuestions,
|
|
9
|
+
updateQuestion,
|
|
10
|
+
deleteQuestion,
|
|
11
|
+
getQuestionStats,
|
|
12
|
+
} from './apis';
|
|
13
|
+
import { PageLoader } from './components/PageLoader';
|
|
14
|
+
import { SurveyPageLayout } from './components/SurveyPageLayout';
|
|
15
|
+
|
|
16
|
+
const DEFAULT_FORM_DATA: QuestionFormData = {
|
|
17
|
+
questionText: '',
|
|
18
|
+
maxRating: 5,
|
|
19
|
+
isActive: true,
|
|
20
|
+
};
|
|
160
21
|
|
|
161
22
|
const SurveyManager: FC = () => {
|
|
162
23
|
const [questions, setQuestions] = useState<SurveyQuestion[]>([]);
|
|
163
|
-
const [questionStats, setQuestionStats] = useState<
|
|
24
|
+
const [questionStats, setQuestionStats] = useState<Record<string, QuestionStats>>({});
|
|
164
25
|
const [loading, setLoading] = useState(true);
|
|
165
26
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
166
27
|
const [editingQuestion, setEditingQuestion] = useState<SurveyQuestion | null>(null);
|
|
167
|
-
const [formData, setFormData] = useState(
|
|
168
|
-
questionText: '',
|
|
169
|
-
maxRating: 5,
|
|
170
|
-
isActive: true
|
|
171
|
-
});
|
|
28
|
+
const [formData, setFormData] = useState<QuestionFormData>(DEFAULT_FORM_DATA);
|
|
172
29
|
|
|
173
|
-
// Load questions and their stats
|
|
174
30
|
const loadQuestions = async () => {
|
|
175
31
|
try {
|
|
176
32
|
setLoading(true);
|
|
177
33
|
const result = await getAllQuestions();
|
|
178
34
|
setQuestions(result.items);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const stats: { [questionId: string]: QuestionStats } = {};
|
|
35
|
+
|
|
36
|
+
const stats: Record<string, QuestionStats> = {};
|
|
182
37
|
for (const question of result.items) {
|
|
183
38
|
if (question._id) {
|
|
184
39
|
stats[question._id] = await getQuestionStats(question._id);
|
|
@@ -188,7 +43,7 @@ const SurveyManager: FC = () => {
|
|
|
188
43
|
} catch (error) {
|
|
189
44
|
dashboard.showToast({
|
|
190
45
|
message: 'Failed to load survey questions',
|
|
191
|
-
type: 'error'
|
|
46
|
+
type: 'error',
|
|
192
47
|
});
|
|
193
48
|
} finally {
|
|
194
49
|
setLoading(false);
|
|
@@ -201,76 +56,53 @@ const SurveyManager: FC = () => {
|
|
|
201
56
|
|
|
202
57
|
const openModal = (question?: SurveyQuestion) => {
|
|
203
58
|
setEditingQuestion(question || null);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
questionText: '',
|
|
214
|
-
maxRating: 5,
|
|
215
|
-
isActive: true
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
|
|
59
|
+
setFormData(
|
|
60
|
+
question
|
|
61
|
+
? {
|
|
62
|
+
questionText: question.questionText,
|
|
63
|
+
maxRating: question.maxRating,
|
|
64
|
+
isActive: question.isActive,
|
|
65
|
+
}
|
|
66
|
+
: DEFAULT_FORM_DATA
|
|
67
|
+
);
|
|
219
68
|
setIsModalOpen(true);
|
|
220
69
|
};
|
|
221
70
|
|
|
222
71
|
const closeModal = () => {
|
|
223
72
|
setIsModalOpen(false);
|
|
224
73
|
setEditingQuestion(null);
|
|
225
|
-
setFormData(
|
|
226
|
-
questionText: '',
|
|
227
|
-
maxRating: 5,
|
|
228
|
-
isActive: true
|
|
229
|
-
});
|
|
74
|
+
setFormData(DEFAULT_FORM_DATA);
|
|
230
75
|
};
|
|
231
76
|
|
|
232
77
|
const handleSave = async () => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
78
|
+
if (!formData.questionText.trim()) {
|
|
79
|
+
dashboard.showToast({
|
|
80
|
+
message: 'Question text is required',
|
|
81
|
+
type: 'error',
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
241
85
|
|
|
86
|
+
try {
|
|
242
87
|
if (editingQuestion) {
|
|
243
|
-
|
|
244
|
-
const updatedQuestion: SurveyQuestion = {
|
|
245
|
-
...editingQuestion,
|
|
246
|
-
questionText: formData.questionText,
|
|
247
|
-
maxRating: formData.maxRating,
|
|
248
|
-
isActive: formData.isActive
|
|
249
|
-
};
|
|
250
|
-
await updateQuestion(updatedQuestion);
|
|
88
|
+
await updateQuestion({ ...editingQuestion, ...formData });
|
|
251
89
|
dashboard.showToast({
|
|
252
90
|
message: 'Question updated successfully',
|
|
253
|
-
type: 'success'
|
|
91
|
+
type: 'success',
|
|
254
92
|
});
|
|
255
93
|
} else {
|
|
256
|
-
|
|
257
|
-
await createQuestion({
|
|
258
|
-
questionText: formData.questionText,
|
|
259
|
-
maxRating: formData.maxRating,
|
|
260
|
-
isActive: formData.isActive
|
|
261
|
-
});
|
|
94
|
+
await createQuestion(formData);
|
|
262
95
|
dashboard.showToast({
|
|
263
96
|
message: 'Question created successfully',
|
|
264
|
-
type: 'success'
|
|
97
|
+
type: 'success',
|
|
265
98
|
});
|
|
266
99
|
}
|
|
267
|
-
|
|
268
100
|
closeModal();
|
|
269
101
|
loadQuestions();
|
|
270
102
|
} catch (error) {
|
|
271
103
|
dashboard.showToast({
|
|
272
104
|
message: editingQuestion ? 'Failed to update question' : 'Failed to create question',
|
|
273
|
-
type: 'error'
|
|
105
|
+
type: 'error',
|
|
274
106
|
});
|
|
275
107
|
}
|
|
276
108
|
};
|
|
@@ -280,272 +112,60 @@ const SurveyManager: FC = () => {
|
|
|
280
112
|
await deleteQuestion(questionId);
|
|
281
113
|
dashboard.showToast({
|
|
282
114
|
message: `"${questionText}" deleted successfully`,
|
|
283
|
-
type: 'success'
|
|
115
|
+
type: 'success',
|
|
284
116
|
});
|
|
285
117
|
loadQuestions();
|
|
286
118
|
} catch (error) {
|
|
287
119
|
dashboard.showToast({
|
|
288
120
|
message: 'Failed to delete question',
|
|
289
|
-
type: 'error'
|
|
121
|
+
type: 'error',
|
|
290
122
|
});
|
|
291
123
|
}
|
|
292
124
|
};
|
|
293
125
|
|
|
294
|
-
const
|
|
126
|
+
const handleToggleStatus = async (question: SurveyQuestion) => {
|
|
295
127
|
try {
|
|
296
|
-
|
|
297
|
-
...question,
|
|
298
|
-
isActive: !question.isActive
|
|
299
|
-
};
|
|
300
|
-
await updateQuestion(updatedQuestion);
|
|
128
|
+
await updateQuestion({ ...question, isActive: !question.isActive });
|
|
301
129
|
dashboard.showToast({
|
|
302
|
-
message: `Question ${
|
|
303
|
-
type: 'success'
|
|
130
|
+
message: `Question ${!question.isActive ? 'activated' : 'deactivated'}`,
|
|
131
|
+
type: 'success',
|
|
304
132
|
});
|
|
305
133
|
loadQuestions();
|
|
306
134
|
} catch (error) {
|
|
307
135
|
dashboard.showToast({
|
|
308
136
|
message: 'Failed to update question status',
|
|
309
|
-
type: 'error'
|
|
137
|
+
type: 'error',
|
|
310
138
|
});
|
|
311
139
|
}
|
|
312
140
|
};
|
|
313
141
|
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
<Text size="medium" weight="normal">
|
|
320
|
-
{question.questionText}
|
|
321
|
-
</Text>
|
|
322
|
-
<Box gap="6px">
|
|
323
|
-
<Badge
|
|
324
|
-
skin={question.isActive ? 'success' : 'neutral'}
|
|
325
|
-
size="small"
|
|
326
|
-
>
|
|
327
|
-
{question.isActive ? 'Active' : 'Inactive'}
|
|
328
|
-
</Badge>
|
|
329
|
-
<Text size="tiny" secondary>
|
|
330
|
-
Max Rating: {question.maxRating}
|
|
331
|
-
</Text>
|
|
332
|
-
</Box>
|
|
333
|
-
</Box>
|
|
334
|
-
),
|
|
335
|
-
width: 'auto'
|
|
336
|
-
},
|
|
337
|
-
{
|
|
338
|
-
title: 'Responses',
|
|
339
|
-
render: (question: SurveyQuestion) => {
|
|
340
|
-
const stats = question._id ? questionStats[question._id] : null;
|
|
341
|
-
return (
|
|
342
|
-
<Box direction="vertical" gap="3px">
|
|
343
|
-
<Text size="medium" weight="bold">
|
|
344
|
-
{stats?.totalResponses || 0}
|
|
345
|
-
</Text>
|
|
346
|
-
<Text size="tiny" secondary>
|
|
347
|
-
Total responses
|
|
348
|
-
</Text>
|
|
349
|
-
</Box>
|
|
350
|
-
);
|
|
351
|
-
},
|
|
352
|
-
width: '120px'
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
title: 'Average Rating',
|
|
356
|
-
render: (question: SurveyQuestion) => {
|
|
357
|
-
const stats = question._id ? questionStats[question._id] : null;
|
|
358
|
-
return (
|
|
359
|
-
<Box direction="vertical" gap="3px">
|
|
360
|
-
<Text size="medium" weight="bold">
|
|
361
|
-
{stats?.averageRating || 0}
|
|
362
|
-
</Text>
|
|
363
|
-
<Text size="tiny" secondary>
|
|
364
|
-
out of {question.maxRating}
|
|
365
|
-
</Text>
|
|
366
|
-
</Box>
|
|
367
|
-
);
|
|
368
|
-
},
|
|
369
|
-
width: '120px'
|
|
370
|
-
},
|
|
371
|
-
{
|
|
372
|
-
title: 'Created',
|
|
373
|
-
render: (question: SurveyQuestion) => (
|
|
374
|
-
<Text size="small">
|
|
375
|
-
{question._createdDate ? new Date(question._createdDate).toLocaleDateString() : ''}
|
|
376
|
-
</Text>
|
|
377
|
-
),
|
|
378
|
-
width: '100px'
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
title: 'Actions',
|
|
382
|
-
render: (question: SurveyQuestion) => (
|
|
383
|
-
<TableActionCell
|
|
384
|
-
primaryAction={{
|
|
385
|
-
text: 'Edit',
|
|
386
|
-
onClick: () => openModal(question)
|
|
387
|
-
}}
|
|
388
|
-
secondaryActions={[
|
|
389
|
-
{
|
|
390
|
-
text: question.isActive ? 'Deactivate' : 'Activate',
|
|
391
|
-
icon: question.isActive ? <Icons.Hidden /> : <Icons.Visible />,
|
|
392
|
-
onClick: () => toggleQuestionStatus(question)
|
|
393
|
-
},
|
|
394
|
-
{
|
|
395
|
-
text: 'Delete',
|
|
396
|
-
icon: <Icons.Delete />,
|
|
397
|
-
onClick: () => {
|
|
398
|
-
if (question._id) {
|
|
399
|
-
handleDelete(question._id, question.questionText);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
]}
|
|
404
|
-
/>
|
|
405
|
-
),
|
|
406
|
-
width: '120px'
|
|
407
|
-
}
|
|
408
|
-
];
|
|
142
|
+
const totalResponses = Object.values(questionStats).reduce(
|
|
143
|
+
(sum, stats) => sum + stats.totalResponses,
|
|
144
|
+
0
|
|
145
|
+
);
|
|
146
|
+
const activeQuestions = questions.filter((q) => q.isActive).length;
|
|
409
147
|
|
|
410
148
|
if (loading) {
|
|
411
|
-
return
|
|
412
|
-
<WixDesignSystemProvider features={{ newColorsBranding: true }}>
|
|
413
|
-
<Page height="100vh">
|
|
414
|
-
<Page.Header title="Survey Manager" />
|
|
415
|
-
<Page.Content>
|
|
416
|
-
<Box align="center" verticalAlign="middle" height="50vh">
|
|
417
|
-
<Loader text="Loading survey questions..." />
|
|
418
|
-
</Box>
|
|
419
|
-
</Page.Content>
|
|
420
|
-
</Page>
|
|
421
|
-
</WixDesignSystemProvider>
|
|
422
|
-
);
|
|
149
|
+
return <PageLoader title="Survey Manager" message="Loading survey questions..." />;
|
|
423
150
|
}
|
|
424
151
|
|
|
425
152
|
return (
|
|
426
|
-
<
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
{questions.length === 0 ? (
|
|
442
|
-
<EmptyState
|
|
443
|
-
title="No survey questions yet"
|
|
444
|
-
subtitle="Create your first rating question to start collecting feedback from visitors"
|
|
445
|
-
skin="page"
|
|
446
|
-
>
|
|
447
|
-
<Button
|
|
448
|
-
onClick={() => openModal()}
|
|
449
|
-
prefixIcon={<Icons.Add />}
|
|
450
|
-
>
|
|
451
|
-
Create First Question
|
|
452
|
-
</Button>
|
|
453
|
-
</EmptyState>
|
|
454
|
-
) : (
|
|
455
|
-
<>
|
|
456
|
-
{/* Summary Cards */}
|
|
457
|
-
<Box gap="24px" marginBottom="24px">
|
|
458
|
-
<Card>
|
|
459
|
-
<Card.Content>
|
|
460
|
-
<Box direction="vertical" gap="6px">
|
|
461
|
-
<Heading size="small">Total Questions</Heading>
|
|
462
|
-
<Text size="medium" weight="bold">{questions.length}</Text>
|
|
463
|
-
</Box>
|
|
464
|
-
</Card.Content>
|
|
465
|
-
</Card>
|
|
466
|
-
<Card>
|
|
467
|
-
<Card.Content>
|
|
468
|
-
<Box direction="vertical" gap="6px">
|
|
469
|
-
<Heading size="small">Active Questions</Heading>
|
|
470
|
-
<Text size="medium" weight="bold">
|
|
471
|
-
{questions.filter(q => q.isActive).length}
|
|
472
|
-
</Text>
|
|
473
|
-
</Box>
|
|
474
|
-
</Card.Content>
|
|
475
|
-
</Card>
|
|
476
|
-
<Card>
|
|
477
|
-
<Card.Content>
|
|
478
|
-
<Box direction="vertical" gap="6px">
|
|
479
|
-
<Heading size="small">Total Responses</Heading>
|
|
480
|
-
<Text size="medium" weight="bold">
|
|
481
|
-
{Object.values(questionStats).reduce((sum, stats) => sum + stats.totalResponses, 0)}
|
|
482
|
-
</Text>
|
|
483
|
-
</Box>
|
|
484
|
-
</Card.Content>
|
|
485
|
-
</Card>
|
|
486
|
-
</Box>
|
|
487
|
-
|
|
488
|
-
{/* Questions Table */}
|
|
489
|
-
<Table data={questions} columns={columns}>
|
|
490
|
-
<TableToolbar>
|
|
491
|
-
<TableToolbar.ItemGroup position="start">
|
|
492
|
-
<TableToolbar.Item>
|
|
493
|
-
<TableToolbar.Title>Survey Questions</TableToolbar.Title>
|
|
494
|
-
</TableToolbar.Item>
|
|
495
|
-
</TableToolbar.ItemGroup>
|
|
496
|
-
</TableToolbar>
|
|
497
|
-
<Table.Content />
|
|
498
|
-
</Table>
|
|
499
|
-
</>
|
|
500
|
-
)}
|
|
501
|
-
</Page.Content>
|
|
502
|
-
</Page>
|
|
503
|
-
|
|
504
|
-
{/* Add/Edit Question Modal */}
|
|
505
|
-
<Modal isOpen={isModalOpen} onRequestClose={closeModal}>
|
|
506
|
-
<CustomModalLayout
|
|
507
|
-
primaryButtonText="Save"
|
|
508
|
-
secondaryButtonText="Cancel"
|
|
509
|
-
onCloseButtonClick={closeModal}
|
|
510
|
-
primaryButtonOnClick={handleSave}
|
|
511
|
-
secondaryButtonOnClick={closeModal}
|
|
512
|
-
title={`${editingQuestion ? 'Edit' : 'Add'} Survey Question`}
|
|
513
|
-
content={
|
|
514
|
-
<Layout gap="24px">
|
|
515
|
-
<Cell span={12}>
|
|
516
|
-
<FormField label="Question Text" required>
|
|
517
|
-
<Input
|
|
518
|
-
value={formData.questionText}
|
|
519
|
-
onChange={(e) => setFormData({ ...formData, questionText: e.target.value })}
|
|
520
|
-
placeholder="How would you rate our service?"
|
|
521
|
-
/>
|
|
522
|
-
</FormField>
|
|
523
|
-
</Cell>
|
|
524
|
-
<Cell span={6}>
|
|
525
|
-
<FormField label="Maximum Rating">
|
|
526
|
-
<Input
|
|
527
|
-
type="number"
|
|
528
|
-
value={formData.maxRating.toString()}
|
|
529
|
-
onChange={(e) => setFormData({ ...formData, maxRating: Math.max(1, Math.min(10, Number(e.target.value))) })}
|
|
530
|
-
min={1}
|
|
531
|
-
max={10}
|
|
532
|
-
/>
|
|
533
|
-
</FormField>
|
|
534
|
-
</Cell>
|
|
535
|
-
<Cell span={6}>
|
|
536
|
-
<FormField label="Active" labelPlacement="right" stretchContent={false}>
|
|
537
|
-
<ToggleSwitch
|
|
538
|
-
checked={formData.isActive}
|
|
539
|
-
onChange={() => setFormData({ ...formData, isActive: !formData.isActive })}
|
|
540
|
-
/>
|
|
541
|
-
</FormField>
|
|
542
|
-
</Cell>
|
|
543
|
-
</Layout>
|
|
544
|
-
}
|
|
545
|
-
/>
|
|
546
|
-
</Modal>
|
|
547
|
-
</WixDesignSystemProvider>
|
|
153
|
+
<SurveyPageLayout
|
|
154
|
+
questions={questions}
|
|
155
|
+
questionStats={questionStats}
|
|
156
|
+
activeQuestions={activeQuestions}
|
|
157
|
+
totalResponses={totalResponses}
|
|
158
|
+
isModalOpen={isModalOpen}
|
|
159
|
+
editingQuestion={editingQuestion}
|
|
160
|
+
formData={formData}
|
|
161
|
+
onOpenModal={openModal}
|
|
162
|
+
onCloseModal={closeModal}
|
|
163
|
+
onFormChange={setFormData}
|
|
164
|
+
onSave={handleSave}
|
|
165
|
+
onDelete={handleDelete}
|
|
166
|
+
onToggleStatus={handleToggleStatus}
|
|
167
|
+
/>
|
|
548
168
|
);
|
|
549
169
|
};
|
|
550
170
|
|
|
551
|
-
export default SurveyManager;
|
|
171
|
+
export default SurveyManager;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { items } from '@wix/data';
|
|
2
|
+
|
|
3
|
+
export type WixDataItem = items.WixDataItem;
|
|
4
|
+
|
|
5
|
+
export interface SurveyQuestion extends WixDataItem {
|
|
6
|
+
questionText: string;
|
|
7
|
+
maxRating: number;
|
|
8
|
+
isActive: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SurveyResponse extends WixDataItem {
|
|
12
|
+
questionId: string;
|
|
13
|
+
rating: number;
|
|
14
|
+
submittedAt: Date;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface QuestionStats {
|
|
18
|
+
totalResponses: number;
|
|
19
|
+
averageRating: number;
|
|
20
|
+
ratingDistribution: { [rating: number]: number };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface QuestionFormData {
|
|
24
|
+
questionText: string;
|
|
25
|
+
maxRating: number;
|
|
26
|
+
isActive: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
package/dist/out.js
CHANGED
|
@@ -64620,7 +64620,14 @@ var require_load_examples = __commonJS({
|
|
|
64620
64620
|
description: "A dashboard app that allows administrators to create and manage rating (1-5) survey questions and view aggregated results",
|
|
64621
64621
|
files: {
|
|
64622
64622
|
[types_1.ExtensionType.DASHBOARD_PAGE]: [
|
|
64623
|
-
"survey-manager/src/dashboard/pages/page.tsx"
|
|
64623
|
+
"survey-manager/src/dashboard/pages/page.tsx",
|
|
64624
|
+
"survey-manager/src/dashboard/pages/components/PageLoader.tsx",
|
|
64625
|
+
"survey-manager/src/dashboard/pages/components/QuestionModal.tsx",
|
|
64626
|
+
"survey-manager/src/dashboard/pages/components/QuestionsTable.tsx",
|
|
64627
|
+
"survey-manager/src/dashboard/pages/components/SurveyPageLayout.tsx",
|
|
64628
|
+
"survey-manager/src/dashboard/pages/components/SurveyStats.tsx",
|
|
64629
|
+
"survey-manager/src/dashboard/pages/apis.ts",
|
|
64630
|
+
"survey-manager/src/dashboard/pages/types.ts"
|
|
64624
64631
|
]
|
|
64625
64632
|
}
|
|
64626
64633
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/ditto-codegen-public",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.180",
|
|
4
4
|
"description": "AI-powered Wix CLI app generator - standalone executable",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "node build.mjs",
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"@wix/ditto-codegen": "1.0.0",
|
|
25
25
|
"esbuild": "^0.25.9"
|
|
26
26
|
},
|
|
27
|
-
"falconPackageHash": "
|
|
27
|
+
"falconPackageHash": "e8dba8e74db8a8a39dce94ec11ac6c277c9fa845178e72670a580574"
|
|
28
28
|
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<svg width="166" height="64" viewBox="0 0 166 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<g clip-path="url(#clip0_14809_9463)">
|
|
3
|
-
<path
|
|
4
|
-
d="M165.304 0H156.173C153.64 0 151.273 1.25433 149.85 3.35199L137.639 21.3662C137.313 21.844 136.608 21.844 136.283 21.3662L124.071 3.35199C122.651 1.25433 120.281 0 117.748 0H108.617L130.371 32.0894L108.737 64H117.868C120.401 64 122.769 62.7457 124.192 60.648L136.283 42.8126C136.608 42.3349 137.313 42.3349 137.639 42.8126L149.73 60.648C151.15 62.7457 153.52 64 156.053 64H165.184L143.551 32.0894L165.304 0Z"
|
|
5
|
-
fill="black" />
|
|
6
|
-
<path
|
|
7
|
-
d="M89.8281 6.54652V64H94.1922C97.8088 64 100.74 61.0697 100.74 57.4535V0H96.3755C92.7588 0 89.8281 2.93032 89.8281 6.54652Z"
|
|
8
|
-
fill="black" />
|
|
9
|
-
<path
|
|
10
|
-
d="M81.8276 0H77.944C73.6681 0 69.9633 2.95701 69.0158 7.12564L60.3278 45.3185L52.7234 9.66632C51.3168 3.0771 44.5559 -1.36641 37.6375 0.544431C33.2307 1.76139 29.9637 5.48434 29.0108 9.95455L21.4839 45.2705L12.8118 7.12831C11.8616 2.95968 8.15687 0 3.88092 0H0L14.5548 63.9973H20.0692C25.0738 63.9973 29.3978 60.4986 30.4415 55.604L39.7461 11.9401C39.8608 11.3984 40.3466 11.006 40.8991 11.006C41.4516 11.006 41.9374 11.3984 42.0522 11.9401L51.3648 55.6067C52.4084 60.5012 56.7324 63.9973 61.7371 63.9973H67.2702L81.8276 0Z"
|
|
11
|
-
fill="black" />
|
|
12
|
-
</g>
|
|
13
|
-
<defs>
|
|
14
|
-
<clipPath id="clip0_14809_9463">
|
|
15
|
-
<rect width="165.305" height="64" fill="white" />
|
|
16
|
-
</clipPath>
|
|
17
|
-
</defs>
|
|
18
|
-
</svg>
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { app, extensions } from '@wix/astro/builders';
|
|
2
|
-
|
|
3
|
-
export default app().use(
|
|
4
|
-
extensions.dashboardPage({
|
|
5
|
-
component: './dashboard/pages/page.tsx',
|
|
6
|
-
id: '4344c4a2-1864-4587-87e9-5e4109b16999',
|
|
7
|
-
routePath: 'survey-manager',
|
|
8
|
-
title: 'Survey Manager',
|
|
9
|
-
})
|
|
10
|
-
);
|