@ozdao/martyrs 0.2.558 → 0.2.560

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.
Files changed (44) hide show
  1. package/dist/builder.js +1 -1
  2. package/dist/core.observer-MZsqaE6F.js +79 -0
  3. package/dist/{crud-CZw4NwNd.js → crud-DFFgLl09.js} +2 -78
  4. package/dist/events.server.js +1 -514
  5. package/dist/inventory.server.js +2 -1
  6. package/dist/martyrs/src/components/Dropdown/{Dropdown.vue.js → Dropdown.vue2.js} +2 -2
  7. package/dist/martyrs/src/components/Dropdown/Dropdown.vue2.js.map +1 -0
  8. package/dist/martyrs/src/components/EditImages/{EditImages.vue.js → EditImages.vue2.js} +2 -2
  9. package/dist/martyrs/src/components/EditImages/EditImages.vue2.js.map +1 -0
  10. package/dist/martyrs/src/components/Feed/Feed.vue.js +2 -2
  11. package/dist/martyrs/src/modules/auth/views/components/pages/Profile.vue.js +1 -1
  12. package/dist/martyrs/src/modules/core/views/components/blocks/CardHeader.vue.js +1 -1
  13. package/dist/martyrs/src/modules/core/views/components/partials/Navigation.vue.js +1 -1
  14. package/dist/martyrs/src/modules/core/views/components/sections/{Filters.vue.js → Filters.vue2.js} +2 -2
  15. package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue2.js.map +1 -0
  16. package/dist/martyrs/src/modules/events/components/pages/EditEvent.vue.js +1 -1
  17. package/dist/martyrs/src/modules/events/components/pages/EditEventTickets.vue.js +1 -1
  18. package/dist/martyrs/src/modules/events/components/pages/Event.vue.js +2 -2
  19. package/dist/martyrs/src/modules/gallery/components/sections/BackofficeGallery.vue.js +1 -1
  20. package/dist/martyrs/src/modules/inventory/components/pages/Inventory.vue.js +1 -1
  21. package/dist/martyrs/src/modules/inventory/components/pages/InventoryEdit.vue.js +1 -1
  22. package/dist/martyrs/src/modules/music/components/pages/Album.vue.js +1 -1
  23. package/dist/martyrs/src/modules/music/components/pages/Playlist.vue.js +1 -1
  24. package/dist/martyrs/src/modules/music/components/pages/Track.vue.js +1 -1
  25. package/dist/martyrs/src/modules/orders/components/sections/FormDelivery.vue.js +2 -2
  26. package/dist/martyrs/src/modules/organizations/components/pages/Organization.vue.js +1 -1
  27. package/dist/martyrs/src/modules/products/components/pages/CategoryEdit.vue.js +2 -2
  28. package/dist/martyrs/src/modules/products/components/pages/ProductEdit.vue.js +1 -1
  29. package/dist/martyrs/src/modules/products/components/pages/Products.vue.js +1 -1
  30. package/dist/martyrs/src/modules/products/components/sections/EditVariants.vue.js +1 -1
  31. package/dist/martyrs/src/modules/rents/views/components/pages/Gant/GanttToolbar.vue.js +1 -1
  32. package/dist/martyrs/src/modules/spots/components/layouts/Spots.vue.js +1 -1
  33. package/dist/martyrs/src/modules/wallet/views/components/pages/Wallet.vue.js +1 -1
  34. package/dist/music.server.js +1 -1
  35. package/dist/orders.server.js +1 -1
  36. package/dist/products.server.js +1 -1
  37. package/dist/tickets.controller-B7r0mK-5.js +517 -0
  38. package/dist/wallet.server.js +226 -0
  39. package/package.json +1 -1
  40. package/src/builder/rspack/rspack.config.base.js +2 -1
  41. package/src/modules/wallet/controllers/routes/payments.routes.js +1 -0
  42. package/dist/martyrs/src/components/Dropdown/Dropdown.vue.js.map +0 -1
  43. package/dist/martyrs/src/components/EditImages/EditImages.vue.js.map +0 -1
  44. package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue.js.map +0 -1
