From 7cf888f284eea157f2312dacc045fdd8a7636337 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 19 Aug 2025 14:17:10 +0530 Subject: [PATCH 1/2] feat: Withdraw Inquiry --- dashboard/components.d.ts | 1 + dashboard/src/pages/SponsorshipDetails.vue | 93 +++++++++++++++++++++- events/api.py | 20 +++++ 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/dashboard/components.d.ts b/dashboard/components.d.ts index d0534cc..18b0359 100644 --- a/dashboard/components.d.ts +++ b/dashboard/components.d.ts @@ -15,6 +15,7 @@ declare module 'vue' { EventSponsorForm: typeof import('./src/components/EventSponsorForm.vue')['default'] LucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] LucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] + LucideXCircle: typeof import('~icons/lucide/x-circle')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SponsorLogoUploader: typeof import('./src/components/SponsorLogoUploader.vue')['default'] diff --git a/dashboard/src/pages/SponsorshipDetails.vue b/dashboard/src/pages/SponsorshipDetails.vue index 0ad2838..0bdbc27 100644 --- a/dashboard/src/pages/SponsorshipDetails.vue +++ b/dashboard/src/pages/SponsorshipDetails.vue @@ -1,8 +1,20 @@ + +``` + +Or from the CDN: +```html + + +``` + +Start using Gantt: +```js +let tasks = [ + { + id: '1', + name: 'Redesign website', + start: '2016-12-28', + end: '2016-12-31', + progress: 20 + }, + ... +] +let gantt = new Gantt("#gantt", tasks); +``` + +### Configuration +Frappe Gantt offers a wide range of options to customize your chart. + + +| **Option** | **Description** | **Possible Values** | **Default** | +|---------------------------|---------------------------------------------------------------------------------|----------------------------------------------------|------------------------------------| +| `arrow_curve` | Curve radius of arrows connecting dependencies. | Any positive integer. | `5` | +| `auto_move_label` | Move task labels when user scrolls horizontally. | `true`, `false` | `false` | +| `bar_corner_radius` | Radius of the task bar corners (in pixels). | Any positive integer. | `3` | +| `bar_height` | Height of task bars (in pixels). | Any positive integer. | `30` | +| `container_height` | Height of the container. | `auto` - dynamic container height to fit all tasks - _or_ any positive integer (for pixels). | `auto` | +| `column_width` | Width of each column in the timeline. | Any positive integer. | 45 | +| `date_format` | Format for displaying dates. | Any valid JS date format string. | `YYYY-MM-DD` | +| `upper_header_height` | Height of the upper header in the timeline (in pixels). | Any positive integer. | `45` | +| `lower_header_height` | Height of the lower header in the timeline (in pixels). | Any positive integer. | `30` | +| `snap_at` | Snap tasks at particular intervel while resizing or dragging. | Any _interval_ (see below) | `1d` | +| `infinite_padding` | Whether to extend timeline infinitely when user scrolls. | `true`, `false` | `true` | +| `holidays` | Highlighted holidays on the timeline. | Object mapping CSS colors to holiday types. Types can either be a) 'weekend', or b) array of _strings_ or _date objects_ or _objects_ in the format `{date: ..., label: ...}` | `{ 'var(--g-weekend-highlight-color)': 'weekend' }` | +| `ignore` | Ignored areas in the rendering | `weekend` _or_ Array of strings or date objects (`weekend` can be present to the array also). | `[]` | +| `language` | Language for localization. | ISO 639-1 codes like `en`, `fr`, `es`. | `en` | +| `lines` | Determines which grid lines to display. | `none` for no lines, `vertical` for only vertical lines, `horizontal` for only horizontal lines, `both` for complete grid. | `both` | +| `move_dependencies` | Whether moving a task automatically moves its dependencies. | `true`, `false` | `true` | +| `padding` | Padding around task bars (in pixels). | Any positive integer. | `18` | +| `popup_on` | Event to trigger the popup display. | `click` _or_ `hover` | `click` | +| `readonly_progress` | Disables editing task progress. | `true`, `false` | `false` | +| `readonly_dates` | Disables editing task dates. | `true`, `false` | `false` | +| `readonly` | Disables all editing features. | `true`, `false` | `false` | +| `scroll_to` | Determines the starting point when chart is rendered. | `today`, `start`, `end`, or a date string. | `today` | +| `show_expected_progress` | Shows expected progress for tasks. | `true`, `false` | `false` | +| `today_button` | Adds a button to navigate to today’s date. | `true`, `false` | `true` | +| `view_mode` | The initial view mode of the Gantt chart. | `Day`, `Week`, `Month`, `Year`. | `Day` | +| `view_mode_select` | Allows selecting the view mode from a dropdown. | `true`, `false` | `false` | + +Apart from these ones, two options - `popup` and `view_modes` (plural, not singular) - are available. They have "sub"-APIs, and thus are listed separately. + +#### View Mode Configuration +The `view_modes` option determines all the available view modes for the chart. It should be an array of objects. + +Each object can have the following properties: +- `name` (string) - the name of view mode. +- `padding` (interval) - the time above. +- `step` - the interval of each column +- `lower_text` (date format string _or_ function) - the format for text in lower header. Blank string for none. The function takes in `currentDate`, `previousDate`, and `lang`, and should return a string. +- `upper_text` (date format string _or_ function) - the format for text in upper header. Blank string for none. The function takes in `currentDate`, `previousDate`, and `lang`, and should return a string. +- `upper_text_frequency` (number) - how often the upper text has a value. Utilized in internal calculation to improve performance. +- `thick_line` (function) - takes in `currentDate`, returns Boolean determining whether the line for that date should be thicker than the others. + +Three other options allow you to override general configuration for this view mode alone: +- `date_format` +- `column_width` +- `snap_at` +For details, see the above table. + +#### Popup Configuration +`popup` is a function. If it returns +- `false`, there will be no popup. +- `undefined`, the popup will be rendered based on manipulation within the function +- a HTML string, the popup will be that string. + +The function receives one object as an argument, containing: +- `task` - the task as an object +- `chart` - the entire Gantt chart +- `get_title`, `get_subtitle`, `get_details` (functions) - get the relevant section as a HTML node. +- `set_title`, `set_subtitle`, `set_details` (functions) - take in the HTML of the relevant section +- `add_action` (function) - accepts two parameters, `html` and `func` - respectively determining the HTML of the action and the callback when the action is pressed. + +### API +Frappe Gantt exposes a few helpful methods for you to interact with the chart: + +| **Name** | **Description** | **Parameters** | +|---------------------------|---------------------------------------------------------------------------------|------------------------------------------| +| `.update_options` | Re-renders the chart after updating specific options. | `new_options` - object containing new options. | +| `.change_view_mode` | Updates the view mode. | `view_mode` - Name of view mode _or_ view mode object (see above) and `maintain_pos` - whether to go back to current scroll position after rerendering, defaults to `false`. | +| `.scroll_current` | Scrolls to the current date | No parameters. | +| `.update_task` | Re-renders a specific task bar alone | `task_id` - id of task and `new_details` - object containing the task properties to be updated. | + +## Development Setup +If you want to contribute enhancements or fixes: + +1. Clone this repo. +2. `cd` into project directory. +3. Run `pnpm i` to install dependencies. +4. `pnpm run build` to build files - or `pnpm run build-dev` to build and watch for changes. +5. Open `index.html` in your browser. +6. Make your code changes and test them. + +
+
+
+ + + + Frappe Technologies + + +
diff --git a/POC_SUMMARY.md b/POC_SUMMARY.md new file mode 100644 index 0000000..e1a6811 --- /dev/null +++ b/POC_SUMMARY.md @@ -0,0 +1,38 @@ +## Event Schedule Gantt POC - Quick Summary + +🎯 **Objective**: Replace manual child table scheduling with visual Gantt chart experience + +### What We Built: +βœ… **Full Vue.js POC Component** (`ScheduleGanttPOC.vue`) +βœ… **Frappe Gantt Integration** with interactive features +βœ… **Multi-Track Support** (Day 1/2, Track 1/2, different rooms) +βœ… **CRUD Operations** - Add, Edit, Delete talks/breaks +βœ… **Drag & Drop Scheduling** - Visual rescheduling +βœ… **Data Export** - JSON format for backend integration +βœ… **Responsive UI** - Tailwind styling + +### Key Features: +- **Visual Timeline**: See all tracks and schedules at once +- **Conflict Detection**: Immediately spot overlapping sessions +- **Interactive Editing**: Click to edit, drag to reschedule +- **Modal Forms**: User-friendly add/edit experience +- **Track Filtering**: Focus on specific tracks +- **Multiple View Modes**: Day/Week views +- **Real-time Updates**: Changes reflect immediately + +### Current vs POC: +**Before**: Manual form fields, no visual context, prone to conflicts +**After**: Visual timeline, drag-drop, instant feedback, better UX + +### Demo Setup: +1. Server running at: http://localhost:8081 +2. Access via: `/schedule-gantt-poc` +3. Dashboard link added for easy navigation +4. Sample data included (4 tracks, talks, breaks) + +### Integration Ready: +- Maps to existing Frappe doctypes (FE Event, Schedule Item, Event Track) +- JSON export matches current data structure +- Ready for backend API integration + +**Result**: A complete POC demonstrating how Gantt charts can revolutionize event scheduling UX! πŸš€ diff --git a/SCHEDULE_GANTT_POC.md b/SCHEDULE_GANTT_POC.md new file mode 100644 index 0000000..e552a2a --- /dev/null +++ b/SCHEDULE_GANTT_POC.md @@ -0,0 +1,113 @@ +# Event Schedule Gantt POC + +## Overview +This is a Proof of Concept (POC) that demonstrates how Frappe Gantt can be used to improve the event scheduling experience. Instead of manually selecting tracks, start times, and end times in a child table format, this POC provides an intuitive visual scheduling interface. + +## Current Problem +The existing scheduling system uses a child table approach where users must: +1. Manually select a track from a dropdown +2. Manually enter start time +3. Manually enter end time +4. Repeat for each talk/break +5. No visual representation of schedule conflicts or timeline + +## POC Solution +This POC uses Frappe Gantt to provide: +1. **Visual Timeline**: See all tracks and their schedules at a glance +2. **Drag & Drop**: Move talks between time slots easily +3. **Conflict Detection**: Visual indication of overlapping sessions +4. **Multi-Track Support**: Handle multiple tracks (Day 1 Track 1, Day 2 Track 2, etc.) +5. **Interactive Editing**: Click to edit, drag to reschedule +6. **Export Functionality**: Export schedule data for backend integration + +## Features Demonstrated + +### 1. Data Structure Mapping +- **Current**: Child table with separate track, start_time, end_time fields +- **POC**: Gantt tasks with start/end datetime, visual track grouping + +### 2. Track Management +- Support for multiple tracks (Main Hall, Workshop Room, etc.) +- Color-coded tracks for visual distinction +- Filter by specific track + +### 3. Schedule Item Types +- **Talks**: Linked to Event Talk doctype +- **Breaks**: Custom descriptions (Coffee Break, Lunch, etc.) + +### 4. Interactive Features +- Add new talks/breaks through modal forms +- Edit existing items by clicking +- Delete items with confirmation +- Drag to reschedule (updates start/end times automatically) +- View mode switching (Day/Week views) + +### 5. Data Export +- Export current schedule as JSON +- Ready for backend integration +- Maintains original data structure for compatibility + +## Technical Implementation + +### Dependencies +- `frappe-gantt`: Core Gantt chart library +- `vue 3`: Frontend framework with Composition API +- `tailwindcss`: Styling + +### Key Components +1. **Gantt Configuration**: Custom view modes, popup handling, event callbacks +2. **Data Transformation**: Convert schedule items to Gantt tasks format +3. **CRUD Operations**: Add, edit, delete schedule items +4. **Real-time Updates**: Sync changes between Gantt and data model + +### Integration Points +The POC is designed to integrate with existing Frappe doctypes: +- `FE Event`: Parent event document +- `Event Track`: Track definitions +- `Schedule Item`: Individual schedule entries (child table) +- `Event Talk`: Talk details + +## Usage + +1. **Access**: Navigate to `/schedule-gantt-poc` in the dashboard +2. **View**: See the current schedule across all tracks +3. **Add**: Use "Add New Talk" or "Add Break" buttons +4. **Edit**: Click on any schedule item to edit details +5. **Move**: Drag items to different time slots +6. **Filter**: Select specific tracks from dropdown +7. **Export**: Download schedule data as JSON + +## Benefits Over Current System + +1. **Visual Context**: See entire schedule at once +2. **Conflict Prevention**: Immediately see overlapping sessions +3. **Efficiency**: Drag-and-drop vs manual time entry +4. **User Experience**: Intuitive vs form-heavy interface +5. **Planning**: Better for schedule coordination and planning + +## Next Steps for Integration + +1. **Backend API**: Create endpoints to fetch/save schedule data +2. **Real-time Sync**: WebSocket integration for collaborative editing +3. **Validation**: Add business rules (minimum talk duration, break requirements) +4. **Speaker Integration**: Link to speaker availability +5. **Room Capacity**: Factor in venue capacity constraints +6. **Mobile Responsive**: Optimize for mobile schedule management + +## File Structure + +``` +dashboard/src/pages/ScheduleGanttPOC.vue - Main POC component +dashboard/src/router.js - Route configuration +dashboard/src/index.css - Frappe Gantt CSS import +``` + +## Demo Data + +The POC includes sample data representing: +- 4 tracks across 2 days +- Mix of talks and breaks +- Different durations and timing patterns +- Color-coded tracks for visual distinction + +This demonstrates how the system would work with real event data while providing a complete scheduling experience. diff --git a/dashboard/package.json b/dashboard/package.json index 12cfdc2..aadd7e1 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -14,6 +14,7 @@ "@vueuse/core": "^13.6.0", "canvas-confetti": "^1.9.3", "feather-icons": "^4.29.2", + "frappe-gantt": "^1.0.3", "frappe-ui": "^0.1.188", "socket.io-client": "^4.7.2", "vue": "^3.5.13", diff --git a/dashboard/src/assets/frappe-gantt.css b/dashboard/src/assets/frappe-gantt.css new file mode 100644 index 0000000..5cf6017 --- /dev/null +++ b/dashboard/src/assets/frappe-gantt.css @@ -0,0 +1,366 @@ +:root { + --g-arrow-color: #1f2937; + --g-bar-color: #fff; + --g-bar-border: #fff; + --g-tick-color-thick: #ededed; + --g-tick-color: #f3f3f3; + --g-actions-background: #f3f3f3; + --g-border-color: #ebeff2; + --g-text-muted: #7c7c7c; + --g-text-light: #fff; + --g-text-dark: #171717; + --g-progress-color: #dbdbdb; + --g-handle-color: #37352f; + --g-weekend-label-color: #dcdce4; + --g-expected-progress: #c4c4e9; + --g-header-background: #fff; + --g-row-color: #fdfdfd; + --g-row-border-color: #c7c7c7; + --g-today-highlight: #37352f; + --g-popup-actions: #ebeff2; + --g-weekend-highlight-color: #f7f7f7; +} + +.gantt-container { + line-height: 14.5px; + position: relative; + overflow: auto; + font-size: 12px; + height: var(--gv-grid-height); + width: 100%; + border-radius: 8px; + + & .popup-wrapper { + position: absolute; + top: 0; + left: 0; + background: #fff; + box-shadow: 0px 10px 24px -3px rgba(0, 0, 0, 0.2); + padding: 10px; + border-radius: 5px; + width: max-content; + z-index: 1000; + + & .title { + margin-bottom: 2px; + color: var(--g-text-dark); + font-size: 0.85rem; + font-weight: 650; + line-height: 15px; + } + + & .subtitle { + color: var(--g-text-dark); + font-size: 0.8rem; + margin-bottom: 5px; + } + + & .details { + color: var(--g-text-muted); + font-size: 0.7rem; + } + + & .actions { + margin-top: 10px; + margin-left: 3px; + } + + & .action-btn { + border: none; + padding: 5px 8px; + background-color: var(--g-popup-actions); + border-right: 1px solid var(--g-text-light); + + &:hover { + background-color: brightness(97%); + } + + &:first-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + + &:last-child { + border-right: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } + } + } + + & .grid-header { + height: calc( + var(--gv-lower-header-height) + var(--gv-upper-header-height) + 10px + ); + background-color: var(--g-header-background); + position: sticky; + top: 0; + left: 0; + border-bottom: 1px solid var(--g-row-border-color); + z-index: 1000; + } + + & .lower-text, + & .upper-text { + text-anchor: middle; + } + + & .upper-header { + height: var(--gv-upper-header-height); + } + + & .lower-header { + height: var(--gv-lower-header-height); + } + + & .lower-text { + font-size: 12px; + position: absolute; + width: calc(var(--gv-column-width) * 0.8); + height: calc(var(--gv-lower-header-height) * 0.8); + margin: 0 calc(var(--gv-column-width) * 0.1); + align-content: center; + text-align: center; + color: var(--g-text-muted); + } + + & .upper-text { + position: absolute; + width: fit-content; + font-weight: 500; + font-size: 14px; + color: var(--g-text-dark); + height: calc(var(--gv-lower-header-height) * 0.66); + } + + & .current-upper { + position: sticky; + left: 0 !important; + padding-left: 17px; + background: white; + } + + & .side-header { + position: sticky; + top: 0; + right: 0; + float: right; + + z-index: 1000; + line-height: 20px; + font-weight: 400; + width: max-content; + margin-left: auto; + padding-right: 10px; + padding-top: 10px; + background: var(--g-header-background); + display: flex; + } + + & .side-header * { + transition-property: background-color; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + background-color: var(--g-actions-background); + border-radius: 0.5rem; + border: none; + padding: 5px 8px; + color: var(--g-text-dark); + font-size: 14px; + letter-spacing: 0.02em; + font-weight: 420; + box-sizing: content-box; + + margin-right: 5px; + + &:last-child { + margin-right: 0; + } + + &:hover { + filter: brightness(97.5%); + } + } + + & .side-header select { + width: 60px; + padding-top: 2px; + padding-bottom: 2px; + } + & .side-header select:focus { + outline: none; + } + + & .date-range-highlight { + background-color: var(--g-progress-color); + border-radius: 12px; + height: calc(var(--gv-lower-header-height) - 6px); + top: calc(var(--gv-upper-header-height) + 5px); + position: absolute; + } + + & .current-highlight { + position: absolute; + background: var(--g-today-highlight); + width: 1px; + z-index: 999; + } + + & .current-ball-highlight { + position: absolute; + background: var(--g-today-highlight); + z-index: 1001; + border-radius: 50%; + } + + & .current-date-highlight { + background: var(--g-today-highlight); + color: var(--g-text-light); + border-radius: 5px; + } + + & .holiday-label { + position: absolute; + top: 0; + left: 0; + opacity: 0; + z-index: 1000; + background: --g-weekend-label-color; + border-radius: 5px; + padding: 2px 5px; + + &.show { + opacity: 100; + } + } + + & .extras { + position: sticky; + left: 0px; + + & .adjust { + position: absolute; + left: 8px; + top: calc(var(--gv-grid-height) - 60px); + background-color: rgba(0, 0, 0, 0.7); + color: white; + border: none; + padding: 8px; + border-radius: 3px; + } + } + + .hide { + display: none; + } +} + +.gantt { + user-select: none; + -webkit-user-select: none; + position: absolute; + + & .grid-background { + fill: none; + } + + & .grid-row { + fill: var(--g-row-color); + } + + & .row-line { + stroke: var(--g-border-color); + } + + & .tick { + stroke: var(--g-tick-color); + stroke-width: 0.4; + + &.thick { + stroke: var(--g-tick-color-thick); + stroke-width: 0.7; + } + } + + & .arrow { + fill: none; + stroke: var(--g-arrow-color); + stroke-width: 1.5; + } + + & .bar-wrapper .bar { + fill: var(--g-bar-color); + stroke: var(--g-bar-border); + stroke-width: 0; + transition: stroke-width 0.3s ease; + } + + & .bar-progress { + fill: var(--g-progress-color); + border-radius: 4px; + } + + & .bar-expected-progress { + fill: var(--g-expected-progress); + } + + & .bar-invalid { + fill: transparent; + stroke: var(--g-bar-border); + stroke-width: 1; + stroke-dasharray: 5; + + & ~ .bar-label { + fill: var(--g-text-light); + } + } + + & .bar-label { + fill: var(--g-text-dark); + dominant-baseline: central; + font-family: Helvetica; + font-size: 13px; + font-weight: 400; + + &.big { + fill: var(--g-text-dark); + text-anchor: start; + } + } + + & .handle { + fill: var(--g-handle-color); + opacity: 0; + transition: opacity 0.3s ease; + &.active, + &.visible { + cursor: ew-resize; + opacity: 1; + } + } + + & .handle.progress { + fill: var(--g-text-muted); + } + + & .bar-wrapper { + cursor: pointer; + + & .bar { + outline: 1px solid var(--g-row-border-color); + border-radius: 3px; + } + + &:hover { + .bar { + transition: transform 0.3s ease; + } + + .date-range-highlight { + display: block; + } + } + } +} + + diff --git a/dashboard/src/pages/Dashboard.vue b/dashboard/src/pages/Dashboard.vue index dd70c09..e7f6f56 100644 --- a/dashboard/src/pages/Dashboard.vue +++ b/dashboard/src/pages/Dashboard.vue @@ -1,7 +1,18 @@ diff --git a/dashboard/src/pages/ScheduleGanttPOC.vue b/dashboard/src/pages/ScheduleGanttPOC.vue new file mode 100644 index 0000000..0055757 --- /dev/null +++ b/dashboard/src/pages/ScheduleGanttPOC.vue @@ -0,0 +1,775 @@ + + + + + diff --git a/dashboard/src/router.js b/dashboard/src/router.js index ddefe90..8f3963f 100644 --- a/dashboard/src/router.js +++ b/dashboard/src/router.js @@ -8,6 +8,11 @@ const routes = [ name: "dashboard", component: () => import("@/pages/Dashboard.vue"), }, + { + path: "/schedule-gantt-poc", + name: "schedule-gantt-poc", + component: () => import("@/pages/ScheduleGanttPOC.vue"), + }, { path: "/book-tickets/:eventRoute", props: true, diff --git a/dashboard/yarn.lock b/dashboard/yarn.lock index 7c3e573..9eab12f 100644 --- a/dashboard/yarn.lock +++ b/dashboard/yarn.lock @@ -1570,6 +1570,11 @@ fraction.js@^4.1.2: resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz" integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA== +frappe-gantt@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/frappe-gantt/-/frappe-gantt-1.0.3.tgz#3e261f2a4f93a00066408df91ec2976609a78b4d" + integrity sha512-NkwO2MD4JoWJ82ZruK/rQVIAEpI3nXUoo5gG+wQHV18yKXhzZsTehVfCXJ92DLl/foTtut1RVL0dqj7fo6Cfpg== + frappe-ui@^0.1.188: version "0.1.188" resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.188.tgz#69f4fd506361c31bed8b38c95c9ed50ea5ef114f"