@@ -0,0 +1,226 @@
1
+ import * as QRCode from "qrcode";
2
+ import { m as middlewareIndexFactory } from "./index-_Edcmck_.js";
3
+ import { c as controllerFactory$1 } from "./tickets.controller-B7r0mK-5.js";
4
+ import { O as ObserverNamespaced } from "./core.observer-MZsqaE6F.js";
5
+ import Stripe from "stripe";
6
+ const controllerFactory = (db) => {
7
+ const Payment = db.payment;
8
+ const Wallet = db.wallet;
9
+ db.order;
10
+ const create = async (req, res) => {
11
+ let { amount, positions, userId } = req.body;
12
+ const newPayment = new Payment(req.body);
13
+ await newPayment.save();
14
+ const qrData = `PaymentID:${newPayment._id};Amount:${amount}`;
15
+ const qrCode = await QRCode.toDataURL(qrData);
16
+ res.send({ qrCode, paymentId: newPayment._id });
17
+ };
18
+ const read = async (req, res) => {
19
+ try {
20
+ const paymentes = await Payment.find({}).sort({ createdAt: "desc" }).exec();
21
+ if (!paymentes) {
22
+ return res.status(404).send({ message: "Paymentes not found." });
23
+ }
24
+ res.status(200).send(paymentes);
25
+ } catch (err) {
26
+ res.status(500).send({ message: err.toString() });
27
+ }
28
+ };
29
+ const update = async (req, res) => {
30
+ const session = await db.mongoose.startSession();
31
+ session.startTransaction();
32
+ try {
33
+ const { paymentId, walletId } = req.body;
34
+ const payment = await Payment.findById(paymentId).session(session);
35
+ if (!payment) {
36
+ throw new Error("Payment not found");
37
+ }
38
+ if (payment.status !== "pending") {
39
+ throw new Error("Payment is not in a pending state");
40
+ }
41
+ const wallet = await Wallet.findById(walletId).session(session);
42
+ if (!wallet) {
43
+ throw new Error("Wallet not found");
44
+ }
45
+ if (wallet.amount < payment.amount) {
46
+ throw new Error("Insufficient funds");
47
+ }
48
+ wallet.amount -= payment.amount;
49
+ await wallet.save({ session });
50
+ payment.status = "completed";
51
+ await payment.save({ session });
52
+ await session.commitTransaction();
53
+ res.send("Payment processed successfully");
54
+ } catch (error) {
55
+ await session.abortTransaction();
56
+ res.status(500).send(error.message);
57
+ } finally {
58
+ session.endSession();
59
+ }
60
+ };
61
+ const deletePayment = (req, res) => {
62
+ Payment.findOneAndDelete({ _id: req.params._id }, (err, payment) => {
63
+ if (err) {
64
+ return res.status(500).send({ message: err });
65
+ }
66
+ if (!payment) {
67
+ return res.status(404).send({ message: "Something wrong when deleting payment." });
68
+ }
69
+ res.status(200).send(payment);
70
+ });
71
+ };
72
+ return {
73
+ create,
74
+ read,
75
+ update,
76
+ delete: deletePayment
77
+ };
78
+ };
79
+ class Webhook {
80
+ constructor(app, db, observer, path, handlerMethod, middlewares = []) {
81
+ this.app = app;
82
+ this.db = db;
83
+ this.observer = observer;
84
+ this.app.post(path, [...middlewares, handlerMethod.bind(this)]);
85
+ }
86
+ // Abstract method, must be implemented in derived classes
87
+ handleWebhook(req, res) {
88
+ throw new Error("handleWebhook() must be implemented by subclasses");
89
+ }
90
+ }
91
+ const stripe = new Stripe(process.env.STRIPE_API_KEY ?? "", {
92
+ apiVersion: "2025-04-30.basil"
93
+ });
94
+ function middleware(req, res, next) {
95
+ next();
96
+ }
97
+ class StripeWebhook extends Webhook {
98
+ constructor(app, db, observer, path = "/api/webhook/stripe", handlerMethod = StripeWebhook.prototype.handleWebhook, middlewares = []) {
99
+ super(app, db, observer, path, handlerMethod, [middleware]);
100
+ }
101
+ async handleWebhook(req, res) {
102
+ req.headers["stripe-signature"];
103
+ try {
104
+ const payload = req.body;
105
+ const payloadString = JSON.stringify(payload, null, 2);
106
+ const secret = process.env.STRIPE_WEBHOOK_SECRET;
107
+ const header = stripe.webhooks.generateTestHeaderString({
108
+ payload: payloadString,
109
+ secret
110
+ });
111
+ const event = stripe.webhooks.constructEvent(payloadString, header, secret);
112
+ this.observer.notify(event.type, event.data.object);
113
+ } catch (err) {
114
+ console.log(`Error verifying Stripe webhook signature: ${err.message}`);
115
+ return res.status(400).send(`Webhook Error: ${err.message}`);
116
+ }
117
+ res.sendStatus(200);
118
+ }
119
+ }
120
+ const RoutesPayments = (function(app, db, origins, publicPath) {
121
+ const observer = new ObserverNamespaced();
122
+ new StripeWebhook(app, db, observer);
123
+ const controller = controllerFactory(db);
124
+ console.log("payments ticket", publicPath);
125
+ const controllerTickets = controllerFactory$1(db, publicPath);
126
+ middlewareIndexFactory(db);
127
+ observer.subscribe("checkout.session.completed", async (paymentIntent) => {
128
+ try {
129
+ const event = await db.event.findOne({ _id: paymentIntent.metadata.product });
130
+ if (!event) {
131
+ console.error("Event not found:", paymentIntent.metadata.product);
132
+ return;
133
+ }
134
+ const ticketType = event.ticketsTypes.find((type) => type.name === paymentIntent.metadata.ticketType);
135
+ if (!ticketType) {
136
+ console.error("Ticket type not found:", paymentIntent.metadata.ticketType);
137
+ return;
138
+ }
139
+ const quantity = paymentIntent.amount_total / (ticketType.price * 100);
140
+ console.log("Purchase details:");
141
+ console.log("Email:", paymentIntent.customer_details.email);
142
+ console.log("Name:", paymentIntent.customer_details.name);
143
+ console.log("Event ID:", paymentIntent.metadata.eventId);
144
+ console.log("Ticket Type:", ticketType.name);
145
+ console.log("Quantity:", quantity);
146
+ let ticketData = {
147
+ name: paymentIntent.customer_details.name,
148
+ email: paymentIntent.customer_details.email,
149
+ target: paymentIntent.metadata.product,
150
+ type: "event",
151
+ seat: ticketType.name,
152
+ quantity,
153
+ price: ticketType.price,
154
+ paymentMethod: "stripe"
155
+ };
156
+ await controllerTickets.saveAndSendTicket(ticketData);
157
+ } catch (error) {
158
+ console.error("Error processing checkout session:", error);
159
+ }
160
+ });
161
+ app.get("/api/payments/read", controller.read);
162
+ app.post("/api/payments/create", controller.create);
163
+ app.post("/api/payments/update", controller.update);
164
+ app.delete("/api/payments/delete", controller.delete);
165
+ });
166
+ const ModelPayment = (db) => {
167
+ const PaymentSchema = new db.mongoose.Schema(
168
+ {
169
+ data: {
170
+ type: Object
171
+ },
172
+ currency: {
173
+ type: String
174
+ },
175
+ status: {
176
+ type: String
177
+ },
178
+ customer: {
179
+ type: db.mongoose.Schema.Types.ObjectId,
180
+ ref: "User"
181
+ },
182
+ creator: {
183
+ type: db.mongoose.Schema.Types.ObjectId,
184
+ ref: "User"
185
+ }
186
+ },
187
+ {
188
+ timestamps: {
189
+ currentTime: () => Date.now()
190
+ }
191
+ }
192
+ );
193
+ const Payment = db.mongoose.model("Payment", PaymentSchema);
194
+ return Payment;
195
+ };
196
+ function initializePayments(app, db, wss, origins, publicPath) {
197
+ db.payment = ModelPayment(db);
198
+ if (app) {
199
+ RoutesPayments(app, db, origins, publicPath);
200
+ }
201
+ }
202
+ const models = {
203
+ // ModelWallet,
204
+ ModelPayment
205
+ // ModelReward,
206
+ };
207
+ const routes = {
208
+ RoutesPayments
209
+ // RoutesRewards,
210
+ };
211
+ const controllers = {
212
+ FactoryPayments: controllerFactory
213
+ };
214
+ const wallet_server = {
215
+ initialize: initializePayments,
216
+ models,
217
+ routes,
218
+ controllers
219
+ };
220
+ export {
221
+ controllers,
222
+ wallet_server as default,
223
+ initializePayments as initialize,
224
+ models,
225
+ routes
226
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ozdao/martyrs",
3
- "version": "0.2.558",
3
+ "version": "0.2.560",
4
4
  "description": "Fullstack framework focused on user experience and ease of development.",
5
5
  "author": "OZ DAO <hello@ozdao.dev>",
6
6
  "license": "GPL-3.0-or-later",
@@ -90,7 +90,8 @@ export default (projectRoot) => {
90
90
  sourceMap: true,
91
91
  url: {
92
92
  filter: (url, resourcePath) => {
93
- if (url.indexOf("/fonts/")==0) {
93
+ // Не обрабатывать изображения из public
94
+ if (url.indexOf("/static/")==0) {
94
95
  return false;
95
96
  }
96
97
  return true;
@@ -39,6 +39,7 @@ export default (function (app, db, origins, publicPath) {
39
39
  seat: ticketType.name,
40
40
  quantity: quantity,
41
41
  price: ticketType.price,
42
+ paymentMethod: 'stripe',
42
43
  };
43
44
  await controllerTickets.saveAndSendTicket(ticketData);
44
45
  } catch (error) {
@@ -1 +0,0 @@
1
- {"version":3,"file":"Dropdown.vue.js","sources":["../../../../../src/components/Dropdown/Dropdown.vue"],"sourcesContent":["<template>\n <div\n class=\"dropdown pos-relative\"\n v-click-outside=\"clickedOutside\"\n @click.stop=\"trigger === 'click' ? (isOpen = !isOpen) : null\"\n @mouseenter=\"trigger === 'hover' ? (isOpen = true) : null\"\n @mouseleave=\"trigger === 'hover' ? (isOpen = false) : null\"\n >\n <slot name=\"label\">\n <div v-if=\"isComponentLabel\" class=\"w-100 h-100 flex-center flex\">\n <component :is=\"label.component\" v-bind=\"label.props\" :class=\"label.class\"></component>\n </div>\n <div v-else>\n {{ label }}\n </div>\n </slot>\n <transition name=\"TransitionTranslateY\" mode=\"out-in\">\n <div\n v-show=\"isOpen\"\n :style=\"{ left: align === 'left' ? '0' : 'auto', right: align === 'right' ? '0' : 'auto' }\"\n class=\"dropdown-content \"\n >\n <slot></slot>\n </div>\n </transition>\n </div>\n</template>\n\n<script setup>\nimport { ref, computed } from 'vue';\nimport clickOutside from '../FieldPhone/click-outside.js';\n\nlet vClickOutside = clickOutside\n\nconst props = defineProps({\n label: {\n type: [String, Object],\n default: 'Open'\n },\n align: {\n type: String,\n default: 'left'\n },\n trigger: {\n type: String,\n default: 'click'\n }\n})\n\nconst isOpen = ref(false);\nconst isComponentLabel = computed(() => typeof props.label === 'object');\n\nfunction clickedOutside () {\n if (props.trigger === 'click') {\n isOpen.value = false\n }\n}\n</script>\n\n<style >\n.dropdown-content {\n display: block;\n position: absolute;\n box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);\n z-index: 1;\n}\n\n/*.dropdown:hover .dropdown-content {\n display: block;\n}*/\n</style>\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,QAAI,gBAAgB;AAEpB,UAAM,QAAQ;AAed,UAAM,SAAS,IAAI,KAAK;AACxB,UAAM,mBAAmB,SAAS,MAAM,OAAO,MAAM,UAAU,QAAQ;AAEvE,aAAS,iBAAkB;AACzB,UAAI,MAAM,YAAY,SAAS;AAC7B,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"EditImages.vue.js","sources":["../../../../../src/components/EditImages/EditImages.vue"],"sourcesContent":["<template>\n\t<div class=\"flex-nowrap flex gap-small\">\n <VueDraggableNext v-if=\"localImages.length > 0\" class=\"gap-small flex dragArea list-group w-full\" v-model=\"localImages\" @change=\"emitChanges\">\n\t\t\t<div v-for=\"(image, index) in localImages\" class=\"pos-relative\">\n\t\t\t\t<img loading=\"lazy\" class=\"i-extra object-fit-contain bg-black-transp-5 pd-nano radius-small o-hidden\" :src=\"(FILE_SERVER_URL || '') + image\" />\n\t\t\t\t\n <IconCross \n @click=\"deleteImage(index)\" \n class=\"cursor-pointer pos-absolute t-center flex-center flex radius-extra i-medium bg-red pos-t-10-negative pos-r-10-negative pd-micro\"\n />\n\t\t\t</div>\n\t\t</VueDraggableNext>\n <div\n v-if=\"localImages.length > 0\" \n class=\"i-extra uppercase flex-center flex radius-small o-hidden br-solid br-main br-2px pd-small\"\n >\n <UploadImageMultiple \n @update:images=\"onImagesUpdate\"\n text=\"Add\"\n :options=\"{\n showText: false\n }\"\n :uploadPath=\"'photos'\"\n class=\"radius-big\"\n />\n </div>\n\n\n\t\t<UploadImageMultiple \t\n v-if=\"localImages.length < 1\" \n @update:images=\"onImagesUpdate\"\n :uploadPath=\"props.uploadPath\"\n :text=\"props.text\"\n :options=\"props.options\"\n class=\"w-100 pd-medium\"\n />\n\t</div>\t\n</template>\n\n<script setup>\nimport { ref, defineProps, watchEffect } from 'vue';\nimport { VueDraggableNext } from 'vue-draggable-next'\nimport UploadImageMultiple from \"@martyrs/src/components/UploadImageMultiple/UploadImageMultiple.vue\";\nimport IconCross from '@martyrs/src/modules/icons/navigation/IconCross.vue';\n\nconst props = defineProps({\n images: Array,\n text: Object,\n options: Object,\n uploadPath: {\n type: Object,\n default: 'unsorted'\n }\n});\n\nconst emit = defineEmits(['update:images'])\n\nconst localImages = ref([...props.images])\n\nwatchEffect(() => {\n localImages.value = [...props.images]; // Обновление localImages при изменении props.images\n});\n\nconst emitChanges = () => {\n emit('update:images', localImages.value)\n}\n\nconst onImagesUpdate = (newImages) => {\n localImages.value = [...localImages.value, ...newImages]\n emitChanges()\n}\n\nconst deleteImage = (index) => {\n localImages.value.splice(index, 1)\n emitChanges()\n}\n</script>\n\n<style lang=\"scss\">\n// Your styles here\n</style>\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,UAAM,QAAQ;AAUd,UAAM,OAAO;AAEb,UAAM,cAAc,IAAI,CAAC,GAAG,MAAM,MAAM,CAAC;AAEzC,gBAAY,MAAM;AAChB,kBAAY,QAAQ,CAAC,GAAG,MAAM,MAAM;AAAA,IACtC,CAAC;AAED,UAAM,cAAc,MAAM;AACxB,WAAK,iBAAiB,YAAY,KAAK;AAAA,IACzC;AAEA,UAAM,iBAAiB,CAAC,cAAc;AACpC,kBAAY,QAAQ,CAAC,GAAG,YAAY,OAAO,GAAG,SAAS;AACvD,kBAAW;AAAA,IACb;AAEA,UAAM,cAAc,CAAC,UAAU;AAC7B,kBAAY,MAAM,OAAO,OAAO,CAAC;AACjC,kBAAW;AAAA,IACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"Filters.vue.js","sources":["../../../../../../../../src/modules/core/views/components/sections/Filters.vue"],"sourcesContent":["<template>\n <div class=\"flex t-nowrap gap-thin\">\n <!-- All Filters Button -->\n <button\n @click=\"showAllFilters = true\"\n class=\"pd-small radius-medium bg-light flex-v-center flex gap-micro cursor-pointer\"\n :class=\"{ 'bg-main': activeFiltersCount > 0 }\"\n >\n <IconFilter class=\"i-medium\" />\n <span class=\"h-1r\"></span>\n <span v-if=\"activeFiltersCount\">{{ activeFiltersCount }}</span>\n </button>\n\n <!-- Individual Filter Buttons -->\n <button\n v-for=\"filter in filters\"\n :key=\"filter.value\"\n @click=\"openFilter(filter.value)\"\n class=\"pd-small radius-medium bg-light cursor-pointer flex-v-center flex gap-micro\"\n :class=\"{ 'selected bg-main': isFilterActive(filter) }\"\n >\n <IconCalendar v-if=\"filter.type === 'date'\" class=\"mn-r-micro i-medium\" />\n <span class=\"t-nowrap\">{{ filter.type === 'date' && getFilterValue(filter) ? formatFilterValue(filter) : filter.title }}</span>\n <span v-if=\"getFilterValue(filter) && filter.type !== 'date'\" class=\"mn-l-micro\">\n {{ formatFilterValue(filter) }}\n </span>\n </button>\n\n <!-- All Filters Popup -->\n <Popup\n :isPopupOpen=\"showAllFilters\"\n @close-popup=\"showAllFilters = false\"\n :align=\"isPhone() ? 'bottom center' : 'center center'\"\n class=\"w-min-20r bg-white radius-medium mobile:radius-zero mobile:radius-tr-medium mobile:radius-tl-medium mobile:w-100 pd-medium\"\n >\n <div class=\"flex-v-center flex-nowrap flex mn-b-medium\">\n <h3 class=\"flex-child-full\">Filters</h3>\n </div>\n\n <div class=\"filters-content\">\n <div \n v-for=\"filter in filters\" \n :key=\"filter.value\"\n class=\"mn-b-medium\"\n >\n <h4 class=\"mn-b-small\">{{ filter.title }}</h4>\n \n <!-- Checkbox Filter -->\n <div v-if=\"filter.type === 'checkbox'\">\n <Checkbox\n v-for=\"option in filter.options\"\n :key=\"option.value\"\n :label=\"option.label\"\n :value=\"option.value\"\n v-model:radio=\"tempSelected[filter.value]\"\n mode=\"checkbox\"\n class=\"mn-b-micro\"\n />\n </div>\n\n <!-- Radio Filter -->\n <div v-else-if=\"filter.type === 'radio'\">\n <div\n v-for=\"option in filter.options\"\n :key=\"option.value\"\n @click=\"tempSelected[filter.value] = option.value\"\n class=\"pd-small radius-small cursor-pointer mn-b-micro\"\n :class=\"{ \n 'bg-main': tempSelected[filter.value] === option.value,\n 'bg-light': tempSelected[filter.value] !== option.value\n }\"\n >\n {{ option.label }}\n </div>\n </div>\n\n <!-- Range Filter -->\n <div v-else-if=\"filter.type === 'range'\" class=\"flex gap-thin\">\n <Field\n v-model:field=\"tempSelected[filter.value].min\"\n :placeholder=\"filter.minPlaceholder || 'Min'\"\n type=\"number\"\n :label=\"returnCurrency()\"\n class=\"w-50 bg-light pd-small radius-small\"\n />\n <Field\n v-model:field=\"tempSelected[filter.value].max\"\n :placeholder=\"filter.maxPlaceholder || 'Max'\"\n type=\"number\"\n :label=\"returnCurrency()\"\n class=\"w-50 bg-light pd-small radius-small\"\n />\n </div>\n\n <!-- Date Filter -->\n <div v-else-if=\"filter.type === 'date'\">\n <Calendar\n v-model:date=\"tempSelected[filter.value]\"\n :allowRange=\"true\"\n :disablePastDates=\"true\"\n class=\"bg-light radius-small\"\n />\n </div>\n </div>\n </div>\n\n <div class=\"flex gap-thin mn-t-medium\">\n <button \n @click=\"applyAllFilters\" \n class=\"button bg-main flex-child-full\"\n >\n Apply\n </button>\n <button \n @click=\"resetFilters\" \n class=\"button bg-light\"\n >\n Reset Filters\n </button>\n </div>\n </Popup>\n\n <!-- Individual Filter Popups -->\n <Popup\n v-for=\"filter in filters\"\n :key=\"`popup-${filter.value}`\"\n :isPopupOpen=\"individualPopups[filter.value]\"\n @close-popup=\"individualPopups[filter.value] = false\"\n :align=\"isPhone() ? 'bottom center' : 'center center'\"\n class=\"bg-white radius-medium mobile:radius-zero mobile:radius-tr-medium mobile:radius-tl-medium mobile:w-100 pd-medium\"\n >\n <h4 class=\"mn-b-medium\">{{ filter.title }}</h4>\n \n <!-- Checkbox Filter -->\n <div v-if=\"filter.type === 'checkbox'\">\n <Checkbox\n v-for=\"option in filter.options\"\n :key=\"option.value\"\n :label=\"option.label\"\n :value=\"option.value\"\n v-model:radio=\"tempSelected[filter.value]\"\n mode=\"checkbox\"\n class=\"mn-b-micro\"\n />\n </div>\n\n <!-- Radio Filter -->\n <div v-else-if=\"filter.type === 'radio'\">\n <div\n v-for=\"option in filter.options\"\n :key=\"option.value\"\n @click=\"tempSelected[filter.value] = option.value\"\n class=\"pd-small radius-small cursor-pointer mn-b-micro\"\n :class=\"{ \n 'bg-main': tempSelected[filter.value] === option.value,\n 'bg-light': tempSelected[filter.value] !== option.value\n }\"\n >\n {{ option.label }}\n </div>\n </div>\n\n <!-- Range Filter -->\n <div v-else-if=\"filter.type === 'range'\" class=\"flex gap-thin\">\n <Field\n v-model:field=\"tempSelected[filter.value].min\"\n :placeholder=\"filter.minPlaceholder || 'Min'\"\n type=\"number\"\n :label=\"returnCurrency()\"\n class=\"w-50 bg-light pd-small radius-small\"\n />\n <Field\n v-model:field=\"tempSelected[filter.value].max\"\n :placeholder=\"filter.maxPlaceholder || 'Max'\"\n type=\"number\"\n :label=\"returnCurrency()\"\n class=\"w-50 bg-light pd-small radius-small\"\n />\n </div>\n\n <!-- Date Filter -->\n <div v-else-if=\"filter.type === 'date'\">\n <div class=\"mn-t-small\">\n <Calendar\n v-model:date=\"tempSelected[filter.value]\"\n :allowRange=\"true\"\n :disablePastDates=\"true\"\n class=\"bg-light radius-small\"\n />\n </div>\n </div>\n\n <div class=\"flex gap-thin mn-t-medium\">\n <button \n @click=\"cancelFilter(filter.value)\" \n class=\"bg-light button flex-child-full\"\n >\n Cancel\n </button>\n <button \n @click=\"applyFilter(filter.value)\" \n class=\"bg-main w-100 button flex-child-full\"\n >\n Apply\n </button>\n \n \n </div>\n </Popup>\n </div>\n</template>\n\n<script setup>\nimport { ref, computed, reactive, watch } from 'vue'\nimport { useGlobalMixins } from '@martyrs/src/modules/core/views/mixins/mixins.js'\nimport Popup from '@martyrs/src/components/Popup/Popup.vue'\nimport Checkbox from '@martyrs/src/components/Checkbox/Checkbox.vue'\nimport Field from '@martyrs/src/components/Field/Field.vue'\nimport Calendar from '@martyrs/src/components/Calendar/Calendar.vue'\nimport IconFilter from '@martyrs/src/modules/icons/navigation/IconFilter.vue'\nimport IconCross from '@martyrs/src/modules/icons/navigation/IconCross.vue'\nimport IconCalendar from '@martyrs/src/modules/icons/entities/IconCalendar.vue'\n\nconst filters = defineModel('filters', {\n type: Array,\n required: true\n})\n\nconst selected = defineModel('selected', {\n type: Object,\n default: () => ({})\n})\n\nconst emit = defineEmits(['select'])\n\nconst { formatDate, returnCurrency } = useGlobalMixins()\n\n// State\nconst showAllFilters = ref(false)\nconst individualPopups = reactive({})\nconst tempSelected = reactive({})\nconst tempDateRange = ref(null)\n\n// Initialize popups and temp values\nwatch(filters, (newFilters) => {\n newFilters.forEach(filter => {\n individualPopups[filter.value] = false\n \n if (!tempSelected[filter.value]) {\n if (filter.type === 'checkbox') {\n tempSelected[filter.value] = [...(selected.value[filter.value] || [])]\n } else if (filter.type === 'range') {\n tempSelected[filter.value] = { ...(selected.value[filter.value] || { min: '', max: '' }) }\n } else if (filter.type === 'date') {\n tempSelected[filter.value] = selected.value[filter.value] || null\n } else {\n tempSelected[filter.value] = selected.value[filter.value] || null\n }\n }\n })\n}, { immediate: true, deep: true })\n\n// Sync selected to tempSelected\nwatch(selected, (newSelected) => {\n Object.keys(newSelected).forEach(key => {\n const filter = filters.value.find(f => f.value === key)\n if (filter) {\n if (filter.type === 'checkbox') {\n tempSelected[key] = [...(newSelected[key] || [])]\n } else if (filter.type === 'range') {\n tempSelected[key] = { ...(newSelected[key] || { min: '', max: '' }) }\n } else {\n tempSelected[key] = newSelected[key]\n }\n }\n })\n}, { deep: true })\n\n// Computed\nconst activeFiltersCount = computed(() => {\n return Object.entries(selected.value).filter(([key, value]) => {\n if (Array.isArray(value)) return value.length > 0\n if (typeof value === 'object' && value !== null) {\n return value.min || value.max\n }\n return value !== null && value !== undefined\n }).length\n})\n\n// Methods\nconst openFilter = (filterValue) => {\n individualPopups[filterValue] = true\n}\n\nconst isFilterActive = (filter) => {\n const value = selected.value[filter.value]\n if (!value) return false\n if (Array.isArray(value)) return value.length > 0\n if (filter.type === 'range') return value.min || value.max\n return true\n}\n\nconst getFilterValue = (filter) => {\n const value = selected.value[filter.value]\n if (!value) return false\n \n if (filter.type === 'range') {\n return value.min || value.max\n }\n \n if (filter.type === 'date') {\n return value && value.start && value.end\n }\n \n if (Array.isArray(value)) {\n return value.length > 0\n }\n \n return value\n}\n\nconst formatFilterValue = (filter) => {\n const value = selected.value[filter.value]\n if (!value) return ''\n \n if (Array.isArray(value)) {\n return `(${value.length})`\n }\n \n if (filter.type === 'range') {\n if (!value.min && !value.max) return ''\n return `${value.min || '0'}-${value.max || '∞'}`\n }\n \n if (filter.type === 'date') {\n if (!value || !value.start || !value.end) return ''\n return `${formatDate(value.start, { dayMonth: true, language: 'en' })} - ${formatDate(value.end, { dayMonth: true, language: 'en' })}`\n }\n \n if (filter.type === 'radio') {\n const option = filter.options.find(o => o.value === value)\n return option ? `(${option.label})` : ''\n }\n \n return ''\n}\n\nconst applyFilter = (filterValue) => {\n selected.value[filterValue] = tempSelected[filterValue]\n individualPopups[filterValue] = false\n emit('select', { filter: filterValue, value: tempSelected[filterValue] })\n}\n\nconst cancelFilter = (filterValue) => {\n const filter = filters.value.find(f => f.value === filterValue)\n if (filter) {\n if (filter.type === 'checkbox') {\n tempSelected[filterValue] = [...(selected.value[filterValue] || [])]\n } else if (filter.type === 'range') {\n tempSelected[filterValue] = { ...(selected.value[filterValue] || { min: '', max: '' }) }\n } else if (filter.type === 'date') {\n tempSelected[filterValue] = selected.value[filterValue] || null\n } else {\n tempSelected[filterValue] = selected.value[filterValue] || null\n }\n }\n individualPopups[filterValue] = false\n}\n\nconst applyAllFilters = () => {\n Object.entries(tempSelected).forEach(([key, value]) => {\n if (selected.value[key] !== value) {\n selected.value[key] = value\n emit('select', { filter: key, value })\n }\n })\n showAllFilters.value = false\n}\n\nconst resetFilters = () => {\n filters.value.forEach(filter => {\n if (filter.type === 'checkbox') {\n tempSelected[filter.value] = []\n selected.value[filter.value] = []\n } else if (filter.type === 'range') {\n tempSelected[filter.value] = { min: '', max: '' }\n selected.value[filter.value] = { min: '', max: '' }\n } else if (filter.type === 'date') {\n tempSelected[filter.value] = null\n selected.value[filter.value] = null\n } else {\n tempSelected[filter.value] = null\n selected.value[filter.value] = null\n }\n emit('select', { filter: filter.value, value: null })\n })\n}\n</script>\n\n<style scoped>\n.filters-content {\n max-height: 60vh;\n overflow-y: auto;\n}\n</style>"],"names":["_useModel"],"mappings":"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"}