Compare commits

..

1043 Commits

Author SHA1 Message Date
Nicolai 8ac7eb5a29 refactor(sidebar): remove external URLs from hardcoded items
External web pages (chatgpt-schoolchild, ai-teachers, my-child)
removed from addEnlightCustomItems() — they should be managed
via the new "More +" sidebar page manager in v2.46.0.

Only Vue-router pages remain hardcoded:
- LeaderBoard (/leaderboard)
- MyPoints (/mypoints)
- Role-based profiles (StudentProfile, SchoolchildrenProfile,
  CourseCreatorProfile, ParentProfile)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 18:51:01 +03:00
Nicolai 8bb7a61f56 fix(sidebar): restore custom Enlight sidebar items after v2.46.0 merge
Two bugs fixed:
1. updateSidebarLinks() was called AFTER custom add functions,
   resetting sidebarLinks.value and erasing all custom items
2. Custom items were pushed as flat objects but the template
   expects grouped structure { label, hideLabel, items: [] }

Refactored all custom functions into a single addEnlightCustomItems()
that builds a proper group and pushes it AFTER updateSidebarLinks().
Items: LeaderBoard, MyPoints, ChatGPT (role-based), MyChild, Profile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 18:43:34 +03:00
Nicolai ba99a48c88 Merge v2.46.0 into develop — resolve all conflicts
- yarn.lock, components.d.ts, ru.po: accepted upstream v2.46.0
- AppSidebar.vue: kept custom sidebar links (MyPoints, LeaderBoard,
  ChatGPT, MyChild, Profile) + adopted async watch from v2.46.0
- MobileLayout.vue: merged onMounted with sidebarSettings.reload +
  custom addSideBar() for role-based mobile links
- EditProfile.vue: adopted new Dialog options structure (size only)
- Courses/Courses.vue: unified tab values to lowercase (enrolled,
  upcoming, new, created, unpublished)
- CourseDetail.vue (old): removed — logic migrated to Courses/CourseDetail.vue;
  transferred custom tag flex-wrap styling to CourseOverview.vue
- LessonForm.vue: kept Rutube video support
- App.vue: clean (no conflict markers)
- user.py: merged imports + kept custom sign_up params (phone, user_role)
- utils.py: kept render_html (Rutube), is_mentor, is_eligible_to_review;
  added type hints for get_course_progress from v2.46.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 18:25:58 +03:00
Frappe PR Bot 5933a59a14 chore(release): Bumped to Version 2.46.0 2026-03-04 05:08:05 +00:00
Jannat Patel 5c3834cbbe Merge pull request #2161 from frappe/main-hotfix
chore: merge 'main-hotfix' into 'main'
2026-03-04 10:37:22 +05:30
Jannat Patel c77fdf55b3 Merge branch 'main' into main-hotfix 2026-03-04 10:22:29 +05:30
Frappe PR Bot 2c2e8ca112 chore(release): Bumped to Version 2.45.2 2026-03-03 07:19:36 +00:00
Jannat Patel 4771ebbcfd fix: enrollment error during course progress 2026-03-03 12:48:51 +05:30
Jannat Patel 08fbcc963d Merge pull request #2158 from frappe/mergify/bp/main-hotfix/pr-2157
fix: pricing section issue in course form (backport #2157)
2026-03-02 18:32:05 +05:30
Jannat Patel 54e9396fdb fix: pricing section issue in course form
(cherry picked from commit a0d6b2b6b626040572ddefb47f9ef09beec1a801)
2026-03-02 12:54:01 +00:00
Frappe PR Bot 315ec3d655 chore(release): Bumped to Version 2.45.1 2026-02-25 12:36:10 +00:00
Jannat Patel 484c3d7402 Merge pull request #2128 from frappe/mergify/bp/main/pr-2126
fix: permission issue during quiz submission (backport #2126)
2026-02-25 18:05:27 +05:30
Jannat Patel e7ce850691 fix: removed trailing comma at the end of permission 2026-02-25 16:45:02 +05:30
Jannat Patel 62b5715b98 Merge pull request #2127 from frappe/mergify/bp/main-hotfix/pr-2126
fix: permission issue during quiz submission (backport #2126)
2026-02-25 13:08:14 +05:30
Jannat Patel 593c70affb chore: resolved conflicts 2026-02-25 13:07:05 +05:30
Jannat Patel 3a1a7db386 chore: resolved conflicts 2026-02-25 13:06:37 +05:30
Jannat Patel a5e948bba8 fix: permission issue during quiz submission
(cherry picked from commit af5bce9e34df945cdc368eceb2a1c3e56023fb34)

# Conflicts:
#	lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json
2026-02-25 07:21:47 +00:00
Jannat Patel e63d83beb5 fix: permission issue during quiz submission
(cherry picked from commit af5bce9e34df945cdc368eceb2a1c3e56023fb34)
2026-02-25 07:21:40 +00:00
Jannat Patel 1ea8705552 Merge pull request #2125 from frappe/develop
chore: merge `develop` into `main-hotfix`
2026-02-25 11:24:11 +05:30
Jannat Patel 61193b71f4 Merge branch 'main-hotfix' into develop 2026-02-25 11:09:48 +05:30
Jannat Patel 2331ddfc67 Merge pull request #2123 from frappe/main-hotfix
chore: merge 'main-hotfix' into 'main'
2026-02-25 10:55:53 +05:30
Jannat Patel afe9674a6a Merge pull request #2124 from frappe/mergify/bp/main-hotfix/pr-2121
chore: sync translations from crowdin (backport #2121)
2026-02-25 10:47:30 +05:30
Jannat Patel 5b22ef46c0 chore: resolved conflicts 2026-02-25 10:40:06 +05:30
MochaMind 8f1604e237 chore: Persian translations
(cherry picked from commit 63321fe2c8)
2026-02-25 05:08:21 +00:00
MochaMind a9f4eb1291 chore: Spanish translations
(cherry picked from commit 68848fc642)

# Conflicts:
#	lms/locale/es.po
2026-02-25 05:08:20 +00:00
Jannat Patel 26301c26e9 Merge pull request #2121 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-25 10:37:32 +05:30
MochaMind 63321fe2c8 chore: Persian translations 2026-02-24 22:48:48 +05:30
MochaMind 68848fc642 chore: Spanish translations 2026-02-24 22:48:29 +05:30
Jannat Patel aa7ec019bc Merge pull request #2120 from pateljannat/issues-192
fix: misc issues
2026-02-24 18:22:57 +05:30
Jannat Patel eb33155db2 fix: enqueue progress calculation after validating enrollments 2026-02-24 18:13:02 +05:30
Jannat Patel 3088b14d83 fix: recalculate course progress when lesson is inserted or deleted 2026-02-24 17:40:58 +05:30
Jannat Patel bf89f3ba2f fix: show system timezone in certificate request 2026-02-24 13:01:29 +05:30
Jannat Patel 2198adf902 fix: sidebar settings issue if guest access was not allowed 2026-02-24 12:36:46 +05:30
Jannat Patel c5145c6c24 Merge pull request #2119 from pateljannat/issues-191
fix: misc issues
2026-02-24 12:17:41 +05:30
Jannat Patel 499bcd5281 chore: resolved conflicts 2026-02-24 12:05:44 +05:30
Jannat Patel dc4bbdaa55 Merge pull request #2116 from raizasafeel/fix/codesandbox-embed
fix(lesson): render codesandbox
2026-02-24 12:02:52 +05:30
Jannat Patel bf19ebd3a8 fix: assignment submission issue 2026-02-24 12:02:10 +05:30
Jannat Patel fa7e59b4ad Merge pull request #2118 from frappe/mergify/bp/main-hotfix/pr-2117
chore: capture more events for analytics (backport #2117)
2026-02-23 16:59:02 +05:30
Jannat Patel 5fcd3ddabe revert: removed new batch modal changes 2026-02-23 16:45:37 +05:30
Jannat Patel a9dd43d0ea chore: resolved conflicts 2026-02-23 16:44:04 +05:30
Jannat Patel 22e005f19c chore: capture more events for analytics
(cherry picked from commit b3c8fbd833)

# Conflicts:
#	frontend/src/pages/Batches/components/NewBatchModal.vue
#	lms/lms/doctype/lms_course_review/lms_course_review.json
2026-02-23 11:11:44 +00:00
Jannat Patel 5a6a7ff646 Merge pull request #2117 from pateljannat/issues-189
chore: capture more events for analytics
2026-02-23 16:41:23 +05:30
Jannat Patel b3c8fbd833 chore: capture more events for analytics 2026-02-23 16:31:58 +05:30
Jannat Patel f828c76a0f Merge pull request #2115 from pateljannat/issues-188
fix: misc issues
2026-02-23 15:13:02 +05:30
raizasafeel 2634a4e316 fix(lesson): render codesandbox properly 2026-02-23 15:12:08 +05:30
Jannat Patel fb0517caa0 fix: show only instructor tab for admins on home page 2026-02-23 14:54:54 +05:30
Jannat Patel 90151be166 fix: updated app name in workspace and desktop 2026-02-23 12:35:13 +05:30
Jannat Patel 9b0a7f5fa5 Merge pull request #2112 from pateljannat/issues-187
fix: lesson progress issue
2026-02-23 12:03:30 +05:30
Jannat Patel aa93375e6c Merge pull request #2110 from frappe/mergify/bp/main-hotfix/pr-2109
fix: check permission of session user during batch enrollment (backport #2109)
2026-02-23 11:49:25 +05:30
Jannat Patel e8edf33be6 fix: lesson progress issue 2026-02-23 11:49:06 +05:30
Jannat Patel 619f02a74b fix: check permission of session user during batch enrollment
(cherry picked from commit c1260edb00)
2026-02-23 06:08:16 +00:00
Jannat Patel b77c4867e1 Merge pull request #2109 from pateljannat/issues-186
fix: check permission of session user during batch enrollment
2026-02-23 11:36:49 +05:30
Jannat Patel c1260edb00 fix: check permission of session user during batch enrollment 2026-02-23 11:28:58 +05:30
Jannat Patel 41d5ef5fd5 Merge pull request #2108 from pateljannat/issues-185
fix: misc permission issues
2026-02-23 11:22:52 +05:30
Jannat Patel 14937fd4fc fix: check ptype for permission if not admin 2026-02-23 11:06:34 +05:30
Jannat Patel 58826fe30f fix: removed badge page reference from the router 2026-02-23 10:41:13 +05:30
Jannat Patel 0da9eec0af Merge pull request #2105 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-23 10:29:32 +05:30
Jannat Patel bb47fd5ba9 fix: misc permission issues 2026-02-23 10:29:13 +05:30
MochaMind db49cb2d64 chore: Thai translations 2026-02-21 22:13:22 +05:30
MochaMind 58732148e2 chore: Vietnamese translations 2026-02-21 22:13:18 +05:30
MochaMind 08eb7ef17b chore: Dutch translations 2026-02-21 22:13:10 +05:30
MochaMind 8bda7edb7b chore: Spanish translations 2026-02-21 22:13:03 +05:30
Jannat Patel 49d596216d fix: zoom account link issue 2026-02-21 12:58:19 +05:30
Jannat Patel faa9c94970 fix: zoom account link issue 2026-02-21 12:28:55 +05:30
Jannat Patel c596d1e215 fix: misc permission issues 2026-02-21 12:25:47 +05:30
Jannat Patel 235958e432 Merge pull request #2103 from raizasafeel/fix/lesson-body-filter
fix(security): remove ignore_xss_filter to enable HTML sanitization
2026-02-20 14:20:07 +05:30
raizasafeel 7f85dbccec fix: added timestamps for bench migrate 2026-02-20 13:52:13 +05:30
Jannat Patel 5b69ddf9b5 Merge pull request #2099 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-20 13:39:16 +05:30
Jannat Patel dfb7152aa3 fix: video watch time permission and other issues 2026-02-20 13:08:27 +05:30
Raizaaa 2a311bfb6f Merge pull request #2104 from raizasafeel/fix/lms-enrollment
fix: duplicate enrollment validation on update
2026-02-20 12:21:45 +05:30
raizasafeel 0e90627144 fix: duplicate enrollment validation on update 2026-02-20 12:09:08 +05:30
Raizaaa aac1692058 Merge branch 'frappe:develop' into fix/lesson-body-filter 2026-02-20 11:58:57 +05:30
raizasafeel d58d362c72 fix(security): remove ignore_xss_filter to enable HTML sanitization 2026-02-20 11:54:17 +05:30
MochaMind e7f2386d14 chore: Thai translations 2026-02-19 21:23:50 +05:30
MochaMind 79a50d2454 chore: Portuguese, Brazilian translations 2026-02-19 21:23:45 +05:30
MochaMind 936f82c477 chore: Spanish translations 2026-02-19 21:23:24 +05:30
Jannat Patel 133037698c fix: remove read permission on lms settings for lms student 2026-02-19 16:30:13 +05:30
Jannat Patel 07c58251a1 fix: lms certificate request will allow students to read only if they are owner 2026-02-19 16:27:35 +05:30
Jannat Patel c88d36df1e fix: permission issues on badges 2026-02-19 15:59:03 +05:30
Jannat Patel 08373dc2ab fix: refactored job form and permissions 2026-02-19 15:58:44 +05:30
Jannat Patel 44ca59c64a fix: return profile details only if the profile is of an LMS user 2026-02-19 12:51:30 +05:30
Jannat Patel c961923fa0 fix: verify enrollment and admin access before returing batch assessment data 2026-02-19 12:43:50 +05:30
Jannat Patel 72cee75474 fix: only allow lms roles to be modified by moderator 2026-02-19 12:39:55 +05:30
Jannat Patel cb3af6fa63 fix: sanitised badge assignment api 2026-02-19 12:24:47 +05:30
Jannat Patel 0ff14a959d Merge pull request #2096 from pateljannat/issues-184
fix: course form issues
2026-02-19 11:58:09 +05:30
Jannat Patel 35adf49015 fix: course form overflow issue 2026-02-19 11:47:17 +05:30
Jannat Patel e5f0d55ff0 fix: course form permission issue 2026-02-19 11:46:56 +05:30
Jannat Patel ba395fe982 Merge pull request #2081 from pateljannat/batch-dashboard-update
Batch dashboard update
2026-02-18 15:45:01 +05:30
Jannat Patel 8ab6776fa9 fix: redirect from batch/details to batch 2026-02-18 15:36:55 +05:30
Jannat Patel 61d13aeb12 Merge pull request #2094 from frappe/develop
chore: merge `develop` into `main-hotfix`
2026-02-18 12:11:10 +05:30
Jannat Patel 24bfe69985 chore: resolved conflicts 2026-02-18 11:16:00 +05:30
Jannat Patel 7b2a4fe24a Merge pull request #2092 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-18 11:10:40 +05:30
Jannat Patel 6f86b822bf fix: mobile view for batch dashboard 2026-02-18 10:58:49 +05:30
MochaMind c3b2907ebf chore: Portuguese, Brazilian translations 2026-02-17 21:06:06 +05:30
MochaMind 48c5b82c73 chore: Spanish translations 2026-02-17 21:06:04 +05:30
Jannat Patel af273a9a1c refactor: batch student progress 2026-02-17 19:38:19 +05:30
Jannat Patel 3b80ccd8db Merge pull request #2083 from raizasafeel/fix/dark-mode
fix: dark mode ui
2026-02-17 17:41:40 +05:30
Jannat Patel 6484d551d1 Merge pull request #2091 from frappe/mergify/bp/main-hotfix/pr-2087
chore: project URLs (backport #2087)
2026-02-17 17:41:05 +05:30
Ankush Menat 719d7b5e88 chore: project URLs (#2087)
(cherry picked from commit 3cd9d89f0b)
2026-02-17 11:38:09 +00:00
Ankush Menat 3cd9d89f0b chore: project URLs (#2087) 2026-02-17 15:54:23 +05:30
raizasafeel 6e44da1993 test: update batch creation test to match Badge component 2026-02-17 15:13:13 +05:30
Jannat Patel 44b7a210ce chore: resolved conflicts 2026-02-17 15:04:31 +05:30
Jannat Patel 0e382f77ef Merge pull request #2048 from raizasafeel/fix/video-embedding
fix(lesson): vimeo player rendered for private and unsanitized content
2026-02-17 14:57:00 +05:30
raizasafeel 7a1a247113 Merge upstream/develop into fix/dark-mode 2026-02-17 14:28:46 +05:30
raizasafeel 2c6ab3c331 Merge remote-tracking branch 'upstream/develop' into fix/video-embedding 2026-02-17 14:21:22 +05:30
Jannat Patel 79dba165c5 Merge pull request #2084 from pateljannat/issues-183
fix: misc issues
2026-02-17 13:57:09 +05:30
Jannat Patel d83f3464cd fix: permission issue when cancelling evaluation 2026-02-17 13:35:15 +05:30
Jannat Patel e2ef8f732d test: fixed category input selection in course creation test 2026-02-17 12:09:29 +05:30
Jannat Patel 919904a7f1 refactor: autocomplete component 2026-02-17 12:05:40 +05:30
raizasafeel 8453226f29 fix: add vimeo emded URL to extract hash properly 2026-02-17 11:38:26 +05:30
Jannat Patel 03759ca3c3 fix: spacing issue on course overview page 2026-02-16 19:53:33 +05:30
Jannat Patel c1608f8cc4 refactor: Link component 2026-02-16 19:48:58 +05:30
Jannat Patel 73b20653f0 chore: updated release process with main-hotfix 2026-02-16 18:33:45 +05:30
Jannat Patel 7e683f8b44 fix: permission checks for api 2026-02-16 18:20:02 +05:30
Jannat Patel eba1815390 fix: permission issues when adding new members 2026-02-16 18:05:53 +05:30
raizasafeel 7564f0418b fix: dark mode text and divider colors in course dashboard 2026-02-16 17:03:35 +05:30
raizasafeel 7e9cca2782 refactor: replace deprecated input with formcontrol in announcement modal 2026-02-16 17:00:18 +05:30
raizasafeel dbc7e7d6d4 fix: dark mode styling for controls and modals 2026-02-16 16:58:32 +05:30
raizasafeel ae25cfae6e fix: text editor border color in dark mode across forms and modals 2026-02-16 16:58:32 +05:30
raizasafeel 970635430b fix(settings): dark mode divider and text colors 2026-02-16 16:58:32 +05:30
raizasafeel fe869a5988 fix(batch): use frappe-ui badge for dark mode compatibility 2026-02-16 16:58:32 +05:30
raizasafeel 7ea8040790 fix(sidebar): configuration popover and dark mode fixes 2026-02-16 16:58:32 +05:30
Jannat Patel 9f6f717585 fix: discussions reply endpoint permission check 2026-02-16 12:18:41 +05:30
Jannat Patel 641d729bd1 refactor: student batch dashboard 2026-02-16 12:17:13 +05:30
Jannat Patel ee73d8db86 Merge pull request #2077 from pateljannat/issues-182
refactor: MultiSelect field
2026-02-16 09:10:46 +05:30
Jannat Patel c7b5f9a04d Merge pull request #2078 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-16 09:10:28 +05:30
MochaMind fa4c3a8ad7 chore: Persian translations 2026-02-15 19:54:51 +05:30
MochaMind 71318bff04 chore: Portuguese translations 2026-02-15 19:54:42 +05:30
MochaMind 186ddc93c8 chore: Portuguese, Brazilian translations 2026-02-14 19:58:13 +05:30
Jannat Patel 2f1d9a8690 refactor: MultiSelect field 2026-02-13 13:04:02 +05:30
Jannat Patel 5fc7c52bfe Merge pull request #2073 from UmakanthKaspa/fix/categories-dark-mode-text
fix: add text color for category names in dark mode
2026-02-13 10:18:29 +05:30
Jannat Patel d0da6e7401 Merge pull request #2076 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-13 10:17:53 +05:30
UmakanthKaspa a437c197a5 fix: format code with pre-commit 2026-02-12 21:41:19 +05:30
Jannat Patel 944fd6d013 refactor: new batch quick entry modal 2026-02-12 19:52:46 +05:30
MochaMind 80a9f2abe2 chore: Spanish translations 2026-02-12 19:28:48 +05:30
Jannat Patel c0298f0a70 Merge branch 'develop' of https://github.com/frappe/lms into batch-dashboard-update 2026-02-12 11:09:19 +05:30
Jannat Patel c30b21e5ae Merge pull request #2075 from pateljannat/issues-181
fix: issues on home page
2026-02-12 11:07:12 +05:30
Jannat Patel 3e3afa63c2 fix: issues on home page 2026-02-12 10:44:01 +05:30
Jannat Patel 7ef8aad2c8 fix: dirty state of batch form 2026-02-12 10:33:21 +05:30
Jannat Patel c00cb100a9 Merge pull request #2072 from UmakanthKaspa/fix/job-details-missing-type-hint
fix: add missing type annotation to get_job_details
2026-02-11 21:41:28 +05:30
Jannat Patel f824ac3c28 Merge pull request #2070 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-11 21:40:28 +05:30
UmakanthKaspa 2dea096fa0 fix: use semantic bg class for MultiSelect "Create New" button 2026-02-11 21:39:30 +05:30
UmakanthKaspa f1853a3c97 fix: add text color for category names in dark mode 2026-02-11 21:13:48 +05:30
UmakanthKaspa 4995f8e3fd fix: add missing type annotation to get_job_details 2026-02-11 20:38:08 +05:30
Jannat Patel f59eecd34e fix: circular dependency issues 2026-02-11 19:24:50 +05:30
MochaMind 560ac8d5c4 chore: Burmese translations 2026-02-11 18:52:08 +05:30
Jannat Patel eab929da47 refactor: batch form 2026-02-11 18:42:42 +05:30
Jannat Patel d370ca796f Merge pull request #2067 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-10 19:53:48 +05:30
Jannat Patel e9f0b12550 feat: batch page new look 2026-02-10 19:53:26 +05:30
MochaMind a4035168be chore: Italian translations 2026-02-10 17:32:47 +05:30
Jannat Patel 70872857d1 fix: show console error if course creation fails 2026-02-10 10:55:59 +05:30
Jannat Patel 332334b556 Merge pull request #2066 from UmakanthKaspa/fix/course-creation-error-toast
fix: show error toast when course creation fails
2026-02-10 10:48:34 +05:30
UmakanthKaspa 1d91baa9c5 fix: show error toast when course creation fails 2026-02-09 13:41:12 +00:00
Jannat Patel 1e8040ef7b Merge pull request #2036 from Aradhya-Tripathi/setup-fix
fix(setup): Add frappe dependency and build utils
2026-02-09 17:21:05 +05:30
Jannat Patel ad6e0a3b80 Merge pull request #2064 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-09 15:32:59 +05:30
Jannat Patel 8f6810923d fix: only published courses should be added to batch 2026-02-09 14:42:32 +05:30
Jannat Patel 990db83ab3 chore: Italian translations 2026-02-09 13:02:11 +05:30
Jannat Patel 01f08ba449 chore: fixed typing for non mandatory fields of certificate 2026-02-09 12:44:54 +05:30
Jannat Patel 7a3701cc10 Merge pull request #2063 from pateljannat/issues-180
chore: added type hints to all whitelisted functions
2026-02-06 17:29:57 +05:30
Jannat Patel f021ddd84c chore: fixed type annotations for doc_event methods 2026-02-06 17:08:49 +05:30
Jannat Patel 0e3157c57e chore: fixed type check for batch flows 2026-02-06 16:53:25 +05:30
Raizaaa 22eb8b9f3f Merge branch 'frappe:develop' into fix/video-embedding 2026-02-06 16:51:52 +05:30
Jannat Patel 9609398643 chore: fixed type check for batch flows 2026-02-06 16:18:37 +05:30
Jannat Patel cd0d4c413d chore: added type hints to all whitelisted functions 2026-02-06 16:01:49 +05:30
Jannat Patel 1bbdff9aaf Merge pull request #2060 from raizasafeel/translations-fix
fix(profile): translations in tab are now rendered
2026-02-06 15:00:20 +05:30
Jannat Patel 8754d0498c Merge pull request #2062 from pateljannat/issues-179
fix: improved the default print format
2026-02-06 14:59:08 +05:30
raizasafeel 395ac52740 fix(assessments): translation render in list heading 2026-02-06 14:46:02 +05:30
Jannat Patel 29cdbe5b8b Merge pull request #2061 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-06 14:38:40 +05:30
Raizaaa 0677c21dc7 Merge branch 'frappe:develop' into translations-fix 2026-02-06 14:37:22 +05:30
Jannat Patel 1a58e2669f fix: improved the default print format 2026-02-06 14:33:24 +05:30
raizasafeel 3fa27024f9 fix(breadcrumbs): translations are now rendered 2026-02-06 12:53:17 +05:30
Jannat Patel 04c4069c75 chore: Serbian (Latin) translations 2026-02-06 12:08:55 +05:30
Jannat Patel dd77b01ff1 chore: Bosnian translations 2026-02-06 12:08:52 +05:30
Jannat Patel 085614bca6 chore: Croatian translations 2026-02-06 12:08:50 +05:30
Jannat Patel ef2606c41a chore: Swedish translations 2026-02-06 12:08:41 +05:30
Jannat Patel 1d95361587 chore: Serbian (Cyrillic) translations 2026-02-06 12:08:40 +05:30
raizasafeel 6ead16edf0 fix(profile): translations in tab are now rendered 2026-02-06 10:10:29 +05:30
Jannat Patel 31d21bf689 Merge pull request #2056 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-05 13:07:15 +05:30
Jannat Patel c5ee140551 Merge pull request #2054 from pateljannat/issues-178
feat: student progress in course dashboard
2026-02-05 12:40:26 +05:30
Jannat Patel 8e97b2f5bb chore: codecov config 2026-02-05 12:33:38 +05:30
Jannat Patel 19171a8019 chore: removed files that are no longer used 2026-02-05 12:24:56 +05:30
Jannat Patel 3f49cf0c9c chore: added type hints to course assessment progress functions 2026-02-05 12:24:36 +05:30
Jannat Patel 8f9cc536e2 chore: Esperanto translations 2026-02-05 12:13:55 +05:30
Jannat Patel 573bc74a41 chore: Serbian (Latin) translations 2026-02-05 12:13:53 +05:30
Jannat Patel bb2552b30c chore: Norwegian Bokmal translations 2026-02-05 12:13:52 +05:30
Jannat Patel 20ac312f57 chore: Bosnian translations 2026-02-05 12:13:50 +05:30
Jannat Patel f58842438b chore: Burmese translations 2026-02-05 12:13:49 +05:30
Jannat Patel a34f99ed49 chore: Croatian translations 2026-02-05 12:13:48 +05:30
Jannat Patel 44b7243f75 chore: Thai translations 2026-02-05 12:13:46 +05:30
Jannat Patel d2f7d80114 chore: Persian translations 2026-02-05 12:13:45 +05:30
Jannat Patel 192b246381 chore: Indonesian translations 2026-02-05 12:13:43 +05:30
Jannat Patel 17d9a3991e chore: Portuguese, Brazilian translations 2026-02-05 12:13:42 +05:30
Jannat Patel 3407a02046 chore: Vietnamese translations 2026-02-05 12:13:41 +05:30
Jannat Patel be3546e79c chore: Chinese Simplified translations 2026-02-05 12:13:39 +05:30
Jannat Patel 556067de7a chore: Turkish translations 2026-02-05 12:13:38 +05:30
Jannat Patel 4ce08af516 chore: Swedish translations 2026-02-05 12:13:36 +05:30
Jannat Patel 8a4477a01f chore: Serbian (Cyrillic) translations 2026-02-05 12:13:35 +05:30
Jannat Patel 21d868a355 chore: Slovenian translations 2026-02-05 12:13:33 +05:30
Jannat Patel 68a2cc1003 chore: Russian translations 2026-02-05 12:13:32 +05:30
Jannat Patel 5a288836e0 chore: Portuguese translations 2026-02-05 12:13:30 +05:30
Jannat Patel 4eab5e2867 chore: Polish translations 2026-02-05 12:13:29 +05:30
Jannat Patel 6082093fb6 chore: Dutch translations 2026-02-05 12:13:27 +05:30
Jannat Patel 66c26c2a2c chore: Italian translations 2026-02-05 12:13:26 +05:30
Jannat Patel f7eaf3faaa chore: Hungarian translations 2026-02-05 12:13:24 +05:30
Jannat Patel 82a43b4f24 chore: German translations 2026-02-05 12:13:23 +05:30
Jannat Patel 5bd33a1536 chore: Danish translations 2026-02-05 12:13:21 +05:30
Jannat Patel a063c0735c chore: Czech translations 2026-02-05 12:13:20 +05:30
Jannat Patel 732db8290d chore: Arabic translations 2026-02-05 12:13:19 +05:30
Jannat Patel 2b1d57f2bc chore: Spanish translations 2026-02-05 12:13:17 +05:30
Jannat Patel c8c051c1de chore: French translations 2026-02-05 12:13:16 +05:30
Jannat Patel 13139bc2de test: course assessment progress 2026-02-05 12:10:24 +05:30
Jannat Patel 9814abf55f fix: dark mode issues of course dashboard 2026-02-04 16:04:28 +05:30
Jannat Patel 249ecb8c4c Merge pull request #2053 from frappe/develop
chore: merge 'develop' into 'main'
2026-02-04 15:24:23 +05:30
Jannat Patel 582540e7f0 feat: student progress on course dashboard 2026-02-03 21:31:31 +05:30
raizasafeel 2f3fa7c295 fix: added regex anchors to embed urls 2026-02-03 16:22:50 +05:30
raizasafeel 3b49aac1b3 refactor: removed unused functions 2026-02-03 16:14:58 +05:30
raizasafeel dc25b408e6 fix(vimeo): video player is rendered for private videos and unsanitized vimeo links 2026-02-03 14:51:17 +05:30
raizasafeel c8d9b97ab7 refactor: reuse function 'escapehtml' from utils 2026-02-03 14:01:48 +05:30
Jannat Patel 754d3cf2ca fix: import permissions 2026-02-03 10:56:22 +05:30
Jannat Patel e4268d0437 fix: allow attaching payment information for batch enrollment 2026-02-03 10:49:48 +05:30
Jannat Patel 8febe21aa8 fix: dont allow contact us email sending to guest users 2026-02-03 10:48:59 +05:30
Jannat Patel 5384b26610 Merge pull request #2042 from raizasafeel/fix/chapter-deletion
test(chapter): added reindexing test on chapter deletion
2026-02-02 19:20:55 +05:30
Jannat Patel 2a4650e5ed Merge pull request #2044 from raizasafeel/fix/quiz-order
fix(batches): order assessments by their index
2026-02-02 19:12:50 +05:30
Jannat Patel 737993c543 Merge pull request #2043 from raizasafeel/fix/multilanguage-filters
fix: filter tabs not working in non-english languages
2026-02-02 18:34:50 +05:30
raizasafeel 58b49e3608 fix(batches): order assessments by their index 2026-02-02 18:28:06 +05:30
Raizaaa 27553464d6 Merge branch 'frappe:develop' into fix/chapter-deletion 2026-02-02 16:28:06 +05:30
raizasafeel be76268c70 fix(batches): use constant value for filter instead of translated label 2026-02-02 16:22:37 +05:30
raizasafeel df2f2e6603 fix(courses): use constant value for filter instead of translated label 2026-02-02 16:21:31 +05:30
Jannat Patel fb1e1ec2e4 Merge pull request #2040 from pateljannat/issues-177
fix: permissions cleanup
2026-02-02 15:55:22 +05:30
Jannat Patel ac81d1817b fix: batch dashboard chart visibility 2026-02-02 15:43:36 +05:30
Jannat Patel da33e1d3bd test: course and batch details 2026-02-02 15:11:16 +05:30
Jannat Patel 24a511f48e fix: do nor return details of unpublished courses and batches via api 2026-02-02 14:44:13 +05:30
Jannat Patel 14e669435f fix: permissions cleanup 2026-02-02 13:14:16 +05:30
Jannat Patel 0407f01016 Merge pull request #2038 from vishwajeet-13/fix/issue-date
fix: issue date not coming from backend
2026-02-02 11:06:53 +05:30
vishwajeet-13 2a63f781ac fix: issue date not coming from backend 2026-01-30 17:37:07 +05:30
Aradhya-Tripathi 3e7555264f fix(setup): Add frappe dependency and build utils 2026-01-30 15:12:23 +05:30
Jannat Patel ab155ae609 Merge pull request #2017 from Anexus5919/fix-instructor-notes-settings-menu
fix: instructor notes block settings menu overlapped by sidebar
2026-01-30 13:02:07 +05:30
Jannat Patel 2c60521894 Merge pull request #2018 from Anexus5919/fix-lesson-form-stale-state
fix: reindex lessons after deletion to prevent stale form state
2026-01-30 13:01:32 +05:30
Jannat Patel 4fd3e2549a fix: batch access conditions 2026-01-30 13:00:49 +05:30
Jannat Patel 6d3f7ef3c1 chore: resolved conflicts 2026-01-30 12:53:57 +05:30
Jannat Patel 10a9a5230e Merge pull request #2001 from frappe/feat/config-fe-base-path
feat: configurable frontend base path
2026-01-30 12:40:43 +05:30
Jannat Patel bfda88dfd2 chore: resolved conflicts 2026-01-30 12:30:53 +05:30
Jannat Patel 4be34848a4 fix: allow mark as read notification endpoints in auth 2026-01-30 12:26:29 +05:30
Jannat Patel c51eae5665 fix: show course progress summary if average progress is greater than 0 2026-01-30 11:19:08 +05:30
Jannat Patel 6c06a86af4 fix: reset the value of multiple of only one correct answer in quiz question 2026-01-30 11:08:53 +05:30
Jannat Patel 8be53d9050 Merge pull request #2035 from pateljannat/issues-176
fix: api permissions
2026-01-29 21:54:32 +05:30
Jannat Patel 015f903d68 fix: api permissions 2026-01-29 21:42:20 +05:30
Jannat Patel dc06ad6b22 fix: removed tags methods 2026-01-29 20:40:35 +05:30
Jannat Patel 5689fbb455 fix: api permissions 2026-01-29 20:32:02 +05:30
Jannat Patel 62631daafb chore: resolved conflicts 2026-01-29 19:55:13 +05:30
Jannat Patel a9a322c9af Merge pull request #2015 from pateljannat/enroll-students-in-course-from-ui
feat: course admin dashboard
2026-01-29 19:53:27 +05:30
Jannat Patel 933bc58264 fix: api permissions 2026-01-29 19:52:56 +05:30
Jannat Patel 8896a79c09 Merge branch 'develop' into enroll-students-in-course-from-ui 2026-01-29 16:42:26 +05:30
Jannat Patel 3170c066dc Merge pull request #2012 from raizasafeel/fix/chapter-deletion
fix(chapter): recalculate indices on deletion to prevent duplicate index errors
2026-01-29 16:37:44 +05:30
Jannat Patel 7360780022 Merge pull request #1998 from raizasafeel/fix/course-deletion
fix(course): resolve deletion failure for enrolled courses
2026-01-29 16:35:59 +05:30
Jannat Patel 7fe398fc66 test: fixed course ui tests based on new flow 2026-01-29 16:17:34 +05:30
Jannat Patel 5970540a99 feat: lesson completion rate in course dashboard 2026-01-29 15:24:20 +05:30
raizasafeel a882432702 test: added test for chapter deletion and renumbering 2026-01-29 13:32:37 +05:30
raizasafeel f8b6dfc981 Merge branch 'develop' into fix/chapter-deletion 2026-01-29 13:27:29 +05:30
Jannat Patel 486e2b4a37 Merge branch 'develop' of https://github.com/frappe/lms into enroll-students-in-course-from-ui 2026-01-29 12:35:58 +05:30
Jannat Patel d8891b8a7d fix: brand settings 2026-01-29 12:35:44 +05:30
Jannat Patel 98c5318b66 feat: new course form slow 2026-01-29 10:50:28 +05:30
Jannat Patel a353635bb9 Merge pull request #2028 from raizasafeel/fix/restrict-batch-student-details
fix(batch): restrict student list in batch details API to authorised roles
2026-01-28 21:20:33 +05:30
Jannat Patel 9eeb948f04 Merge pull request #2029 from raizasafeel/fix/quiz-questions-empty-on-back-navigation
fix(quiz): questions list empty on back navigation from test quiz
2026-01-28 21:19:20 +05:30
raizasafeel 383850aeb8 fix(quiz): questions list empty on back navigation from test quiz 2026-01-28 16:30:36 +05:30
raizasafeel fb0499acc4 fix(Batch): restrict student list in batch details API 2026-01-28 14:41:10 +05:30
Jannat Patel ff978f5c29 Merge pull request #2022 from raizasafeel/fix/multiselect-last-option-visibility
fix: prevent 'Create New' button from overlapping last dropdown element
2026-01-28 11:33:36 +05:30
Jannat Patel e8768d5687 Merge pull request #2026 from frappe/develop
chore: merge 'develop' into 'main'
2026-01-28 11:32:28 +05:30
Jannat Patel fa1b583968 Merge branch 'main' into develop 2026-01-28 11:15:28 +05:30
Jannat Patel b50d584a5b feat: new course modal 2026-01-28 10:57:58 +05:30
Jannat Patel 5271709094 Merge pull request #2016 from frappe/pot_develop_2026-01-23
chore: update POT file
2026-01-27 14:37:20 +05:30
raizasafeel b66b603af2 fix(MultiSelect): prevent 'Create New' button from overlapping dropdown option 2026-01-27 14:12:43 +05:30
Adarsh Singh 53c77f9070 fix: reindex lessons after deletion to prevent stale form state
When a lesson was deleted, the remaining lessons' idx values were not resequenced. This caused Add Lesson to navigate to an existing lesson instead of creating a new one, showing stale data in the form.
2026-01-24 17:04:38 +05:30
Jannat Patel 49ed082831 feat: allow moderators to create new payment from settings 2026-01-24 11:31:37 +05:30
Adarsh Singh 05d21cf817 fix: instructor notes block settings menu overlapped by sidebar
PR #1987 fixed the "+" (Add block) menu positioning but missed the
block settings menu (6-dot "Click to tune" icon) which uses a different
parent container (.ce-toolbar__actions instead of .ce-toolbox).

This adds the missing selector to fix both menus.

Related: #1990
2026-01-24 00:25:21 +05:30
frappe-pr-bot 39473f0037 chore: update POT file 2026-01-23 16:05:21 +00:00
Jannat Patel 0e8b232ef1 feat: course admin dashboard 2026-01-23 18:26:09 +05:30
raizasafeel aeb2724e82 test: use BaseTestUtils in test_auth for consistent test setup 2026-01-23 12:26:03 +05:30
Jannat Patel b429fb2e47 Merge pull request #1997 from raizasafeel/fix/markdown-paste-render
fix(lesson): ensure markdown text is displayed when pasted
2026-01-23 10:25:25 +05:30
Jannat Patel 14a1c2ac07 Merge pull request #2014 from raizasafeel/fix/lms-import-get-frappe-version
refactor(lms): update get_frappe_version import
2026-01-22 18:25:30 +05:30
raizasafeel 36b0594960 refactor(lms): update get_frappe_version import 2026-01-22 17:42:06 +05:30
raizasafeel cc3cb7ac8d Merge remote-tracking branch 'upstream/develop' into fix/course-deletion 2026-01-22 16:33:07 +05:30
raizasafeel 8bf896f766 refactor(tests): use BaseTestUtils inheritance for tests 2026-01-22 16:05:48 +05:30
raizasafeel 9e61982dcb refactor(tests): extract shared test utilities into BaseTestUtils 2026-01-22 16:03:01 +05:30
Jannat Patel 59e40d5a83 Merge branch 'develop' into fix/chapter-deletion 2026-01-22 14:00:12 +05:30
Jannat Patel 412bdeb085 Merge pull request #2007 from pateljannat/issues-174
fix: misc issues
2026-01-22 13:59:54 +05:30
Jannat Patel 4ef0bd30d9 test: fixed authentication tests 2026-01-22 13:16:12 +05:30
Jannat Patel 87e1fa8d6c Merge pull request #2013 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-22 12:16:01 +05:30
Jannat Patel 984ea27d59 chore: Serbian (Latin) translations 2026-01-22 01:46:42 +05:30
Jannat Patel 9d2c7a7ce0 chore: Norwegian Bokmal translations 2026-01-22 01:46:41 +05:30
Jannat Patel 3b2768cdcc chore: Bosnian translations 2026-01-22 01:46:39 +05:30
Jannat Patel 9ef5f01a80 chore: Croatian translations 2026-01-22 01:46:37 +05:30
Jannat Patel fe492d1134 chore: Portuguese, Brazilian translations 2026-01-22 01:46:33 +05:30
Jannat Patel 3a15e132c2 chore: Vietnamese translations 2026-01-22 01:46:32 +05:30
Jannat Patel 2fb20e2db1 chore: Chinese Simplified translations 2026-01-22 01:46:31 +05:30
Jannat Patel 1fd9b4e626 chore: Turkish translations 2026-01-22 01:46:29 +05:30
Jannat Patel 11668235c2 chore: Swedish translations 2026-01-22 01:46:27 +05:30
Jannat Patel d349af940a chore: Serbian (Cyrillic) translations 2026-01-22 01:46:26 +05:30
Jannat Patel 7eadb6b683 chore: Polish translations 2026-01-22 01:46:22 +05:30
Jannat Patel 904b6c3462 chore: German translations 2026-01-22 01:46:20 +05:30
Jannat Patel 733ab34007 chore: Arabic translations 2026-01-22 01:46:17 +05:30
Jannat Patel 6215817954 chore: Spanish translations 2026-01-22 01:46:15 +05:30
Jannat Patel 1760b01914 chore: French translations 2026-01-22 01:46:14 +05:30
Jannat Patel d218a4773a chore: Italian translations 2026-01-22 01:46:12 +05:30
Jannat Patel 6fbe32cacd chore: Hungarian translations 2026-01-22 01:46:11 +05:30
Jannat Patel c10dd9f5b6 chore: Persian translations 2026-01-22 01:46:09 +05:30
Jannat Patel 7eb1237aa0 chore: Russian translations 2026-01-22 01:46:07 +05:30
Jannat Patel 570e8eaf34 chore: Dutch translations 2026-01-22 01:46:06 +05:30
raizasafeel 297ffa5171 fix(chapter): index recalculation on deletion 2026-01-21 14:37:10 +05:30
Jannat Patel 355c752ec1 Merge pull request #2008 from pateljannat/issues-175
fix: misc issues
2026-01-20 22:08:56 +05:30
Jannat Patel f1031c49a9 fix: allow reset password endpoint 2026-01-20 22:08:39 +05:30
Jannat Patel 043c7902a3 test: updated test for open to work 2026-01-20 22:07:42 +05:30
Jannat Patel 338f46ac17 chore: changed frappe dependency range 2026-01-20 16:57:30 +05:30
Jannat Patel 9987ea1db6 fix: enable pulse only for logged in users 2026-01-20 16:52:42 +05:30
Jannat Patel d507479bda fix: filters on jobs page 2026-01-20 16:52:20 +05:30
Jannat Patel afbdb46fe8 fix: open to work patch 2026-01-20 15:47:55 +05:30
Jannat Patel 1308101fe6 fix: parse allowed_endpoints to list if its string 2026-01-20 12:45:28 +05:30
Jannat Patel c2d6e160d9 fix: renamed open to opportunities to open to work 2026-01-20 12:43:00 +05:30
Jannat Patel 28c88ec519 Merge pull request #2005 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-20 10:58:07 +05:30
Jannat Patel f77eeafb9e chore: Esperanto translations 2026-01-20 01:43:56 +05:30
Jannat Patel 92b8343db3 chore: Serbian (Latin) translations 2026-01-20 01:43:54 +05:30
Jannat Patel ac44cdc303 chore: Norwegian Bokmal translations 2026-01-20 01:43:53 +05:30
Jannat Patel 75dcea4c7e chore: Bosnian translations 2026-01-20 01:43:52 +05:30
Jannat Patel ee03d75b5f chore: Burmese translations 2026-01-20 01:43:50 +05:30
Jannat Patel 31f372629a chore: Croatian translations 2026-01-20 01:43:49 +05:30
Jannat Patel 1b30f476e8 chore: Thai translations 2026-01-20 01:43:47 +05:30
Jannat Patel e4738e9a50 chore: Tamil translations 2026-01-20 01:43:46 +05:30
Jannat Patel 893f9d34fd chore: Indonesian translations 2026-01-20 01:43:44 +05:30
Jannat Patel 2c17a03d36 chore: Portuguese, Brazilian translations 2026-01-20 01:43:43 +05:30
Jannat Patel 74cbe1bb47 chore: Vietnamese translations 2026-01-20 01:43:41 +05:30
Jannat Patel 88c468068b chore: Chinese Simplified translations 2026-01-20 01:43:40 +05:30
Jannat Patel 6b5e269564 chore: Turkish translations 2026-01-20 01:43:38 +05:30
Jannat Patel 97d9460157 chore: Swedish translations 2026-01-20 01:43:37 +05:30
Jannat Patel 0d614a5919 chore: Serbian (Cyrillic) translations 2026-01-20 01:43:35 +05:30
Jannat Patel 3f6afbf1ff chore: Slovenian translations 2026-01-20 01:43:34 +05:30
Jannat Patel 6d0996ebfd chore: Portuguese translations 2026-01-20 01:43:32 +05:30
Jannat Patel 14e085c826 chore: Polish translations 2026-01-20 01:43:31 +05:30
Jannat Patel 53bf2f1fe8 chore: German translations 2026-01-20 01:43:29 +05:30
Jannat Patel b06e199432 chore: Danish translations 2026-01-20 01:43:28 +05:30
Jannat Patel 06e1ec38ad chore: Czech translations 2026-01-20 01:43:27 +05:30
Jannat Patel 3063236bfb chore: Arabic translations 2026-01-20 01:43:25 +05:30
Jannat Patel e3a2eb382d chore: Spanish translations 2026-01-20 01:43:24 +05:30
Jannat Patel 566ef576f5 chore: French translations 2026-01-20 01:43:23 +05:30
Jannat Patel 80ad2da206 chore: Italian translations 2026-01-20 01:43:21 +05:30
Jannat Patel 5d720031b6 chore: Hungarian translations 2026-01-20 01:43:20 +05:30
Jannat Patel ac719b9e8e chore: Persian translations 2026-01-20 01:43:19 +05:30
Jannat Patel db9ebb1d86 chore: Russian translations 2026-01-20 01:43:17 +05:30
Jannat Patel 37f6d62101 chore: Dutch translations 2026-01-20 01:43:16 +05:30
Jannat Patel f7a0fc8b5f build: removed unused inter files 2026-01-19 19:54:06 +05:30
Jannat Patel 0fbe015607 fix: center aligned the load more button on batch student list 2026-01-19 19:49:30 +05:30
Jannat Patel 61909c8498 fix: track cmi.completion_status for SCORM 2004 2026-01-19 19:49:05 +05:30
Jannat Patel f297f2630b fix: only block endpoints if enabled in site config 2026-01-19 19:03:51 +05:30
Jannat Patel 9425f2aa0e fix: allow custom app endpoints 2026-01-19 18:56:39 +05:30
Jannat Patel 535bb60d69 Merge pull request #2004 from pateljannat/issues-173
fix: allow enabled server script endpoints
2026-01-19 18:33:51 +05:30
Jannat Patel 5175050ed6 fix: allow enabled server script endpoints 2026-01-19 18:33:14 +05:30
Jannat Patel 66486a5a1c fix: do no execute send_notification bg job from batch immediately 2026-01-19 18:17:40 +05:30
Jannat Patel 2d4bf49ab9 Merge pull request #2003 from pateljannat/issues-172
fix: allow social login endpoints
2026-01-19 17:41:10 +05:30
Jannat Patel 50a091a7b0 fix: allow social login endpoints 2026-01-19 17:32:09 +05:30
Jannat Patel 854ebee2e6 Merge branch 'develop' of https://github.com/frappe/lms into issues-172 2026-01-19 17:04:35 +05:30
Jannat Patel c829ab65d9 fix: payment gateway form 2026-01-19 16:28:16 +05:30
Jannat Patel 1e0e10ca59 Merge branch 'develop' into fix/markdown-paste-render 2026-01-19 15:39:15 +05:30
Jannat Patel a8cdc76278 Merge branch 'develop' into fix/course-deletion 2026-01-19 15:32:51 +05:30
Jannat Patel c310460727 Merge pull request #2002 from pateljannat/issues-171
fix: misc issues
2026-01-19 15:31:31 +05:30
Jannat Patel 3e98d962aa test: access to endpoints 2026-01-19 15:06:44 +05:30
Jannat Patel ad90b89d25 fix: restrict comments in notifications to 3 lines 2026-01-19 12:58:43 +05:30
Jannat Patel 92eac9634a test: fixed evaluator schedule test 2026-01-19 12:46:45 +05:30
Jannat Patel 96015246bd refactor: get certified from certification list will now redirect to certification courses 2026-01-19 12:39:54 +05:30
Jannat Patel 0883aedc1d fix: course and bath category filter issue 2026-01-19 12:38:12 +05:30
Jannat Patel c97a5e813c fix: only system users can access high level APIs 2026-01-19 11:47:57 +05:30
raizasafeel 2abc243b88 test: refactor course tests and add deletion test 2026-01-19 11:20:33 +05:30
Jannat Patel 62b18e6ffa Merge pull request #2000 from frappe/pot_develop_2026-01-16
chore: update POT file
2026-01-19 10:35:43 +05:30
raizasafeel 36d813b90f feat: add markdown styling for pasted content 2026-01-19 09:53:41 +05:30
Hussain Nagaria 5ce8e8c4ff test: flaky evaluation schedule
* Earlier logic was flaky because the test calculated the last expected date using a 56‑day window, while the production code builds
  schedules for 60 days. Those extra 4 days sometimes include another Monday or Wednesday, so the schedule ends later than the test
  expects.
2026-01-17 23:22:38 +05:30
Hussain Nagaria 3b88892905 refactor: change global variable to function in hooks 2026-01-17 23:14:33 +05:30
Hussain Nagaria fe1aa3dd40 feat: configurable frontend base path
Co-authored-by: Suraj Shetty <surajshetty3416@users.noreply.github.com>
2026-01-17 23:04:31 +05:30
frappe-pr-bot ec5e197716 chore: update POT file 2026-01-16 16:04:50 +00:00
Jannat Patel 244b5e445c fix: open course and batch from evaluation event modal 2026-01-16 15:59:16 +05:30
raizasafeel 0f927071c0 fix(course): resolve deletion failure for enrolled courses 2026-01-16 11:37:13 +05:30
Jannat Patel 376de99ef7 Merge pull request #1996 from pateljannat/issues-170
fix: misc issues
2026-01-16 11:34:29 +05:30
Jannat Patel 7a649957dd refactor: list for programming exercises 2026-01-15 19:46:13 +05:30
Jannat Patel 0968c90717 fix: quiz shuffle and limit conditions 2026-01-15 15:09:59 +05:30
Jannat Patel e22eca9888 Merge pull request #1992 from raizasafeel/improve_batch_performance
feat(batch): add student pagination and optimize dashboard queries
2026-01-15 14:54:48 +05:30
raizasafeel 5890885475 fix(lesson): ensure markdown text is displayed when pasted 2026-01-15 14:49:04 +05:30
Jannat Patel 4a012a99a4 fix: programming exercise test case deletion 2026-01-15 14:47:03 +05:30
raizasafeel e2c0355821 refactor(batch): simplify dashboard with get_count and conditional rendering 2026-01-15 13:13:40 +05:30
Jannat Patel bcf27b7150 Merge pull request #1991 from nextchamp-saqib/refactor-lms-telemetry
refactor: telemetry
2026-01-15 10:37:27 +05:30
Jannat Patel 078f18d99c chore: capture quiz creation and certificate creation for analytics 2026-01-15 10:18:09 +05:30
Jannat Patel 19258e263d chore: resolved conflicts 2026-01-15 09:41:48 +05:30
Jannat Patel 7a52a2bf46 Merge pull request #1995 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-15 09:30:49 +05:30
Jannat Patel a03be5ab4d chore: Serbian (Latin) translations 2026-01-15 01:14:14 +05:30
Jannat Patel de13c5ddfb chore: Serbian (Cyrillic) translations 2026-01-15 01:14:11 +05:30
Jannat Patel 02564b2e77 chore: Russian translations 2026-01-15 01:14:08 +05:30
Jannat Patel 201e0b96a3 Merge pull request #1993 from pateljannat/issues-169
fix: misc issues
2026-01-14 18:02:33 +05:30
Jannat Patel e7ccf0a711 fix: sanitize image filename before saving for course and jobs 2026-01-14 17:54:23 +05:30
raizasafeel c59be28a26 perf(batch): optimise dashboard with query builder 2026-01-14 15:54:54 +05:30
Saqib Ansari a0ede1dd2a fix: run pre-commit 2026-01-14 14:55:28 +05:30
Saqib Ansari 0aeada4549 refactor: telemetry
* only works with frappe v15.96+
2026-01-14 14:48:05 +05:30
Jannat Patel 6d988eb2b4 chore: changed frappe dependency version 2026-01-14 14:01:21 +05:30
raizasafeel b58d04c7dc feat(batch): add load more pagination for batch students 2026-01-14 13:56:07 +05:30
Jannat Patel e2479cd787 Merge pull request #1978 from Omcodes23/develop
fix: Add missing /lms prefix to assignment submission notification links
2026-01-14 12:46:57 +05:30
Jannat Patel ca30ab8a5e Merge pull request #1987 from pateljannat/issues-168
fix: misc issues
2026-01-14 12:46:39 +05:30
Jannat Patel da87845a4c chore: changed frappe dependency version 2026-01-14 12:37:49 +05:30
Om vataliya e7c2ec6965 Merge branch 'develop' into develop 2026-01-14 11:35:38 +05:30
Jannat Patel a7bcc53e0a fix: dayjs condition for evaluation end date 2026-01-14 10:08:21 +05:30
Jannat Patel 6a5978fed6 fix: instructor notes menu position in lesson form 2026-01-14 09:45:44 +05:30
Jannat Patel 8ff339b7ed fix: misc issues 2026-01-14 09:24:24 +05:30
Jannat Patel 4ace8b2ec0 Merge pull request #1986 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-14 09:01:18 +05:30
Jannat Patel 40c917f255 chore: Esperanto translations 2026-01-14 01:14:55 +05:30
Jannat Patel bf024af8aa chore: Serbian (Latin) translations 2026-01-14 01:14:53 +05:30
Jannat Patel 675caa380f chore: Norwegian Bokmal translations 2026-01-14 01:14:52 +05:30
Jannat Patel ad092a71d5 chore: Bosnian translations 2026-01-14 01:14:51 +05:30
Jannat Patel 971fe8fe64 chore: Burmese translations 2026-01-14 01:14:49 +05:30
Jannat Patel fa63a1d5e5 chore: Croatian translations 2026-01-14 01:14:47 +05:30
Jannat Patel ece642796a chore: Thai translations 2026-01-14 01:14:46 +05:30
Jannat Patel ce8f1a5b77 chore: Tamil translations 2026-01-14 01:14:44 +05:30
Jannat Patel 973630059f chore: Indonesian translations 2026-01-14 01:14:43 +05:30
Jannat Patel 0f7e9d2d95 chore: Portuguese, Brazilian translations 2026-01-14 01:14:41 +05:30
Jannat Patel a7f494f4d8 chore: Vietnamese translations 2026-01-14 01:14:39 +05:30
Jannat Patel eb37bd1106 chore: Chinese Simplified translations 2026-01-14 01:14:38 +05:30
Jannat Patel 02c1f4c19e chore: Turkish translations 2026-01-14 01:14:36 +05:30
Jannat Patel 00040061f6 chore: Swedish translations 2026-01-14 01:14:35 +05:30
Jannat Patel cf6292c2c6 chore: Serbian (Cyrillic) translations 2026-01-14 01:14:33 +05:30
Jannat Patel 0cf9984b6b chore: Slovenian translations 2026-01-14 01:14:32 +05:30
Jannat Patel e793afd063 chore: Portuguese translations 2026-01-14 01:14:30 +05:30
Jannat Patel 6ce655c5b6 chore: Polish translations 2026-01-14 01:14:29 +05:30
Jannat Patel dd7eb9a9c8 chore: German translations 2026-01-14 01:14:27 +05:30
Jannat Patel bd8baa6671 chore: Danish translations 2026-01-14 01:14:25 +05:30
Jannat Patel d387c7b493 chore: Czech translations 2026-01-14 01:14:24 +05:30
Jannat Patel c7a991e1b0 chore: Arabic translations 2026-01-14 01:14:22 +05:30
Jannat Patel f7e8707220 chore: Spanish translations 2026-01-14 01:14:21 +05:30
Jannat Patel 27cbc265e2 chore: French translations 2026-01-14 01:14:19 +05:30
Jannat Patel 949ea333eb chore: Italian translations 2026-01-14 01:14:18 +05:30
Jannat Patel d32a80ef85 chore: Hungarian translations 2026-01-14 01:14:16 +05:30
Jannat Patel d0ee3fe2d0 chore: Persian translations 2026-01-14 01:14:14 +05:30
Jannat Patel d4e3676032 chore: Russian translations 2026-01-14 01:14:13 +05:30
Jannat Patel 0265c9dc4c chore: Dutch translations 2026-01-14 01:14:10 +05:30
Jannat Patel 4c81eef80d Merge pull request #1971 from pateljannat/notifications-feed
Notifications feed
2026-01-13 20:38:10 +05:30
Jannat Patel 57c7a8d85c chore: fixed translatable string 2026-01-13 19:16:36 +05:30
Jannat Patel 775d5ecf3a chore: formatting of BrandSettings 2026-01-13 19:13:12 +05:30
Jannat Patel 60fcf0472e fix: mark notification as read when link is visited 2026-01-13 19:07:40 +05:30
Jannat Patel a09599ec8a feat: notification sent checkbox to prevent resending the notification 2026-01-13 19:06:30 +05:30
Om vataliya fb5d79fc77 Merge branch 'develop' into develop 2026-01-13 16:55:36 +05:30
Jannat Patel 8ad5288226 chore: resolved merge conflicts 2026-01-13 12:18:30 +05:30
Jannat Patel 65b18641b5 Merge branch 'develop' of https://github.com/frappe/lms into develop 2026-01-13 11:22:40 +05:30
Jannat Patel 78494b9963 chore: added frappe dependency range to pyproject 2026-01-13 11:22:23 +05:30
Jannat Patel 1d83402163 Merge pull request #1984 from frappe/pot_develop_2026-01-13
chore: update POT file
2026-01-13 11:14:47 +05:30
frappe-pr-bot 64bdd85b03 chore: update POT file 2026-01-13 05:34:56 +00:00
Jannat Patel 0f1b6f3eeb chore: upgraded node version for pot file runner 2026-01-13 11:02:16 +05:30
Jannat Patel 566711df22 Merge pull request #1983 from pateljannat/issues-167
fix: certification filters
2026-01-13 10:44:24 +05:30
Jannat Patel c9c6aef466 fix: certified participants query 2026-01-13 10:36:17 +05:30
Jannat Patel ad650c73c1 fix: changed order by query for certified participants listing 2026-01-13 10:27:08 +05:30
Jannat Patel af5bc2b071 chore: upgraded node version for linter and release github actions 2026-01-13 10:26:13 +05:30
Jannat Patel b5fcfb62de fix: certification filters 2026-01-13 10:18:06 +05:30
Om vataliya 7dccef6b10 fix: Add missing /lms prefix to assignment submission notification links
Fixes #1969

- Added /lms prefix to notification link in lms_assignment_submission.py
- Added /lms prefix to assessment URL in utils.py
- Ensures consistent routing with other notification links (courses, billing, etc.)
- Resolves 404 errors when users click 'View' on assignment submission notifications
2026-01-09 23:45:29 +05:30
Jannat Patel e1d343528d Merge pull request #1972 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-09 15:47:05 +05:30
Jannat Patel 6eeb688f06 chore: Hungarian translations 2026-01-07 20:03:15 +05:30
Jannat Patel 86e3794d00 Merge pull request #1970 from frappe/develop
chore: merge 'develop' into 'main'
2026-01-07 14:29:49 +05:30
Jannat Patel 45e98b9ddc fix: notification on quiz update and mention 2026-01-07 14:28:56 +05:30
Jannat Patel aad875f72c Merge pull request #1968 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-07 10:59:45 +05:30
Jannat Patel 1698bf0bca chore: Hungarian translations 2026-01-06 20:01:12 +05:30
Jannat Patel ea94813d94 feat: system notification for new courses published 2026-01-06 12:36:15 +05:30
Jannat Patel 142b893f2c Merge pull request #1966 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-06 10:26:01 +05:30
Jannat Patel f88dd84ed3 ci: upgraded python version for pot file generation 2026-01-05 19:55:32 +05:30
Jannat Patel 50660f6720 chore: Persian translations 2026-01-05 19:12:35 +05:30
Jannat Patel 958128060c chore: Dutch translations 2026-01-05 19:12:33 +05:30
Jannat Patel b2b1d2bb00 feat: email notifications for published courses 2026-01-05 18:18:56 +05:30
Jannat Patel a3069bd760 Merge pull request #1965 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-05 12:52:02 +05:30
Jannat Patel f39ee39452 chore: Russian translations 2026-01-04 18:25:34 +05:30
Jannat Patel a43e90e2d0 Merge pull request #1963 from pateljannat/issues-166
fix: payment validation during course enrollment
2026-01-02 16:54:29 +05:30
Jannat Patel 2aa3765ed3 Merge pull request #1962 from rehanrehman389/multiselect
fix: show options on focus and filter selected values
2026-01-02 14:58:17 +05:30
Jannat Patel 45dae0b9d3 fix: payment validation during course enrollment 2026-01-02 14:47:28 +05:30
Jannat Patel b1789cdcba fix: payment validation during course enrollment 2026-01-02 14:42:42 +05:30
Jannat Patel e21f0e3a7f refactor(ui): settings 2026-01-02 14:41:17 +05:30
rehanrehman389 ea9975db2c fix: fix build failure 2026-01-02 12:33:09 +05:30
rehanrehman389 b1c8e01bf5 fix: show options on focus and filter selected values 2026-01-02 12:18:03 +05:30
Jannat Patel 1d19389fc0 Merge pull request #1961 from pateljannat/issues-165
fix: misc issues
2026-01-01 14:51:58 +05:30
Jannat Patel dbd3e17b26 Merge pull request #1912 from royalpinto007/fix/quiz-enter
fix(quizzes): enable enter key to submit quiz creation
2026-01-01 14:28:42 +05:30
Jannat Patel b17c7ca2db fix: statistics chart based on frappe version 2026-01-01 14:26:34 +05:30
Jannat Patel e17af04c9a Merge branch 'develop' into fix/quiz-enter 2026-01-01 13:16:34 +05:30
Jannat Patel 638a9abf88 fix: test case table issue in programming exercise form 2026-01-01 13:09:35 +05:30
Jannat Patel 89f9cbd30d Merge pull request #1959 from pateljannat/full-text-search-improvements
feat: search by instructor name from command palette
2026-01-01 12:30:56 +05:30
Jannat Patel 204fb669c0 test: fixed evaluator schedule test 2026-01-01 12:23:31 +05:30
Jannat Patel 7a24a83d9e fix: improved search result sorting 2026-01-01 12:06:15 +05:30
Jannat Patel edc6007fb6 feat: search by instructor name from command palette 2025-12-31 20:03:44 +05:30
Jannat Patel 27a36540d4 Merge pull request #1957 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-31 12:19:21 +05:30
Frappe PR Bot 6dd1274150 chore(release): Bumped to Version 2.44.0 2025-12-31 06:37:47 +00:00
Jannat Patel 6a80c2ab38 Merge pull request #1952 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-30 10:47:34 +05:30
Jannat Patel 8633444b91 Merge pull request #1953 from pateljannat/open-to-hiring
feat: open to hiring
2025-12-30 10:46:57 +05:30
Jannat Patel cb014a9507 test: certified participants with opportunities and hiring filters 2025-12-30 10:40:26 +05:30
Jannat Patel c241abb820 test: fixed evaluator schedule test 2025-12-29 19:37:02 +05:30
Jannat Patel a497a2d838 fix: mobile layout 2025-12-29 19:14:22 +05:30
Jannat Patel 5e78848d38 chore: Persian translations 2025-12-29 16:39:59 +05:30
Jannat Patel adf897cc08 Merge pull request #1947 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-29 16:03:14 +05:30
Jannat Patel 5053b4e45f test: certified participants data 2025-12-29 16:02:38 +05:30
Jannat Patel 3151854bfd feat: open to hiring 2025-12-29 13:24:04 +05:30
Jannat Patel db5868f69a refactor(ui): no permission page 2025-12-29 13:21:13 +05:30
Jannat Patel 9b6f7635bc chore: Persian translations 2025-12-29 06:49:37 +05:30
Jannat Patel 55ff59095c chore: Persian translations 2025-12-28 06:29:21 +05:30
Jannat Patel 3dce1c0930 Merge pull request #1945 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-26 10:45:27 +05:30
Jannat Patel fd33aa8b70 Merge pull request #1943 from rehanrehman389/python-3.14
build: support Python 3.14
2025-12-26 10:45:06 +05:30
Jannat Patel 05170da762 chore: Serbian (Latin) translations 2025-12-26 05:02:40 +05:30
Jannat Patel 64f0c693d2 chore: Serbian (Cyrillic) translations 2025-12-26 05:02:38 +05:30
Jannat Patel aac1e2d01f chore: Portuguese, Brazilian translations 2025-12-25 04:55:13 +05:30
Jannat Patel 5472c4d387 chore: Chinese Simplified translations 2025-12-25 04:55:11 +05:30
Jannat Patel 39a31a0baf chore: Turkish translations 2025-12-25 04:55:10 +05:30
Jannat Patel cffc740ed3 chore: Russian translations 2025-12-25 04:55:05 +05:30
Rehan Ansari 114f3aae6d build(ci): use python 3.14 and node 24 2025-12-24 19:22:23 +05:30
Rehan Ansari fea7f8f9ae build(deps): bump 2025-12-24 19:21:54 +05:30
Jannat Patel 830f513a06 Merge pull request #1939 from pateljannat/consent-on-billing-page
feat: consent on billing page
2025-12-24 16:50:24 +05:30
Jannat Patel e76ff45241 Merge pull request #1942 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-24 15:57:13 +05:30
Jannat Patel 66f19d06b0 chore: resolved conflicts 2025-12-24 15:54:55 +05:30
Jannat Patel 987b655976 fix: skip self enrollment check if paid batch 2025-12-24 11:18:54 +05:30
Jannat Patel 8619712d20 Merge pull request #1940 from rehanrehman389/dark-mode-fix
fix: dark mode visibility
2025-12-24 09:09:02 +05:30
Jannat Patel bc6ca205d5 Merge pull request #1941 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-24 09:06:27 +05:30
Jannat Patel 48d17e88d9 chore: Esperanto translations 2025-12-24 04:55:52 +05:30
Jannat Patel 1e9f1b7661 chore: Serbian (Latin) translations 2025-12-24 04:55:50 +05:30
Jannat Patel faa6fbf68e chore: Norwegian Bokmal translations 2025-12-24 04:55:49 +05:30
Jannat Patel 789b1016fb chore: Bosnian translations 2025-12-24 04:55:48 +05:30
Jannat Patel 8fb491ac18 chore: Burmese translations 2025-12-24 04:55:46 +05:30
Jannat Patel 3eea872137 chore: Croatian translations 2025-12-24 04:55:45 +05:30
Jannat Patel e4d98019e7 chore: Thai translations 2025-12-24 04:55:44 +05:30
Jannat Patel 7847f681e9 chore: Tamil translations 2025-12-24 04:55:42 +05:30
Jannat Patel 26d7971d99 chore: Persian translations 2025-12-24 04:55:41 +05:30
Jannat Patel e25ef7e07b chore: Indonesian translations 2025-12-24 04:55:40 +05:30
Jannat Patel 85fafe7d56 chore: Portuguese, Brazilian translations 2025-12-24 04:55:38 +05:30
Jannat Patel fdced5c204 chore: Vietnamese translations 2025-12-24 04:55:37 +05:30
Jannat Patel b21fe69123 chore: Chinese Simplified translations 2025-12-24 04:55:36 +05:30
Jannat Patel 4572d03470 chore: Turkish translations 2025-12-24 04:55:35 +05:30
Jannat Patel d348fd9f99 chore: Swedish translations 2025-12-24 04:55:33 +05:30
Jannat Patel 4b6e91e81f chore: Serbian (Cyrillic) translations 2025-12-24 04:55:32 +05:30
Jannat Patel 9126e7cd27 chore: Slovenian translations 2025-12-24 04:55:31 +05:30
Jannat Patel 713ee5287f chore: Russian translations 2025-12-24 04:55:29 +05:30
Jannat Patel 79109f1265 chore: Portuguese translations 2025-12-24 04:55:28 +05:30
Jannat Patel 20f16849bf chore: Polish translations 2025-12-24 04:55:27 +05:30
Jannat Patel 99968f5961 chore: Dutch translations 2025-12-24 04:55:25 +05:30
Jannat Patel 0741fbf583 chore: Italian translations 2025-12-24 04:55:24 +05:30
Jannat Patel 784ed37de0 chore: Hungarian translations 2025-12-24 04:55:23 +05:30
Jannat Patel 1c29b4966c chore: German translations 2025-12-24 04:55:21 +05:30
Jannat Patel a06ea92c8e chore: Danish translations 2025-12-24 04:55:20 +05:30
Jannat Patel 60a47889d2 chore: Czech translations 2025-12-24 04:55:18 +05:30
Jannat Patel d758039b2c chore: Arabic translations 2025-12-24 04:55:17 +05:30
Jannat Patel 4b507f0706 chore: Spanish translations 2025-12-24 04:55:16 +05:30
Jannat Patel 5986838056 chore: French translations 2025-12-24 04:55:14 +05:30
Rehan Ansari e02c99bd16 fix: dark theme styling for programs 2025-12-23 22:35:03 +05:30
Rehan Ansari a3f580a9fb fix: search page dark theme styling 2025-12-23 21:54:07 +05:30
Jannat Patel 43fb0f92df fix: hide consent warning when consent has been provided 2025-12-23 15:05:28 +05:30
Jannat Patel dd4fbfa8a2 feat: billing consent ui 2025-12-23 15:01:14 +05:30
Jannat Patel cf2e57ec40 Merge branch 'develop' of https://github.com/frappe/lms into consent-on-billing-page 2025-12-23 13:09:53 +05:30
Jannat Patel daf2d28f3a Merge pull request #1938 from pateljannat/enrollment-from-batch-main
fix: batch enrollment conditions
2025-12-23 13:05:31 +05:30
Jannat Patel d5e48f9502 fix: batch enrollment conditions 2025-12-23 12:56:17 +05:30
Jannat Patel 3318a1c599 fix: certification list border style 2025-12-23 12:44:36 +05:30
Jannat Patel 3fa2320fe3 Merge pull request #1937 from pateljannat/enrollment-from-batch
fix: batch enrollment conditions
2025-12-23 12:13:11 +05:30
Jannat Patel c0c8fb5bf3 fix: skip cancelled slots when checking if slot is already booked 2025-12-23 12:05:43 +05:30
Jannat Patel acde5ad1d1 fix: batch enrollment conditions 2025-12-23 11:42:45 +05:30
Jannat Patel 0a9b18d04d Merge pull request #1928 from pateljannat/issues-164
fix: misc issues
2025-12-23 11:08:06 +05:30
Jannat Patel 3013372711 fix: don't fetch application count when user is not logged in 2025-12-23 10:58:12 +05:30
Jannat Patel 76979e292d Merge branch 'develop' of https://github.com/frappe/lms into issues-164 2025-12-23 10:40:03 +05:30
Jannat Patel 115d52776f Merge pull request #1936 from rehanrehman389/profile
fix: profile save after image removal
2025-12-23 10:39:36 +05:30
Jannat Patel f1b392ac9b Merge pull request #1935 from rehanrehman389/guest-issue
fix: job details for guest user
2025-12-23 10:38:55 +05:30
Jannat Patel 00198464e9 Merge pull request #1934 from rehanrehman389/fix/jobs
fix: skip closed jobs API call for guest users
2025-12-23 10:37:49 +05:30
Jannat Patel 1b84f00673 Merge pull request #1927 from frappe/pot_develop_2025-12-19
chore: update POT file
2025-12-23 10:36:03 +05:30
Rehan Ansari 3d9850dc73 fix: profile save after image removal 2025-12-22 23:26:41 +05:30
Rehan Ansari dfcf295493 revert: job details api 2025-12-22 23:00:53 +05:30
Rehan Ansari b987bf7f20 revert: job details changes from PR #1890 2025-12-22 22:49:22 +05:30
Rehan Ansari e1d160a898 fix: skip closed jobs API call for guest users 2025-12-22 21:54:11 +05:30
Jannat Patel a3fb63cd08 fix: default livecode url 2025-12-22 11:45:52 +05:30
Jannat Patel 1faa697b6c fix: enrollment eligibility conditions 2025-12-22 11:45:36 +05:30
Jannat Patel 01094cd10a fix: enrollment eligibility conditions 2025-12-22 10:55:36 +05:30
Jannat Patel 4141022431 feat: billing consent 2025-12-22 10:50:45 +05:30
Jannat Patel 69d0efbfa7 Merge pull request #1926 from pateljannat/filter-assignment-by-course
feat: filter assignments by course in lesson
2025-12-19 22:54:29 +05:30
frappe-pr-bot 66cc7d392e chore: update POT file 2025-12-19 16:05:21 +00:00
Jannat Patel 8048cb47c5 feat: filter assignments by course in lesson 2025-12-19 17:42:33 +05:30
Jannat Patel 09c668f7ed fix: description on course details 2025-12-19 16:21:22 +05:30
Jannat Patel 984a63c46a fix: description on course details 2025-12-19 16:20:53 +05:30
Jannat Patel 80c978d265 Merge pull request #1924 from pateljannat/issues-163
fix: certification caching issue
2025-12-19 16:12:47 +05:30
Jannat Patel cde6828c1f fix: certification caching issue 2025-12-19 15:58:46 +05:30
Jannat Patel 51c1b816a1 fix: increase toast duration when evaluation scheduling fails 2025-12-19 14:36:00 +05:30
Jannat Patel 356ce7478c Merge pull request #1923 from pateljannat/improved-evaluation-scheduling
feat: Improved evaluation scheduling
2025-12-19 12:47:49 +05:30
Jannat Patel 2210ef7af7 test: evaluation schedule unavailability 2025-12-19 12:39:39 +05:30
Jannat Patel 0884e1315b test: evaluation schedule 2025-12-19 12:14:10 +05:30
Jannat Patel 58b26b6e32 fix: increased schedule range to 60 days 2025-12-19 10:42:45 +05:30
Jannat Patel 2631681c1d fix: enrollment in restricted courses by admin 2025-12-18 18:01:08 +05:30
Jannat Patel 82d6284b06 Merge pull request #1922 from pateljannat/issues-162
fix: enrollment in restricted courses by admin
2025-12-18 17:56:44 +05:30
Jannat Patel 46d13d65c1 fix: enrollment in restricted courses by admin 2025-12-18 14:59:37 +05:30
Jannat Patel 9da6cff8a5 feat: see all upcoming slots when scheduling evaluation 2025-12-18 14:40:41 +05:30
Jannat Patel dc724831c3 fix: read permission for certificates 2025-12-17 16:50:51 +05:30
Jannat Patel 776730447a fix: removed vue jsx 2025-12-17 15:30:16 +05:30
Jannat Patel 82c6fdf475 fix: removed vue jsx 2025-12-17 14:58:38 +05:30
Jannat Patel ca2b175e1c fix: assignment permission to course creator and evaluator 2025-12-17 14:47:47 +05:30
Jannat Patel ec7e250f96 Merge pull request #1919 from pateljannat/open-to-work
feat: open to opportunities
2025-12-17 11:46:35 +05:30
Jannat Patel 126570fcca fix: save button of profile edit modal 2025-12-17 11:23:10 +05:30
Jannat Patel a7bbb7f150 Merge pull request #1921 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-17 11:01:30 +05:30
Frappe PR Bot 4345ff18bd chore(release): Bumped to Version 2.43.0 2025-12-17 05:20:08 +00:00
Jannat Patel 26409b0336 Merge pull request #1907 from KerollesFathy/fix-category-on-certified-members
fix: Category dropdown options not visible in the certification list page
2025-12-17 10:49:11 +05:30
Jannat Patel 7653b64353 Merge pull request #1920 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-17 10:45:01 +05:30
Jannat Patel 32dd5832b8 chore: Swedish translations 2025-12-17 02:53:41 +05:30
Jannat Patel 59072c3580 chore: Slovenian translations 2025-12-17 02:53:39 +05:30
Jannat Patel d121cd3526 chore: Hungarian translations 2025-12-17 02:53:28 +05:30
KerollesFathy cfbac5f2c4 fix: use update:modelvalue instead of using watch 2025-12-16 21:05:04 +00:00
Jannat Patel 371c72b96c Merge branch 'develop' of https://github.com/frappe/lms into open-to-work 2025-12-16 20:37:38 +05:30
Jannat Patel 55a02004bd fix: updated course and batch category filters 2025-12-16 20:36:53 +05:30
Jannat Patel b3119f5295 feat: open to opportunities 2025-12-16 20:27:00 +05:30
Jannat Patel f57b64531c fix: misc home page issues 2025-12-16 14:33:35 +05:30
Jannat Patel 94a3afdc9b Merge pull request #1918 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-16 11:09:00 +05:30
Jannat Patel b65102e6c8 fix: course card gradient color 2025-12-16 11:06:03 +05:30
Jannat Patel 0f1b48b3e3 chore: Esperanto translations 2025-12-16 02:50:54 +05:30
Jannat Patel 0a62da02fe chore: Serbian (Latin) translations 2025-12-16 02:50:52 +05:30
Jannat Patel ae54e39b69 chore: Norwegian Bokmal translations 2025-12-16 02:50:50 +05:30
Jannat Patel ea565d7334 chore: Bosnian translations 2025-12-16 02:50:49 +05:30
Jannat Patel d11f625ecb chore: Burmese translations 2025-12-16 02:50:48 +05:30
Jannat Patel ab6761a6ce chore: Croatian translations 2025-12-16 02:50:46 +05:30
Jannat Patel 081bc0eaa6 chore: Thai translations 2025-12-16 02:50:45 +05:30
Jannat Patel 30748b7287 chore: Tamil translations 2025-12-16 02:50:43 +05:30
Jannat Patel ad928eb2a6 chore: Persian translations 2025-12-16 02:50:42 +05:30
Jannat Patel bdb281dfa3 chore: Indonesian translations 2025-12-16 02:50:41 +05:30
Jannat Patel 8d26800a5d chore: Portuguese, Brazilian translations 2025-12-16 02:50:39 +05:30
Jannat Patel f0d85b391c chore: Vietnamese translations 2025-12-16 02:50:37 +05:30
Jannat Patel ac205b6944 chore: Chinese Simplified translations 2025-12-16 02:50:36 +05:30
Jannat Patel c6979a2f61 chore: Turkish translations 2025-12-16 02:50:35 +05:30
Jannat Patel 0bbf4a9925 chore: Swedish translations 2025-12-16 02:50:33 +05:30
Jannat Patel 16b047bb73 chore: Serbian (Cyrillic) translations 2025-12-16 02:50:32 +05:30
Jannat Patel f674fdcc0d chore: Slovenian translations 2025-12-16 02:50:30 +05:30
Jannat Patel 05b40ae47e chore: Russian translations 2025-12-16 02:50:29 +05:30
Jannat Patel ce5413f622 chore: Portuguese translations 2025-12-16 02:50:28 +05:30
Jannat Patel fbf4971f52 chore: Polish translations 2025-12-16 02:50:26 +05:30
Jannat Patel a2e2de2ec3 chore: Dutch translations 2025-12-16 02:50:25 +05:30
Jannat Patel 9937851146 chore: Italian translations 2025-12-16 02:50:23 +05:30
Jannat Patel a0bdb84d3f chore: German translations 2025-12-16 02:50:22 +05:30
Jannat Patel 21fca61fab chore: Danish translations 2025-12-16 02:50:21 +05:30
Jannat Patel 1157e6a007 chore: Czech translations 2025-12-16 02:50:19 +05:30
Jannat Patel 6d9db8ef46 chore: Arabic translations 2025-12-16 02:50:18 +05:30
Jannat Patel 8c23510622 chore: Spanish translations 2025-12-16 02:50:16 +05:30
Jannat Patel 660945b8fa chore: French translations 2025-12-16 02:50:15 +05:30
Jannat Patel 6550e1c926 chore: Hungarian translations 2025-12-16 02:50:13 +05:30
KerollesFathy 49bc5750a1 fix: prevent duplicate certification categories 2025-12-15 18:30:01 +00:00
Jannat Patel 163e4b8b1e fix: course card gradient color 2025-12-15 16:32:42 +05:30
Jannat Patel 52fb5e2ad8 Merge pull request #1915 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-15 16:02:38 +05:30
Jannat Patel 2596b85eb2 Merge pull request #1913 from rehanrehman389/cleanup-cohort
chore: Remove deprecated doctype and related links
2025-12-15 15:44:24 +05:30
Jannat Patel 1210a6aa87 chore: beautifulsoup dependency management 2025-12-15 15:21:44 +05:30
Jannat Patel d3a27e8bc9 fix: enrollment eligibility validation 2025-12-15 15:21:22 +05:30
Jannat Patel 6cabb4eed7 chore: resolved conflicts 2025-12-15 15:18:08 +05:30
Jannat Patel 38e6320d8f Merge pull request #1890 from pateljannat/tests-1
test: utils
2025-12-15 15:05:39 +05:30
Jannat Patel 8d41a3d688 test: certificate and rating test 2025-12-15 14:51:37 +05:30
Jannat Patel 7bf6311a90 Merge branch 'develop' of https://github.com/frappe/lms into tests-1 2025-12-15 12:13:19 +05:30
Jannat Patel 41660a1dfe Merge pull request #1895 from pateljannat/full-text-search
feat: Full Text Search
2025-12-15 12:07:04 +05:30
Jannat Patel 46b467847e Merge pull request #1908 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-15 11:22:02 +05:30
Jannat Patel 810635d964 Merge pull request #1906 from frappe/pot_develop_2025-12-12
chore: update POT file
2025-12-15 11:21:48 +05:30
Rehan Ansari b0a96641ef chore: Remove deprecated doctype and related links 2025-12-14 21:16:02 +05:30
royalpinto007 f2846da4ad fix(quizzes): enable enter key to submit quiz creation
Signed-off-by: royalpinto007 <royalpinto007@gmail.com>
2025-12-14 15:28:29 +05:30
Jannat Patel e150225226 chore: Hungarian translations 2025-12-14 02:05:58 +05:30
KerollesFathy 4580ab0181 fix: add watcher for currentCategory to update participants 2025-12-13 15:18:37 +00:00
KerollesFathy f8c10d1807 fix: update certification categories to include label and value 2025-12-13 14:22:08 +00:00
frappe-pr-bot f783c6a62f chore: update POT file 2025-12-12 16:04:35 +00:00
Jannat Patel 1bc610bd76 feat: search jobs from command palette 2025-12-12 19:14:25 +05:30
Jannat Patel f49bb98b92 fix: sidebar improvements 2025-12-12 16:25:45 +05:30
Jannat Patel 819318de37 feat: broke down sidebar into categories 2025-12-12 12:30:16 +05:30
Jannat Patel 3ebff2143a Merge pull request #1903 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-11 17:28:55 +05:30
Frappe PR Bot 80deed2be7 chore(release): Bumped to Version 2.42.0 2025-12-11 11:48:42 +00:00
Jannat Patel 8de3996d36 Merge pull request #1866 from muhamiyan/issues-1
fix: open dialog directly to create quiz and assignment
2025-12-11 17:15:47 +05:30
Jannat Patel b56fd01f39 Merge pull request #1899 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-11 16:36:05 +05:30
Jannat Patel 820ea7e2a4 feat: search page functionality 2025-12-11 14:56:28 +05:30
Jannat Patel 3de5fb0622 Merge branch 'develop' of https://github.com/frappe/lms into full-text-search 2025-12-11 11:45:36 +05:30
Jannat Patel a5f112ff16 fix: remove course filters from certificate evaluation form 2025-12-11 11:33:29 +05:30
Jannat Patel 32b3fceb3b Merge pull request #1900 from pateljannat/issues-161
fix: exclude cancelled slots when fetching available slots
2025-12-11 11:27:58 +05:30
Jannat Patel 1d7c88674d fix: exclude cancelled slots when fetching available slots 2025-12-11 11:25:59 +05:30
Jannat Patel ce158692d4 Merge pull request #1898 from pateljannat/issues-160
fix: misc issues
2025-12-11 10:39:02 +05:30
Jannat Patel 9845498f76 chore: Croatian translations 2025-12-11 00:59:38 +05:30
Jannat Patel 7b8250056b chore: Serbian (Cyrillic) translations 2025-12-11 00:59:37 +05:30
Jannat Patel 80a217e646 feat: configuration to disable pwa 2025-12-10 18:05:50 +05:30
Jannat Patel 0877e32e1b fix: sanitize html of all description fields 2025-12-10 17:25:56 +05:30
Jannat Patel 316e739dd6 Merge pull request #1897 from pateljannat/issues-159
fix: misc issues
2025-12-10 16:41:22 +05:30
Jannat Patel 43efebe3a7 fix: removed filter of an old field from LMS Certificate Evaluation 2025-12-10 16:14:27 +05:30
Jannat Patel ca849da815 fix: validate url starts with http or https for job form 2025-12-10 14:51:16 +05:30
Jannat Patel 9470cc192c fix: batch validations 2025-12-10 14:25:10 +05:30
Jannat Patel 631008832c fix: no need to check if not in test before sending certificate email 2025-12-10 14:11:08 +05:30
Jannat Patel 7fc066679d fix: validate url starts with http or https for job form 2025-12-10 14:06:17 +05:30
Jannat Patel 73ee1b2f09 Merge pull request #1893 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-10 11:19:02 +05:30
Jannat Patel 4c98282335 feat: search page 2025-12-10 11:17:49 +05:30
Jannat Patel 7e3c5beaea fix: filter search records based on roles 2025-12-10 11:17:21 +05:30
Jannat Patel 59d27e06a0 chore: Serbian (Latin) translations 2025-12-10 00:25:38 +05:30
Jannat Patel 2caddafd0b chore: Russian translations 2025-12-10 00:25:22 +05:30
Jannat Patel cbfc10c08b chore: Hungarian translations 2025-12-10 00:25:17 +05:30
muhamiyan ae8ffd4cbd fix: linter issues 2025-12-09 19:41:12 +07:00
muhamiyan e768d5d55c fix: reset QuizForm.vue 2025-12-09 19:40:51 +07:00
Jannat Patel 829245f373 Merge pull request #1891 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-09 11:29:29 +05:30
Jannat Patel c901a15969 test: utils 2025-12-09 11:24:35 +05:30
muhamiyan 017798ce89 fix: url with query string 2025-12-09 12:07:58 +07:00
muhamiyan d16874da7c fix: open dialog directly to create assignment 2025-12-09 12:01:10 +07:00
muhamiyan d8c9204b61 fix: open dialog directly to create quiz 2025-12-09 11:19:51 +07:00
muhamiyan 1ab51b423f Revert "fix: remove redirect to create new quiz and new assignment"
This reverts commit fc82ec8070.
2025-12-09 09:54:16 +07:00
Jannat Patel ce110e8758 chore: Esperanto translations 2025-12-09 00:28:23 +05:30
Jannat Patel e257a3f757 chore: French translations 2025-12-09 00:28:21 +05:30
Jannat Patel 49a6305dec chore: Serbian (Latin) translations 2025-12-09 00:28:20 +05:30
Jannat Patel 1b30df0c41 chore: Norwegian Bokmal translations 2025-12-09 00:28:19 +05:30
Jannat Patel edb2369967 chore: Bosnian translations 2025-12-09 00:28:17 +05:30
Jannat Patel 4f93908ab1 chore: Burmese translations 2025-12-09 00:28:16 +05:30
Jannat Patel dc95c63c62 chore: Croatian translations 2025-12-09 00:28:14 +05:30
Jannat Patel 28a0cb5b12 chore: Thai translations 2025-12-09 00:28:13 +05:30
Jannat Patel a4cbcc2f32 chore: Tamil translations 2025-12-09 00:28:12 +05:30
Jannat Patel 0add20e637 chore: Persian translations 2025-12-09 00:28:10 +05:30
Jannat Patel 50a8550a0c chore: Indonesian translations 2025-12-09 00:28:09 +05:30
Jannat Patel dbd6ac10e9 chore: Portuguese, Brazilian translations 2025-12-09 00:28:07 +05:30
Jannat Patel 1252234eed chore: Vietnamese translations 2025-12-09 00:28:06 +05:30
Jannat Patel 5ee7a6efb6 chore: Chinese Simplified translations 2025-12-09 00:28:04 +05:30
Jannat Patel a4bfe3752b chore: Turkish translations 2025-12-09 00:28:03 +05:30
Jannat Patel dd034ec7ae chore: Swedish translations 2025-12-09 00:28:02 +05:30
Jannat Patel c93b189ede chore: Serbian (Cyrillic) translations 2025-12-09 00:28:00 +05:30
Jannat Patel 2819531d57 chore: Slovenian translations 2025-12-09 00:27:59 +05:30
Jannat Patel dc1ce8e55e chore: Russian translations 2025-12-09 00:27:57 +05:30
Jannat Patel 2e0266a265 chore: Portuguese translations 2025-12-09 00:27:56 +05:30
Jannat Patel bcd7aca2ff chore: Polish translations 2025-12-09 00:27:55 +05:30
Jannat Patel 8c66558b63 chore: Dutch translations 2025-12-09 00:27:53 +05:30
Jannat Patel a263bcd8c3 chore: Italian translations 2025-12-09 00:27:52 +05:30
Jannat Patel 97a873c6b0 chore: Hungarian translations 2025-12-09 00:27:50 +05:30
Jannat Patel fc43259dcb chore: German translations 2025-12-09 00:27:49 +05:30
Jannat Patel 73116446e2 chore: Danish translations 2025-12-09 00:27:48 +05:30
Jannat Patel f0d3439071 chore: Czech translations 2025-12-09 00:27:46 +05:30
Jannat Patel 31a1aa7cac chore: Arabic translations 2025-12-09 00:27:45 +05:30
Jannat Patel 34cd751114 chore: Spanish translations 2025-12-09 00:27:43 +05:30
Jannat Patel 196d4a835f test: utils 2025-12-08 21:10:13 +05:30
Jannat Patel d82517f402 test: utils 2025-12-08 20:22:55 +05:30
Jannat Patel bcda74a455 Merge pull request #1889 from pateljannat/code-coverage
chore: configured codecov for CI
2025-12-08 15:59:53 +05:30
Jannat Patel 1f65439efd chore: upload coverage data codecov 2025-12-08 15:52:41 +05:30
Jannat Patel f95b62a6a6 chore: configured codecov for CI 2025-12-08 15:23:18 +05:30
Jannat Patel 0dc0794add Merge pull request #1888 from pateljannat/version-16-changes
chore: removed cohort related doctypes
2025-12-08 14:54:31 +05:30
Jannat Patel 552b5845ea chore: fixed merge conflicts 2025-12-08 14:54:14 +05:30
Jannat Patel b05739257d chore: removed cohort related doctypes 2025-12-08 14:47:02 +05:30
Jannat Patel 61215cf0ad Merge pull request #1887 from pateljannat/issues-158
fix: charts and dashboards as per version 16
2025-12-08 13:43:16 +05:30
Jannat Patel bc84e46e09 fix: charts and dashboards as per version 16 2025-12-08 13:23:42 +05:30
Jannat Patel 645581e202 Merge pull request #1886 from pateljannat/issues-157
fix: misc issues
2025-12-08 12:46:51 +05:30
Jannat Patel 4dddb9f2e1 fix: exclude frappe-ui from optimiseDeps only on local 2025-12-08 12:37:04 +05:30
Jannat Patel 6e93f952ab refactor: markdown editor 2025-12-08 12:33:38 +05:30
Jannat Patel 93ffcdb8f9 Merge branch 'develop' of https://github.com/frappe/lms into issues-157 2025-12-08 10:37:45 +05:30
Jannat Patel 040d74c20a fix: use frappe-ui theme colors and fixed tailwind config 2025-12-08 10:37:31 +05:30
Jannat Patel 5825bcf9b3 fix: use frappe-ui theme colors and fixed tailwind config 2025-12-08 10:37:08 +05:30
Jannat Patel 114d183524 Merge pull request #1884 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-08 10:35:55 +05:30
Jannat Patel de356c4e64 Merge pull request #1883 from frappe/pot_develop_2025-12-05
chore: update POT file
2025-12-08 10:35:42 +05:30
Jannat Patel fedd5e6a14 chore: Hungarian translations 2025-12-05 22:58:03 +05:30
frappe-pr-bot 29217ab2bb chore: update POT file 2025-12-05 16:04:48 +00:00
Jannat Patel 95f37f7120 Merge pull request #1882 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-05 12:16:51 +05:30
Frappe PR Bot 87a7b93334 chore(release): Bumped to Version 2.41.0 2025-12-05 06:34:16 +00:00
Jannat Patel 0d430ad86c Merge pull request #1881 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-05 12:02:53 +05:30
Jannat Patel 308a108e60 Merge pull request #1880 from pateljannat/issues-155
fix: misc permission issues
2025-12-05 12:02:32 +05:30
Jannat Patel 17401a330d fix: settings issue 2025-12-05 11:34:49 +05:30
Jannat Patel f9d7463710 test: removed tests for old batch and exercises 2025-12-05 11:03:38 +05:30
Jannat Patel 097c53c7b1 chore: Bosnian translations 2025-12-04 22:37:08 +05:30
Jannat Patel 9ae2e5babb chore: Croatian translations 2025-12-04 22:37:05 +05:30
Jannat Patel 1f29f7a282 chore: Slovenian translations 2025-12-04 22:36:52 +05:30
Jannat Patel 1d1fcd5f6d chore: Russian translations 2025-12-04 22:36:51 +05:30
Jannat Patel b6f8f87923 chore: Hungarian translations 2025-12-04 22:36:45 +05:30
Jannat Patel 466b248c30 fix: validate course details before enrolling 2025-12-04 17:48:39 +05:30
Jannat Patel 7c6747aeb0 test: enroll user in course before creating certificate 2025-12-04 17:35:08 +05:30
Jannat Patel 836a6d1203 fix: misc permission issues 2025-12-04 17:15:14 +05:30
Jannat Patel a3bd9d2706 Merge pull request #1879 from pateljannat/issues-156
fix: use dayjs from frappe-ui in evaluation modal
2025-12-04 11:24:49 +05:30
Jannat Patel 2cdd45da96 fix: use dayjs from frappe-ui in evaluation modal 2025-12-04 11:06:34 +05:30
Jannat Patel 0367c1db72 fix: validate course and batch before a reply is added 2025-12-04 09:25:07 +05:30
Jannat Patel 5f17802ab8 fix: only moderators should be allowed to delete sidebar pages 2025-12-04 09:24:27 +05:30
Jannat Patel b3e90c7f2f fix: validate before enrolling in batch 2025-12-03 15:44:19 +05:30
Jannat Patel 6c2978306c Merge branch 'develop' of https://github.com/frappe/lms into develop 2025-12-03 14:18:20 +05:30
Jannat Patel b951e1567c chore: upgraded dependencies 2025-12-03 14:17:49 +05:30
Jannat Patel 97cdb57406 Merge pull request #1878 from pateljannat/issues-154
fix: increased the rate limit
2025-12-03 10:56:05 +05:30
Jannat Patel 651300b043 Merge pull request #1877 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-03 10:48:37 +05:30
Jannat Patel b537e2789d fix: increased the rate limit 2025-12-03 10:43:19 +05:30
Jannat Patel 3cd766bc74 Merge pull request #1876 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-03 10:38:46 +05:30
Jannat Patel 30ee06c0ab Merge pull request #1874 from pateljannat/closed-job-applications
feat: Closed job applications
2025-12-03 10:38:34 +05:30
Jannat Patel 74a1f1dc77 chore: Bosnian translations 2025-12-02 21:42:12 +05:30
Jannat Patel 7a6f6d2c7c chore: Croatian translations 2025-12-02 21:42:11 +05:30
Jannat Patel de8868ec68 chore: Persian translations 2025-12-02 21:42:09 +05:30
Jannat Patel 7105d6271f fix: allow moderators to edit all jobs 2025-12-02 18:34:46 +05:30
Jannat Patel b04a3de201 feat: closed jobs 2025-12-02 17:59:04 +05:30
Jannat Patel 0107032ee3 Merge pull request #1873 from pateljannat/issues-153
fix: misc issues
2025-12-02 16:41:05 +05:30
Jannat Patel 34e07b0083 fix: fetch correct application count 2025-12-02 16:31:54 +05:30
Jannat Patel 6c16516e89 fix: evaluators can only edit batches where they are instructors 2025-12-02 16:14:17 +05:30
Jannat Patel 7bc6dff6ea fix: misc issues 2025-12-02 15:41:42 +05:30
Jannat Patel 1c0be8a2ec Merge pull request #1870 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-02 11:37:50 +05:30
Jannat Patel 4a9f1197fb Merge pull request #1861 from pateljannat/data-import-fixes
fix: data import issues
2025-12-02 11:34:45 +05:30
Jannat Patel ad4e7496cb chore: Slovenian translations 2025-12-01 21:28:42 +05:30
Jannat Patel d321b81a64 chore: Esperanto translations 2025-12-01 21:28:40 +05:30
Jannat Patel 3691e3f240 chore: Serbian (Latin) translations 2025-12-01 21:28:38 +05:30
Jannat Patel 62e2fe56d0 chore: Norwegian Bokmal translations 2025-12-01 21:28:37 +05:30
Jannat Patel 4a6a98c533 chore: Bosnian translations 2025-12-01 21:28:35 +05:30
Jannat Patel 4e5d44c464 chore: Burmese translations 2025-12-01 21:28:34 +05:30
Jannat Patel b2e8fbd84d chore: Croatian translations 2025-12-01 21:28:32 +05:30
Jannat Patel 41dde09a6a chore: Thai translations 2025-12-01 21:28:30 +05:30
Jannat Patel 7bbfb85b0e chore: Tamil translations 2025-12-01 21:28:29 +05:30
Jannat Patel c99d05cf6c chore: Persian translations 2025-12-01 21:28:27 +05:30
Jannat Patel f856aaaacd chore: Indonesian translations 2025-12-01 21:28:26 +05:30
Jannat Patel d029d4e371 chore: Portuguese, Brazilian translations 2025-12-01 21:28:24 +05:30
Jannat Patel 04f3744624 chore: Vietnamese translations 2025-12-01 21:28:23 +05:30
Jannat Patel 4e1d00afff chore: Chinese Simplified translations 2025-12-01 21:28:21 +05:30
Jannat Patel ccfa281490 chore: Turkish translations 2025-12-01 21:28:19 +05:30
Jannat Patel 8979b5ef0c chore: Swedish translations 2025-12-01 21:28:17 +05:30
Jannat Patel 765bd8bfab chore: Serbian (Cyrillic) translations 2025-12-01 21:28:16 +05:30
Jannat Patel 4e7f371c49 chore: Russian translations 2025-12-01 21:28:14 +05:30
Jannat Patel 5ca5624f99 chore: Portuguese translations 2025-12-01 21:28:13 +05:30
Jannat Patel 6e5066022b chore: Polish translations 2025-12-01 21:28:11 +05:30
Jannat Patel b16d8cbd6d chore: Dutch translations 2025-12-01 21:28:10 +05:30
Jannat Patel 030b3095a9 chore: Italian translations 2025-12-01 21:28:08 +05:30
Jannat Patel d9febdaf82 chore: Hungarian translations 2025-12-01 21:28:06 +05:30
Jannat Patel 3143fc8b2a chore: German translations 2025-12-01 21:28:05 +05:30
Jannat Patel d8396d13b4 chore: Danish translations 2025-12-01 21:28:03 +05:30
Jannat Patel 6a474b25ef chore: Czech translations 2025-12-01 21:28:02 +05:30
Jannat Patel c8494f3246 chore: Arabic translations 2025-12-01 21:28:00 +05:30
Jannat Patel 1d66f9695a chore: Spanish translations 2025-12-01 21:27:59 +05:30
Jannat Patel 053a9cd3a9 chore: French translations 2025-12-01 21:27:57 +05:30
Jannat Patel b7546fd2f4 chore: fixed linters 2025-12-01 17:54:47 +05:30
Jannat Patel 61fc0a9ce7 chore: upgraded frappe-ui 2025-12-01 17:54:15 +05:30
Jannat Patel 251abad821 Merge pull request #1865 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-01 12:12:02 +05:30
Jannat Patel f581e7894d Merge pull request #1867 from frappe/pot_develop_2025-11-28
chore: update POT file
2025-12-01 12:11:49 +05:30
Jannat Patel c16b81bfa2 chore: Slovenian translations 2025-11-30 20:15:46 +05:30
Jannat Patel 4fcb19010c chore: Persian translations 2025-11-30 20:15:39 +05:30
Jannat Patel 7f1e2a18ea chore: Slovenian translations 2025-11-29 20:09:06 +05:30
Jannat Patel 7f4bb9e05d chore: Persian translations 2025-11-29 20:08:59 +05:30
frappe-pr-bot 58d9dd3c26 chore: update POT file 2025-11-28 16:04:30 +00:00
Jannat Patel 609ed3cb09 chore: Slovenian translations 2025-11-28 20:12:18 +05:30
Jannat Patel 67dd8ac151 Merge pull request #1864 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-28 16:19:37 +05:30
muhamiyan fc82ec8070 fix: remove redirect to create new quiz and new assignment 2025-11-28 17:21:29 +07:00
Jannat Patel f218230ad7 chore: Slovenian translations 2025-11-27 19:55:31 +05:30
Jannat Patel f8380226ee chore: Croatian translations 2025-11-27 19:55:29 +05:30
Jannat Patel b087faeb90 fix: batch instructors selection issue 2025-11-27 16:19:17 +05:30
Jannat Patel a333b0b754 chore: fixed conflicts 2025-11-27 16:15:58 +05:30
Jannat Patel 2614fbc94c chore: upgraded frappe-ui to latest version 2025-11-27 14:34:08 +05:30
Jannat Patel 66c70dd233 Merge pull request #1860 from pateljannat/issues-152
fix: round the amount after exchange rate calculation
2025-11-27 12:45:03 +05:30
Jannat Patel 8d1c0a7bd1 Merge pull request #1862 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-27 12:42:18 +05:30
Jannat Patel eae74dacae fix: redirect to courses if user is not moderator on data import 2025-11-27 12:41:39 +05:30
Jannat Patel ca0fed9f17 chore: Slovenian translations 2025-11-26 19:26:44 +05:30
Jannat Patel cfc5d94711 chore: upgraded frappe-ui to latest version 2025-11-26 16:06:03 +05:30
Jannat Patel 924a11e4f4 chore: frappe-ui tree shaking changes 2025-11-26 15:06:21 +05:30
Jannat Patel 3be3124951 fix: profile update form 2025-11-26 15:05:55 +05:30
Jannat Patel c846e36032 fix: data import issues 2025-11-26 14:14:09 +05:30
Jannat Patel cf9e5f861b Merge pull request #1858 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-26 11:32:53 +05:30
Jannat Patel 219139e45b fix: round the amount after exchange rate calculation 2025-11-26 11:21:18 +05:30
Jannat Patel eab43a66cf feat: improved search results in command palette 2025-11-25 19:44:31 +05:30
Jannat Patel faa18d6a88 chore: Slovenian translations 2025-11-25 19:10:19 +05:30
Jannat Patel 470e446ae6 chore: Persian translations 2025-11-25 19:10:08 +05:30
Jannat Patel af3f1a5fc3 chore: Turkish translations 2025-11-25 19:10:02 +05:30
Jannat Patel 3b925246ee chore: German translations 2025-11-25 19:09:51 +05:30
Jannat Patel e2d7b409bd chore: Arabic translations 2025-11-25 19:09:47 +05:30
Jannat Patel 731e242974 chore: Spanish translations 2025-11-25 19:09:46 +05:30
Jannat Patel 9614f8eb9d chore: French translations 2025-11-25 19:09:44 +05:30
Jannat Patel a98e8025c4 Merge pull request #1854 from rehanrehman389/fix-zoom
fix: data persistence issue
2025-11-25 10:42:10 +05:30
Jannat Patel 397b7ee032 Merge pull request #1807 from rehanrehman389/program-fix
Misc Programs Issues
2025-11-25 10:40:59 +05:30
Jannat Patel 22b041d252 Merge pull request #1852 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-25 10:40:30 +05:30
Jannat Patel 5f20d8ad63 chore: Slovenian translations 2025-11-24 19:11:54 +05:30
Jannat Patel d3879a9d11 Merge pull request #1853 from frappe/pot_develop_2025-11-21
chore: update POT file
2025-11-24 10:01:27 +05:30
Jannat Patel 0d0b6dcd36 Merge pull request #1785 from pateljannat/data-import
feat: data import
2025-11-24 10:01:10 +05:30
Jannat Patel 588f796069 chore: Slovenian translations 2025-11-23 18:37:06 +05:30
Jannat Patel 440c51f1ca chore: Swedish translations 2025-11-23 18:36:52 +05:30
Rehan Ansari b99b2bd123 refactor: simplify zoom account modal form handling 2025-11-22 23:29:35 +05:30
Jannat Patel e502ff3491 chore: Slovenian translations 2025-11-22 18:07:54 +05:30
Jannat Patel 1ae0f87f9c chore: Serbian (Latin) translations 2025-11-22 18:07:53 +05:30
Jannat Patel 5a3e72eaaf chore: Croatian translations 2025-11-22 18:07:52 +05:30
Jannat Patel 41c8384ef5 chore: Serbian (Cyrillic) translations 2025-11-22 18:07:50 +05:30
frappe-pr-bot 12a5641cb4 chore: update POT file 2025-11-21 16:05:05 +00:00
Jannat Patel e8cd305171 chore: upgraded frappe-ui to latest version 2025-11-21 20:22:32 +05:30
Jannat Patel 880b799df5 chore: upgraded frappe-ui to latest data import commit 2025-11-21 20:00:20 +05:30
Jannat Patel 4c6dc25589 chore: Slovenian translations 2025-11-21 18:01:52 +05:30
Jannat Patel 10a301eebc chore: Persian translations 2025-11-21 18:01:44 +05:30
Jannat Patel e6e50c96e4 chore: Portuguese, Brazilian translations 2025-11-21 18:01:41 +05:30
Jannat Patel fab2ee8420 chore: Polish translations 2025-11-21 18:01:33 +05:30
Jannat Patel 58eb3ccacb chore: Spanish translations 2025-11-21 18:01:25 +05:30
Jannat Patel aacd5ab7a1 chore: upgraded frappe-ui to latest data import commit 2025-11-21 17:40:18 +05:30
Jannat Patel 38b0b9ceb1 Merge branch 'develop' of https://github.com/frappe/lms into data-import 2025-11-21 17:13:27 +05:30
Jannat Patel 9fb2a169e8 chore: downgraded vite-pwa-plugin 2025-11-21 16:46:31 +05:30
Jannat Patel 5977be4a14 chore: upgraded frappe-ui to latest data import commit 2025-11-21 15:53:04 +05:30
Jannat Patel 5b5c53bebc chore: added runtime caching for PWA 2025-11-21 15:02:42 +05:30
Jannat Patel 9b38e62eaf chore: upgraded node version in CI 2025-11-21 14:49:39 +05:30
Jannat Patel 7a490b19bd chore: downgraded vite-pwa-plugin 2025-11-21 14:29:56 +05:30
Jannat Patel 4d34e9e702 Merge pull request #1773 from HUMENTH/patch-2
fix: beautifulsoap4 version upgrade to match with Frappe Framework
2025-11-21 11:32:28 +05:30
Jannat Patel 6fbd504e39 Merge branch 'develop' into patch-2 2025-11-21 10:48:55 +05:30
Jannat Patel b0b79f1d19 test: click on dropdown option to open course or batch form 2025-11-20 18:30:52 +05:30
Jannat Patel 3d7a3ecfc5 refactor: new data import flow 2025-11-20 18:02:02 +05:30
Jannat Patel 9b7d763d52 refactor: new data import flow 2025-11-20 18:01:36 +05:30
Jannat Patel aae8624269 Merge pull request #1851 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-19 21:44:59 +05:30
Jannat Patel 7fd16915c0 chore: Persian translations 2025-11-19 18:02:16 +05:30
Jannat Patel eacb9fe356 Merge pull request #1846 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-18 19:43:28 +05:30
Jannat Patel a7305b679e Merge branch 'develop' into l10n_develop2 2025-11-18 19:09:55 +05:30
Jannat Patel d48405d440 chore: Esperanto translations 2025-11-18 18:04:28 +05:30
Jannat Patel 35157c0b58 chore: Serbian (Latin) translations 2025-11-18 18:04:27 +05:30
Jannat Patel ee7aacd776 chore: Norwegian Bokmal translations 2025-11-18 18:04:25 +05:30
Jannat Patel 2c08b94c3a chore: Bosnian translations 2025-11-18 18:04:23 +05:30
Jannat Patel d34cfbc327 chore: Burmese translations 2025-11-18 18:04:22 +05:30
Jannat Patel d9933e6933 chore: Croatian translations 2025-11-18 18:04:20 +05:30
Jannat Patel 7dbb215a18 chore: Thai translations 2025-11-18 18:04:19 +05:30
Jannat Patel 132f8be21c chore: Tamil translations 2025-11-18 18:04:17 +05:30
Jannat Patel 5fac2198cd chore: Persian translations 2025-11-18 18:04:15 +05:30
Jannat Patel f94b4d1205 chore: Indonesian translations 2025-11-18 18:04:14 +05:30
Jannat Patel fc75f92e89 chore: Portuguese, Brazilian translations 2025-11-18 18:04:13 +05:30
Jannat Patel 998049872d chore: Vietnamese translations 2025-11-18 18:04:11 +05:30
Jannat Patel f9ed0bab5e chore: Chinese Simplified translations 2025-11-18 18:04:10 +05:30
Jannat Patel b87497411b chore: Turkish translations 2025-11-18 18:04:08 +05:30
Jannat Patel b6be206630 chore: Swedish translations 2025-11-18 18:04:07 +05:30
Jannat Patel f7a4350fe8 chore: Serbian (Cyrillic) translations 2025-11-18 18:04:05 +05:30
Jannat Patel 802a104a49 chore: Russian translations 2025-11-18 18:04:03 +05:30
Jannat Patel 35aea2dd77 chore: Portuguese translations 2025-11-18 18:04:02 +05:30
Jannat Patel 6ef7dd75e9 chore: Polish translations 2025-11-18 18:04:00 +05:30
Jannat Patel 115ccdb26a chore: Dutch translations 2025-11-18 18:03:59 +05:30
Jannat Patel 6f888bcf4a chore: Italian translations 2025-11-18 18:03:58 +05:30
Jannat Patel d55de747f5 chore: Hungarian translations 2025-11-18 18:03:56 +05:30
Jannat Patel ba45c57cc6 chore: German translations 2025-11-18 18:03:54 +05:30
Jannat Patel 8632f81237 chore: Danish translations 2025-11-18 18:03:53 +05:30
Jannat Patel f7c2ec7fa6 chore: Czech translations 2025-11-18 18:03:51 +05:30
Jannat Patel 6617c1ef54 chore: Arabic translations 2025-11-18 18:03:49 +05:30
Jannat Patel 229c537731 chore: Spanish translations 2025-11-18 18:03:48 +05:30
Jannat Patel d0060d828f chore: French translations 2025-11-18 18:03:46 +05:30
Jannat Patel 98f9778464 Merge pull request #1845 from frappe/pot_develop_2025-11-14
chore: update POT file
2025-11-17 20:01:25 +05:30
Jannat Patel 1f6a0194f7 chore: resolved conflicts 2025-11-17 10:12:26 +05:30
Jannat Patel c7915e2c3d feat: launch command palette 2025-11-17 10:10:26 +05:30
Jannat Patel 41f7979eb4 chore: Norwegian Bokmal translations 2025-11-16 17:56:52 +05:30
Jannat Patel 343ed8d22f chore: Bosnian translations 2025-11-16 17:56:51 +05:30
Jannat Patel 1c038d3334 chore: Persian translations 2025-11-16 17:56:46 +05:30
Jannat Patel 1c3cdec563 chore: Indonesian translations 2025-11-16 17:56:44 +05:30
Jannat Patel 6c37f0f4fe chore: Turkish translations 2025-11-16 17:56:40 +05:30
Jannat Patel f9967bff2e chore: Swedish translations 2025-11-16 17:56:39 +05:30
Jannat Patel 96a9e34487 chore: Italian translations 2025-11-16 17:56:34 +05:30
Jannat Patel 2eb1574131 chore: German translations 2025-11-16 17:56:32 +05:30
Jannat Patel 583871912b chore: Danish translations 2025-11-16 17:56:30 +05:30
Jannat Patel f4483d7973 chore: Arabic translations 2025-11-16 17:56:28 +05:30
Jannat Patel 21f49690bd chore: Spanish translations 2025-11-16 17:56:27 +05:30
Jannat Patel 18ebac2130 chore: French translations 2025-11-16 17:56:25 +05:30
frappe-pr-bot 1deee7e396 chore: update POT file 2025-11-14 16:05:00 +00:00
Jannat Patel 0a72f0a9a9 Merge pull request #1844 from pateljannat/issues-150
fix: misc issues
2025-11-14 18:14:00 +05:30
Jannat Patel 1932338660 test: open students tab before adding to batch 2025-11-14 18:06:23 +05:30
Jannat Patel a481bcd974 fix: test case table ux 2025-11-14 17:46:31 +05:30
Jannat Patel d86fd0f6f6 fix: misc issues 2025-11-14 12:48:46 +05:30
Jannat Patel bca70e0842 Merge pull request #1843 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-13 19:15:14 +05:30
Jannat Patel cf659f93d8 Merge branch 'develop' into l10n_develop2 2025-11-13 17:42:48 +05:30
Jannat Patel 8bfc2a5297 Merge pull request #1777 from JoeBrar/feature/coupons
feat: coupon code discount
2025-11-13 17:42:34 +05:30
Jannat Patel 783f0ed750 chore: German translations 2025-11-13 17:11:59 +05:30
Jannat Patel 90e4097fa3 fix: update redemption count when payment received 2025-11-13 14:50:55 +05:30
Jannat Patel 2aac558d4a fix: don't escape batch details field 2025-11-13 14:22:50 +05:30
Jannat Patel 9d3714eb90 Merge pull request #1841 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-13 11:47:06 +05:30
Jannat Patel 46f5808fdb refactor: transactions changes based on coupon code 2025-11-13 11:46:35 +05:30
Jannat Patel 8dbc85d03d chore: Bosnian translations 2025-11-12 17:15:50 +05:30
Jannat Patel f4be59f958 chore: Croatian translations 2025-11-12 17:15:48 +05:30
Jannat Patel 4d38f0637c chore: Persian translations 2025-11-12 17:15:46 +05:30
Jannat Patel feb7758830 chore: Swedish translations 2025-11-12 17:15:45 +05:30
Jannat Patel 915be8dbdc Merge pull request #1835 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-12 10:51:02 +05:30
Jannat Patel 3a6d0998c4 chore: Esperanto translations 2025-11-11 17:19:40 +05:30
Jannat Patel 048bc7e421 chore: Serbian (Latin) translations 2025-11-11 17:19:39 +05:30
Jannat Patel 3b0643da47 chore: Norwegian Bokmal translations 2025-11-11 17:19:36 +05:30
Jannat Patel f873b396b6 chore: Bosnian translations 2025-11-11 17:19:35 +05:30
Jannat Patel 5ec67115ba chore: Burmese translations 2025-11-11 17:19:33 +05:30
Jannat Patel 2a21714ed1 chore: Croatian translations 2025-11-11 17:19:31 +05:30
Jannat Patel 1913529bf0 chore: Thai translations 2025-11-11 17:19:30 +05:30
Jannat Patel 419f47d36e chore: Tamil translations 2025-11-11 17:19:28 +05:30
Jannat Patel fba2b1ea9b chore: Persian translations 2025-11-11 17:19:27 +05:30
Jannat Patel 9f53e2c8da chore: Indonesian translations 2025-11-11 17:19:25 +05:30
Jannat Patel 13f2a79ebb chore: Portuguese, Brazilian translations 2025-11-11 17:19:24 +05:30
Jannat Patel 800f624d1d chore: Vietnamese translations 2025-11-11 17:19:22 +05:30
Jannat Patel 91e7b18506 chore: Chinese Simplified translations 2025-11-11 17:19:21 +05:30
Jannat Patel cc3a46b1ff chore: Turkish translations 2025-11-11 17:19:19 +05:30
Jannat Patel a4aed7b61f chore: Swedish translations 2025-11-11 17:19:17 +05:30
Jannat Patel 1aee97c64f chore: Serbian (Cyrillic) translations 2025-11-11 17:19:16 +05:30
Jannat Patel d33980a8c6 chore: Russian translations 2025-11-11 17:19:14 +05:30
Jannat Patel c589def0de chore: Portuguese translations 2025-11-11 17:19:12 +05:30
Jannat Patel c70ef1e2e5 chore: Polish translations 2025-11-11 17:19:11 +05:30
Jannat Patel 60b6a73bd7 chore: Dutch translations 2025-11-11 17:19:09 +05:30
Jannat Patel b755f3b29b chore: Italian translations 2025-11-11 17:19:07 +05:30
Jannat Patel be4eeeb9d7 chore: Hungarian translations 2025-11-11 17:19:06 +05:30
Jannat Patel 31beae63fd chore: German translations 2025-11-11 17:19:04 +05:30
Jannat Patel 16a530fb50 chore: Danish translations 2025-11-11 17:19:03 +05:30
Jannat Patel b8c5b7f479 chore: Czech translations 2025-11-11 17:19:01 +05:30
Jannat Patel f64475b793 chore: Arabic translations 2025-11-11 17:18:59 +05:30
Jannat Patel 8e6de04e23 chore: Spanish translations 2025-11-11 17:18:58 +05:30
Jannat Patel 25f6440b1b chore: French translations 2025-11-11 17:18:56 +05:30
Jannat Patel c1bdfe33f0 Merge branch 'develop' of https://github.com/frappe/lms into feature/coupons 2025-11-11 16:28:07 +05:30
Jannat Patel d7de538345 Merge pull request #1834 from pateljannat/issues-149
fix: clear user cache after updating roles
2025-11-11 15:42:16 +05:30
Jannat Patel d2950bc0b5 fix: clear user cache after updating roles 2025-11-11 15:32:44 +05:30
Jannat Patel 6333f58f56 Merge pull request #1831 from pateljannat/issues-148
fix: misc
2025-11-11 14:30:50 +05:30
Jannat Patel bc187aabfe test: open settings before creating evaluator in batch test 2025-11-11 13:17:10 +05:30
Jannat Patel f825887181 fix: replaced notifications Link with a tag 2025-11-11 13:01:51 +05:30
Jannat Patel 2bdc35055c Merge pull request #1815 from rehanrehman389/link-fix
fix: ensure options reload after updates
2025-11-11 13:00:26 +05:30
Jannat Patel b6602f9e4b feat: session refresh 2025-11-11 12:55:51 +05:30
Jannat Patel ab366837a2 fix: escape HTML in forms 2025-11-11 12:17:26 +05:30
Jannat Patel c0a7a9b753 Merge branch 'develop' of https://github.com/frappe/lms into data-import 2025-11-10 15:35:42 +05:30
Jannat Patel c951732eb4 fix: profile edit issue 2025-11-10 15:11:13 +05:30
Jannat Patel 1b638d118d chore: upgraded frappe-ui to latest data import commit 2025-11-10 15:08:07 +05:30
Jannat Patel 1173ac6504 Merge pull request #1830 from pateljannat/issues-147
fix: misc issues
2025-11-10 13:42:13 +05:30
Jannat Patel 9447903d5b fix: misc issues 2025-11-10 13:19:54 +05:30
Jannat Patel fff9769791 fix: profile slot and schedule tab visibility 2025-11-10 12:09:51 +05:30
Jannat Patel d5c5faf0ca Merge pull request #1829 from pateljannat/issues-146
fix: evaluation issues
2025-11-10 11:34:18 +05:30
Jannat Patel 85d793ee64 fix: evaluation issues 2025-11-10 11:24:43 +05:30
Jannat Patel cf9c9fb5d3 chore: upgraded frappe-ui to latest data import commit 2025-11-10 10:39:43 +05:30
Jannat Patel b7144727e9 Merge pull request #1828 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-10 09:12:29 +05:30
Jannat Patel 60cd84972c Merge pull request #1827 from frappe/pot_develop_2025-11-07
chore: update POT file
2025-11-10 09:12:16 +05:30
Jannat Patel 5854d2514f fix: reload import list after import completes 2025-11-09 15:16:32 +05:30
Jannat Patel 226893a8b2 chore: upgraded frappe-ui to latest data import commit 2025-11-09 14:57:27 +05:30
Jannat Patel b182d5ea16 fix: replaced Link component with a tag in notifications 2025-11-09 12:12:35 +05:30
Jannat Patel 6bc28eafbf chore: upgraded frappe-ui to latest data import commit 2025-11-08 19:28:54 +05:30
Jannat Patel 1983190da3 chore: Indonesian translations 2025-11-08 14:05:12 +05:30
frappe-pr-bot 656c0cf012 chore: update POT file 2025-11-07 16:04:28 +00:00
Jannat Patel 60ddc1f8b2 chore: merged conflicts 2025-11-07 15:45:21 +05:30
Jannat Patel c3604ab74a chore: changed frappe-ui version to data-import commit 2025-11-07 15:26:25 +05:30
Jannat Patel eac0428dc6 Merge pull request #1822 from frappe/develop
chore: merge 'develop' into 'main'
2025-11-07 12:41:36 +05:30
Frappe PR Bot 53cd427a75 chore(release): Bumped to Version 2.40.0 2025-11-07 06:34:46 +00:00
Jannat Patel 96cf6ddbf9 Merge pull request #1791 from rehanrehman389/job-application
feat: show job applications on frontend
2025-11-07 12:02:02 +05:30
Jannat Patel 66c2ec013c Merge pull request #1814 from rehanrehman389/fix-resume-url
fix: use file_url instead of file_name
2025-11-07 12:00:54 +05:30
Rehan Ansari e3d5bf0220 refactor: add dropdown menu for job application actions 2025-11-06 23:42:36 +05:30
Jannat Patel 3281358282 fix: added horizontal scroll to settings modal 2025-11-06 16:45:31 +05:30
Jannat Patel 18e499e6de fix: coupon code application on billing page 2025-11-04 15:12:17 +05:30
Rehan Ansari 514d52f895 fix: improve job applications UI 2025-11-03 09:49:01 +05:30
Rehan Ansari 8719fa6696 feat: refactor job applications UI with ListView 2025-11-03 01:49:59 +05:30
Rehan Ansari 9c38444c4b fix: make resumes private, linked and shared with job owner 2025-11-01 19:08:36 +05:30
Rehan Ansari a5f9adc875 fix: patch old resumes to be private, link to job application, and share with job owner 2025-11-01 14:48:45 +05:30
Rehan Ansari 74de43c3d6 fix: ensure options reload after updates 2025-10-31 00:10:16 +05:30
Rehan Ansari 9f81bf695c fix: use file_url instead of file_name 2025-10-30 01:03:01 +05:30
Jannat Patel 3caf743f29 Merge pull request #1811 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-29 21:02:30 +05:30
Jannat Patel ad28218893 refactor: coupon list and form 2025-10-28 18:05:31 +05:30
Jannat Patel 760811f172 Merge branch 'develop' of https://github.com/frappe/lms into feature/coupons 2025-10-27 12:39:10 +05:30
Rehan Ansari 68e7684da3 fix: avoid backend updates without save action 2025-10-25 14:42:22 +05:30
Rehan Ansari ca2cc7bbda fix: improve delete button UX and add confirmation dialog 2025-10-25 14:09:31 +05:30
Rehan Ansari 741cc4ccc7 fix: allow adding courses/members to new programs 2025-10-25 13:03:53 +05:30
Rehan Ansari 0c1f1fada4 fix: incorrect error msg 2025-10-25 10:09:31 +05:30
Himanshu Shivhare 5a91c73a91 Merge branch 'develop' into patch-2 2025-10-22 17:38:05 +05:30
Rehan Ansari 9da1bfeea1 fix: set job owner as reply-to for job emails 2025-10-20 13:21:52 +05:30
Jannat Patel d4b603a4dd Merge pull request #1792 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-20 12:08:28 +05:30
Rehan Ansari a1a302f222 feat: show job applications on frontend 2025-10-20 11:17:02 +05:30
Jannat Patel 7d2b98f674 Merge pull request #1782 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-15 12:21:17 +05:30
Jannat Patel 2f5b0a3bf8 feat: data import 2025-10-14 19:21:35 +05:30
Joedeep Singh 1efd5ebad5 Merge branch 'develop' into feature/coupons 2025-10-13 21:37:44 +05:30
Joedeep Singh acb5e5e1c9 chore: removed unnecessary comments and log statements 2025-10-13 15:35:56 +00:00
Joedeep Singh 0f24fd6edc feat(coupon-details): prevent duplicate course/batch selections and clear name on type change 2025-10-13 14:54:03 +00:00
Joedeep Singh 99c448e0e5 chore: applied pre-commit hooks 2025-10-13 14:06:37 +00:00
Joedeep Singh cabb499a43 fix: correctly show coupon code in transactions 2025-10-13 13:47:02 +00:00
Joedeep Singh 6933105261 feat: implement coupon code manage
ment with billing and transaction integration; bug fixes
2025-10-13 13:03:43 +00:00
Joedeep Singh bf36890bd3 feat: added coupon code functionality 2025-10-12 17:07:42 +00:00
Himanshu Shivhare 05b6b97b2a fix: beautifulsoap4 version upgrade to match with Frappe Framework 2025-10-12 17:00:34 +05:30
Jannat Patel 6a8aca39a0 Merge pull request #1764 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-10 16:45:01 +05:30
Jannat Patel 7434a324fb Merge pull request #1754 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-01 13:08:16 +05:30
Jannat Patel fd934e1e82 Merge pull request #1735 from frappe/develop
chore: merge 'develop' into 'main'
2025-09-24 13:02:43 +05:30
Jannat Patel dd94d12e3a Merge pull request #1723 from frappe/develop
chore: merge 'develop' into 'main'
2025-09-19 10:51:28 +05:30
Jannat Patel fe85dd867d Merge branch 'develop' of https://github.com/frappe/lms 2025-09-03 15:44:43 +05:30
Jannat Patel 12f2047910 Merge pull request #1709 from frappe/develop
chore: merge 'develop' into 'main'
2025-09-03 12:30:33 +05:30
Jannat Patel cb6931bd88 Merge pull request #1699 from frappe/develop
chore: merge 'develop' into 'main'
2025-08-29 17:42:38 +05:30
Jannat Patel c76f9141fc Merge pull request #1685 from frappe/develop
chore: merge 'develop' into 'main'
2025-08-13 10:47:33 +05:30
Jannat Patel ddf70ce3d4 Merge pull request #1662 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-30 11:57:59 +05:30
Jannat Patel 7beb82e804 Merge pull request #1654 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-23 11:34:46 +05:30
Jannat Patel 8d9b8951bf Merge pull request #1652 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-23 11:10:44 +05:30
Jannat Patel b20d045c8e Merge pull request #1650 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-22 19:00:42 +05:30
Jannat Patel 736ce8eb3f Merge pull request #1636 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-14 12:06:42 +05:30
Jannat Patel 3409049559 Merge pull request #1576 from frappe/develop
chore: merge 'develop' into 'main'
2025-06-16 17:17:02 +05:30
Jannat Patel 5212122946 Merge pull request #1570 from frappe/develop
chore: merge 'develop' into 'main'
2025-06-10 15:51:20 +05:30
430 changed files with 64158 additions and 47196 deletions
+36 -5
View File
@@ -3,10 +3,15 @@ on:
push:
branches:
- main
- develop
- main-hotfix
pull_request: {}
jobs:
tests:
name: Server Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
services:
redis-cache:
image: redis:alpine
@@ -30,13 +35,13 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.14'
- name: setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v6
with:
node-version: '18'
node-version: '24'
check-latest: true
- name: setup cache for bench
uses: actions/cache@v4
@@ -69,6 +74,9 @@ jobs:
- name: setup requirements
working-directory: /home/runner/frappe-bench
run: bench setup requirements --dev
- name: block endpoints
working-directory: /home/runner/frappe-bench
run: bench --site frappe.local set-config block_endpoints 1
- name: allow tests
working-directory: /home/runner/frappe-bench
run: bench --site frappe.local set-config allow_tests true
@@ -77,4 +85,27 @@ jobs:
run: bench --site frappe.local build
- name: run tests
working-directory: /home/runner/frappe-bench
run: bench --site frappe.local run-tests --app lms
run: bench --site frappe.local run-tests --app lms --coverage
- name: Upload coverage data
uses: actions/upload-artifact@v4
with:
path: /home/runner/frappe-bench/sites/coverage.xml
coverage:
name: Coverage Wrap Up
needs: tests
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v3
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Upload coverage data
uses: codecov/codecov-action@v5
with:
name: Server
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
verbose: true
+7 -2
View File
@@ -22,9 +22,14 @@ jobs:
ref: ${{ matrix.branch }}
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.12"
python-version: "3.14"
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Run script to update POT file
run: |
+4 -4
View File
@@ -16,9 +16,9 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 200
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: 20
node-version: 24
check-latest: true
- name: Check commit titles
@@ -35,9 +35,9 @@ jobs:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.14'
- name: Cache pip
uses: actions/cache@v4
+2 -2
View File
@@ -18,9 +18,9 @@ jobs:
owner: frappe
repo: lms
title: |-
"chore: merge 'develop' into 'main'"
"chore: merge 'main-hotfix' into 'main'"
body: "Automated weekly release"
base: main
head: develop
head: main-hotfix
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
+2 -2
View File
@@ -15,9 +15,9 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 24
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
+8 -5
View File
@@ -4,7 +4,10 @@ on:
pull_request:
workflow_dispatch:
push:
branches: [ main ]
branches:
- main
- develop
- main-hotfix
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
@@ -36,9 +39,9 @@ jobs:
uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.11'
python-version: '3.14'
- name: Check for valid Python & Merge Conflicts
run: |
@@ -48,9 +51,9 @@ jobs:
exit 1
fi
- uses: actions/setup-node@v3
- uses: actions/setup-node@v6
with:
node-version: 18
node-version: 24
check-latest: true
- name: Add to Hosts
+3 -1
View File
@@ -12,4 +12,6 @@ node_modules
package-lock.json
lms/public/frontend
lms/www/lms.html
frappe-ui
lms/www/_lms.html
frappe-ui
frappe-semgrep-rules
+30
View File
@@ -0,0 +1,30 @@
pull_request_rules:
- name: backport to develop
conditions:
- label="backport develop"
actions:
backport:
branches:
- develop
assignees:
- "{{ author }}"
- name: backport to main-hotfix
conditions:
- label="backport main-hotfix"
actions:
backport:
branches:
- main-hotfix
assignees:
- "{{ author }}"
- name: backport to main
conditions:
- label="backport main"
actions:
backport:
branches:
- main
assignees:
- "{{ author }}"
+1 -1
View File
@@ -1,5 +1,5 @@
{
"branches": ["develop"],
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular"
+2
View File
@@ -0,0 +1,2 @@
ignore:
- "**/test_helper.py"
+24 -20
View File
@@ -27,6 +27,10 @@ describe("Batch Creation", () => {
cy.get("input[placeholder='Jane']").type(randomName);
cy.get("button").contains("Add").click();
// Open Settings
cy.get("span").contains("Learning").click();
cy.get("span").contains("Settings").click();
// Add evaluator
cy.get("[data-dismissable-layer]")
.find("span")
@@ -48,26 +52,23 @@ describe("Batch Creation", () => {
// Create a batch
cy.get("button").contains("Create").click();
cy.get("span").contains("New Batch").click();
cy.wait(500);
cy.url().should("include", "/batches/new/edit");
cy.get("label").contains("Title").type("Test Batch");
cy.get("label").contains("Start Date").type("2030-10-01");
cy.get("label").contains("End Date").type("2030-10-31");
cy.get("label").contains("Start Time").type("10:00");
cy.get("label").contains("End Time").type("11:00");
cy.get("label").contains("Timezone").type("IST");
cy.get("label").contains("Seat Count").type("10");
cy.get("label").contains("Published").click();
cy.get("label")
.contains("Short Description")
.contains("Description")
.type("Test Batch Short Description to test the UI");
cy.get("div[contenteditable=true").invoke(
"text",
"Test Batch Description. I need a very big description to test the UI. This is a very big description. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
);
/* Instructor */
cy.get("label")
.contains("Instructors")
@@ -85,13 +86,14 @@ describe("Batch Creation", () => {
cy.get("[id^=headlessui-combobox-option-").first().click();
});
});
cy.button("Save").click();
cy.get("label").contains("Published").click();
cy.button("Save").click();
cy.wait(1000);
let batchName;
cy.url().then((url) => {
console.log(url);
batchName = url.split("/").pop();
batchName = url.split("/").pop().split("#")[0];
cy.wrap(batchName).as("batchName");
});
cy.wait(500);
@@ -110,7 +112,7 @@ describe("Batch Creation", () => {
.click();
cy.get("@batchName").then((batchName) => {
cy.get(`a[href='/lms/batches/details/${batchName}'`).within(() => {
cy.get(`a[href='/lms/batches/${batchName}'`).within(() => {
cy.get("div").contains("Test Batch").should("be.visible");
cy.get("div")
.contains("Test Batch Short Description to test the UI")
@@ -123,14 +125,11 @@ describe("Batch Creation", () => {
.should("be.visible");
cy.get("span").contains("IST").should("be.visible");
cy.get("a").contains("Evaluator").should("be.visible");
cy.get("div")
.contains("10")
.should("be.visible")
.get("span")
.contains("Seats Left")
.should("be.visible");
cy.contains("div:visible", "10 Seats Left").should(
"be.visible"
);
});
cy.get(`a[href='/lms/batches/details/${batchName}'`).click();
cy.get(`a[href='/lms/batches/${batchName}'`).click();
});
cy.get("div").contains("Test Batch").should("be.visible");
@@ -152,17 +151,22 @@ describe("Batch Creation", () => {
"Test Batch Description. I need a very big description to test the UI. This is a very big description. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
)
.should("be.visible");
cy.get("button:visible").contains("Manage Batch").click();
cy.get("button:visible").contains("Dashboard").click();
/* Add student to batch */
cy.get("button").contains("Add").click();
cy.get('div[role="dialog"]').first().find("button").eq(1).click();
cy.get("input[id^='headlessui-combobox-input-v-']").type(randomEmail);
cy.get("button").contains("Enroll").click();
cy.get('div[role="dialog"]')
.first()
.find("div[label='Student']")
.find("div")
.first()
.click();
cy.get("input[placeholder='Search']").type(randomEmail);
cy.get("div").contains(randomEmail).click();
cy.get("button").contains("Submit").click();
// Verify Seat Count
cy.get("span").contains("Details").click();
cy.get("button:visible").contains("Overview").click();
cy.contains("div:visible", "9 Seats Left").should("be.visible");
});
});
+19 -17
View File
@@ -9,8 +9,8 @@ describe("Course Creation", () => {
// Create a course
cy.get("button").contains("Create").click();
cy.get("span").contains("New Course").click();
cy.wait(500);
cy.url().should("include", "/courses/new/edit");
cy.get("label").contains("Title").type("Test Course");
cy.get("label")
@@ -34,27 +34,13 @@ describe("Course Creation", () => {
});
});
cy.get("label")
.contains("Preview Video")
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
cy.get("label")
.contains("Category")
.parent()
.within(() => {
cy.get("button").click();
});
cy.get("[id^=headlessui-combobox-option-")
.should("be.visible")
.first()
.click();
/* Instructor */
cy.get("label")
.contains("Instructors")
.parent()
.within(() => {
cy.get("input").click().type("frappe");
cy.wait(500);
cy.get("input")
.invoke("attr", "aria-controls")
.as("instructor_list_id");
@@ -67,13 +53,29 @@ describe("Course Creation", () => {
});
});
cy.button("Save").last().click();
// Edit Course Details
cy.wait(500);
cy.get("label")
.contains("Preview Video")
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
cy.get("label")
.contains("Category")
.parent()
.within(() => {
cy.get("button").click();
});
cy.get("div").contains("Business").click();
cy.get("label").contains("Published").click();
cy.get("label").contains("Published On").type("2021-01-01");
cy.button("Save").click();
// Add Chapter
cy.wait(1000);
cy.button("Add Chapter").click();
cy.button("Add").click();
cy.wait(1000);
cy.get("[data-dismissable-layer]")
+3 -1
View File
@@ -6,5 +6,7 @@
// biome-ignore lint: disable
export {}
declare global {
const LucideGithub: typeof import('~icons/lucide/github').default
const LucideLinkedin: typeof import('~icons/lucide/linkedin').default
const LucideTwitter: typeof import('~icons/lucide/twitter').default
}
+15 -19
View File
@@ -8,13 +8,10 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
Annoucements: typeof import('./src/components/Annoucements.vue')['default']
AnnouncementModal: typeof import('./src/components/Modals/AnnouncementModal.vue')['default']
Apps: typeof import('./src/components/Apps.vue')['default']
AppSidebar: typeof import('./src/components/AppSidebar.vue')['default']
Apps: typeof import('./src/components/Sidebar/Apps.vue')['default']
AppSidebar: typeof import('./src/components/Sidebar/AppSidebar.vue')['default']
AssessmentModal: typeof import('./src/components/Modals/AssessmentModal.vue')['default']
AssessmentPlugin: typeof import('./src/components/AssessmentPlugin.vue')['default']
Assessments: typeof import('./src/components/Assessments.vue')['default']
Assignment: typeof import('./src/components/Assignment.vue')['default']
AssignmentForm: typeof import('./src/components/Modals/AssignmentForm.vue')['default']
AudioBlock: typeof import('./src/components/AudioBlock.vue')['default']
@@ -23,16 +20,8 @@ declare module 'vue' {
BadgeAssignments: typeof import('./src/components/Settings/BadgeAssignments.vue')['default']
BadgeForm: typeof import('./src/components/Settings/BadgeForm.vue')['default']
Badges: typeof import('./src/components/Settings/Badges.vue')['default']
BatchCard: typeof import('./src/components/BatchCard.vue')['default']
BatchCourseModal: typeof import('./src/components/Modals/BatchCourseModal.vue')['default']
BatchCourses: typeof import('./src/components/BatchCourses.vue')['default']
BatchDashboard: typeof import('./src/components/BatchDashboard.vue')['default']
BatchFeedback: typeof import('./src/components/BatchFeedback.vue')['default']
BatchOverlay: typeof import('./src/components/BatchOverlay.vue')['default']
BatchStudentProgress: typeof import('./src/components/Modals/BatchStudentProgress.vue')['default']
BatchStudents: typeof import('./src/components/BatchStudents.vue')['default']
BrandSettings: typeof import('./src/components/Settings/BrandSettings.vue')['default']
BulkCertificates: typeof import('./src/components/Modals/BulkCertificates.vue')['default']
Categories: typeof import('./src/components/Settings/Categories.vue')['default']
CertificationLinks: typeof import('./src/components/CertificationLinks.vue')['default']
ChapterModal: typeof import('./src/components/Modals/ChapterModal.vue')['default']
@@ -41,12 +30,18 @@ declare module 'vue' {
CodeEditor: typeof import('./src/components/Controls/CodeEditor.vue')['default']
CollapseSidebar: typeof import('./src/components/Icons/CollapseSidebar.vue')['default']
ColorSwatches: typeof import('./src/components/Controls/ColorSwatches.vue')['default']
CommandPalette: typeof import('./src/components/CommandPalette/CommandPalette.vue')['default']
CommandPaletteGroup: typeof import('./src/components/CommandPalette/CommandPaletteGroup.vue')['default']
Configuration: typeof import('./src/components/Sidebar/Configuration.vue')['default']
ContactUsEmail: typeof import('./src/components/ContactUsEmail.vue')['default']
CouponDetails: typeof import('./src/components/Settings/Coupons/CouponDetails.vue')['default']
CouponItems: typeof import('./src/components/Settings/Coupons/CouponItems.vue')['default']
CouponList: typeof import('./src/components/Settings/Coupons/CouponList.vue')['default']
Coupons: typeof import('./src/components/Settings/Coupons/Coupons.vue')['default']
CourseCard: typeof import('./src/components/CourseCard.vue')['default']
CourseCardOverlay: typeof import('./src/components/CourseCardOverlay.vue')['default']
CourseInstructors: typeof import('./src/components/CourseInstructors.vue')['default']
CourseOutline: typeof import('./src/components/CourseOutline.vue')['default']
CourseProgressSummary: typeof import('./src/components/Modals/CourseProgressSummary.vue')['default']
CourseReviews: typeof import('./src/components/CourseReviews.vue')['default']
CreateOutline: typeof import('./src/components/CreateOutline.vue')['default']
DateRange: typeof import('./src/components/Common/DateRange.vue')['default']
@@ -75,7 +70,6 @@ declare module 'vue' {
LessonContent: typeof import('./src/components/LessonContent.vue')['default']
LessonHelp: typeof import('./src/components/LessonHelp.vue')['default']
Link: typeof import('./src/components/Controls/Link.vue')['default']
LiveClass: typeof import('./src/components/LiveClass.vue')['default']
LiveClassAttendance: typeof import('./src/components/Modals/LiveClassAttendance.vue')['default']
LiveClassModal: typeof import('./src/components/Modals/LiveClassModal.vue')['default']
LMSLogo: typeof import('./src/components/Icons/LMSLogo.vue')['default']
@@ -86,6 +80,7 @@ declare module 'vue' {
NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default']
Notes: typeof import('./src/components/Notes/Notes.vue')['default']
NotPermitted: typeof import('./src/components/NotPermitted.vue')['default']
NumberChartGraph: typeof import('./src/components/NumberChartGraph.vue')['default']
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
PaymentGatewayDetails: typeof import('./src/components/Settings/PaymentGatewayDetails.vue')['default']
PaymentGateways: typeof import('./src/components/Settings/PaymentGateways.vue')['default']
@@ -103,18 +98,19 @@ declare module 'vue' {
SettingDetails: typeof import('./src/components/Settings/SettingDetails.vue')['default']
SettingFields: typeof import('./src/components/Settings/SettingFields.vue')['default']
Settings: typeof import('./src/components/Settings/Settings.vue')['default']
SidebarLink: typeof import('./src/components/SidebarLink.vue')['default']
SidebarLink: typeof import('./src/components/Sidebar/SidebarLink.vue')['default']
StudentHeatmap: typeof import('./src/components/StudentHeatmap.vue')['default']
StudentModal: typeof import('./src/components/Modals/StudentModal.vue')['default']
Tags: typeof import('./src/components/Tags.vue')['default']
TransactionDetails: typeof import('./src/components/Settings/TransactionDetails.vue')['default']
Transactions: typeof import('./src/components/Settings/Transactions.vue')['default']
TransactionDetails: typeof import('./src/components/Settings/Transactions/TransactionDetails.vue')['default']
TransactionList: typeof import('./src/components/Settings/Transactions/TransactionList.vue')['default']
Transactions: typeof import('./src/components/Settings/Transactions/Transactions.vue')['default']
UnsplashImageBrowser: typeof import('./src/components/UnsplashImageBrowser.vue')['default']
UpcomingEvaluations: typeof import('./src/components/UpcomingEvaluations.vue')['default']
Uploader: typeof import('./src/components/Controls/Uploader.vue')['default']
UploadPlugin: typeof import('./src/components/UploadPlugin.vue')['default']
UserAvatar: typeof import('./src/components/UserAvatar.vue')['default']
UserDropdown: typeof import('./src/components/UserDropdown.vue')['default']
UserDropdown: typeof import('./src/components/Sidebar/UserDropdown.vue')['default']
VideoBlock: typeof import('./src/components/VideoBlock.vue')['default']
VideoStatistics: typeof import('./src/components/Modals/VideoStatistics.vue')['default']
ZoomAccountModal: typeof import('./src/components/Modals/ZoomAccountModal.vue')['default']
+47 -45
View File
@@ -6,55 +6,57 @@
"scripts": {
"dev": "vite",
"serve": "vite preview",
"build": "vite build --base=/assets/lms/frontend/ && yarn copy-html-entry",
"copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/lms.html"
"build": "vite build --base=/assets/lms/frontend/ && yarn copy-html-entry && yarn copy-colors-json",
"copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/_lms.html",
"copy-colors-json": "cp node_modules/frappe-ui/tailwind/colors.json src/utils/frappe-ui-colors.json"
},
"dependencies": {
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-python": "^6.2.1",
"@editorjs/checklist": "^1.6.0",
"@editorjs/code": "^2.9.0",
"@editorjs/editorjs": "^2.29.0",
"@editorjs/embed": "^2.7.0",
"@editorjs/header": "^2.8.1",
"@editorjs/inline-code": "^1.5.0",
"@editorjs/nested-list": "^1.4.2",
"@editorjs/paragraph": "^2.11.3",
"@editorjs/simple-image": "^1.6.0",
"@editorjs/table": "^2.4.2",
"@vueuse/router": "^12.7.0",
"ace-builds": "^1.36.2",
"apexcharts": "^4.3.0",
"chart.js": "^4.4.1",
"codemirror": "^6.0.1",
"dayjs": "^1.11.6",
"dompurify": "^3.2.6",
"feather-icons": "^4.28.0",
"frappe-ui": "^0.1.201",
"highlight.js": "^11.11.1",
"lucide-vue-next": "^0.383.0",
"markdown-it": "^14.0.0",
"pinia": "^2.0.33",
"plyr": "^3.7.8",
"socket.io-client": "^4.7.2",
"tailwindcss": "3.4.15",
"thememirror": "^2.0.1",
"typescript": "^5.7.2",
"vue": "^3.4.23",
"vue-chartjs": "^5.3.0",
"vue-codemirror": "^6.1.1",
"vue-draggable-next": "^2.2.1",
"vue-router": "^4.0.12",
"vue3-apexcharts": "^1.8.0",
"@codemirror/lang-html": "6.4.9",
"@codemirror/lang-javascript": "6.2.4",
"@codemirror/lang-json": "6.0.1",
"@codemirror/lang-python": "6.2.1",
"@editorjs/checklist": "1.6.0",
"@editorjs/code": "2.9.0",
"@editorjs/editorjs": "2.29.0",
"@editorjs/embed": "2.7.0",
"@editorjs/header": "2.8.1",
"@editorjs/inline-code": "1.5.0",
"@editorjs/nested-list": "1.4.2",
"@editorjs/paragraph": "2.11.3",
"@editorjs/simple-image": "1.6.0",
"@editorjs/table": "2.4.2",
"@vueuse/core": "^14.1.0",
"ace-builds": "1.36.2",
"apexcharts": "4.3.0",
"chart.js": "4.4.1",
"codemirror": "6.0.1",
"dayjs": "1.11.10",
"dompurify": "3.2.6",
"feather-icons": "4.28.0",
"frappe-ui": "^0.1.261",
"highlight.js": "11.11.1",
"lucide-vue-next": "0.383.0",
"markdown-it": "14.0.0",
"pinia": "2.0.33",
"plyr": "3.7.8",
"socket.io-client": "4.7.2",
"thememirror": "2.0.1",
"typescript": "5.7.2",
"vue": "^3.5.27",
"vue-chartjs": "5.3.0",
"vue-codemirror": "6.1.1",
"vue-draggable-next": "2.2.1",
"vue-router": "^4.6.4",
"vue3-apexcharts": "1.8.0",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.3",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.5",
"vite": "^5.0.11",
"vite-plugin-pwa": "^1.0.2"
"@vitejs/plugin-vue": "5.0.3",
"autoprefixer": "10.4.2",
"postcss": "8.4.5",
"tailwindcss": "^3.4.15",
"unplugin-auto-import": "^20.3.0",
"vite": "5.0.11",
"vite-plugin-pwa": "^1.2.0"
}
}
+4 -11
View File
@@ -3,18 +3,17 @@
<Layout class="isolate text-base">
<router-view />
</Layout>
<!--<InstallPrompt v-if="isMobile" />-->
<InstallPrompt v-if="isMobile && !settings.data?.disable_pwa" />
<Dialogs />
</FrappeUIProvider>
</template>
<script setup>
import { FrappeUIProvider } from 'frappe-ui'
import { Dialogs } from '@/utils/dialogs'
import { computed, onUnmounted, ref, watch } from 'vue'
import { computed, onUnmounted, ref } from 'vue'
import { useScreenSize } from './utils/composables'
import { usersStore } from '@/stores/user'
import { useSettings } from '@/stores/settings'
import { useRouter } from 'vue-router'
import { posthogSettings } from '@/telemetry'
import DesktopLayout from './components/DesktopLayout.vue'
import MobileLayout from './components/MobileLayout.vue'
import NoSidebarLayout from './components/NoSidebarLayout.vue'
@@ -23,7 +22,7 @@ import InstallPrompt from './components/InstallPrompt.vue'
const { isMobile } = useScreenSize()
const router = useRouter()
const noSidebar = ref(false)
const { userResource } = usersStore()
const { settings } = useSettings()
router.beforeEach((to, from, next) => {
if (to.query.fromLesson || to.path === '/persona') {
@@ -47,10 +46,4 @@ const Layout = computed(() => {
onUnmounted(() => {
noSidebar.value = false
})
watch(userResource, () => {
if (userResource.data) {
posthogSettings.reload()
}
})
</script>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-152
View File
@@ -1,152 +0,0 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url("Inter-Thin.woff2?v=3.12") format("woff2"),
url("Inter-Thin.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url("Inter-ThinItalic.woff2?v=3.12") format("woff2"),
url("Inter-ThinItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 200;
font-display: swap;
src: url("Inter-ExtraLight.woff2?v=3.12") format("woff2"),
url("Inter-ExtraLight.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 200;
font-display: swap;
src: url("Inter-ExtraLightItalic.woff2?v=3.12") format("woff2"),
url("Inter-ExtraLightItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("Inter-Light.woff2?v=3.12") format("woff2"),
url("Inter-Light.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url("Inter-LightItalic.woff2?v=3.12") format("woff2"),
url("Inter-LightItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("Inter-Regular.woff2?v=3.12") format("woff2"),
url("Inter-Regular.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url("Inter-Italic.woff2?v=3.12") format("woff2"),
url("Inter-Italic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("Inter-Medium.woff2?v=3.12") format("woff2"),
url("Inter-Medium.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url("Inter-MediumItalic.woff2?v=3.12") format("woff2"),
url("Inter-MediumItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("Inter-SemiBold.woff2?v=3.12") format("woff2"),
url("Inter-SemiBold.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url("Inter-SemiBoldItalic.woff2?v=3.12") format("woff2"),
url("Inter-SemiBoldItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("Inter-Bold.woff2?v=3.12") format("woff2"),
url("Inter-Bold.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url("Inter-BoldItalic.woff2?v=3.12") format("woff2"),
url("Inter-BoldItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("Inter-ExtraBold.woff2?v=3.12") format("woff2"),
url("Inter-ExtraBold.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 800;
font-display: swap;
src: url("Inter-ExtraBoldItalic.woff2?v=3.12") format("woff2"),
url("Inter-ExtraBoldItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url("Inter-Black.woff2?v=3.12") format("woff2"),
url("Inter-Black.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url("Inter-BlackItalic.woff2?v=3.12") format("woff2"),
url("Inter-BlackItalic.woff?v=3.12") format("woff");
}
-53
View File
@@ -1,53 +0,0 @@
<template>
<div v-if="communications.data?.length">
<div v-for="comm in communications.data">
<div class="mb-8">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center">
<Avatar :label="comm.sender_full_name" size="lg" />
<div class="ml-2 text-ink-gray-7">
{{ comm.sender_full_name }}
</div>
</div>
<div class="text-sm">
{{ timeAgo(comm.communication_date) }}
</div>
</div>
<div
class="prose prose-sm bg-surface-menu-bar !min-w-full px-4 py-2 rounded-md"
v-html="comm.content"
></div>
</div>
</div>
</div>
<div v-else class="text-sm italic text-ink-gray-5">
{{ __('No announcements') }}
</div>
</template>
<script setup>
import { createResource, Avatar } from 'frappe-ui'
import { timeAgo } from '@/utils'
const props = defineProps({
batch: {
type: String,
required: true,
},
})
const communications = createResource({
url: 'lms.lms.api.get_announcements',
makeParams(value) {
return {
batch: props.batch,
}
},
auto: true,
cache: ['announcement', props.batch],
})
</script>
<style>
.prose-sm p {
margin: 0 0 0.5rem;
}
</style>
+38 -11
View File
@@ -26,28 +26,52 @@
v-model="quiz"
doctype="LMS Quiz"
:label="__('Select a quiz')"
placeholder=" "
:onCreate="(value, close) => redirectToForm()"
/>
<Link
v-else
v-model="assignment"
doctype="LMS Assignment"
:label="__('Select an assignment')"
:onCreate="(value, close) => redirectToForm()"
/>
<div v-else class="space-y-4">
<Link
v-if="filterAssignmentsByCourse"
v-model="assignment"
doctype="LMS Assignment"
:filters="{
course: route.params.courseName,
}"
placeholder=" "
:label="__('Select an Assignment')"
:onCreate="(value, close) => redirectToForm()"
/>
<Link
v-else
v-model="assignment"
doctype="LMS Assignment"
placeholder=" "
:label="__('Select an Assignment')"
:onCreate="(value, close) => redirectToForm()"
/>
<FormControl
type="checkbox"
:label="__('Filter assignments by course')"
v-model="filterAssignmentsByCourse"
/>
</div>
</div>
</div>
</template>
</Dialog>
</template>
<script setup>
import { Dialog } from 'frappe-ui'
import { onMounted, ref, nextTick } from 'vue'
import { Dialog, FormControl } from 'frappe-ui'
import { nextTick, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { getLmsRoute } from '@/utils/basePath'
import Link from '@/components/Controls/Link.vue'
const show = ref(false)
const quiz = ref(null)
const assignment = ref(null)
const filterAssignmentsByCourse = ref(false)
const route = useRoute()
const props = defineProps({
type: {
@@ -71,7 +95,10 @@ const addAssessment = () => {
}
const redirectToForm = () => {
if (props.type == 'quiz') window.open('/lms/quizzes/new', '_blank')
else window.open('/lms/assignments/new', '_blank')
if (props.type == 'quiz') {
window.open(getLmsRoute('quizzes?new=true'), '_blank')
} else {
window.open(getLmsRoute('assignments?new=true'), '_blank')
}
}
</script>
+114 -146
View File
@@ -16,8 +16,8 @@
{{ __('Submission by') }} {{ submissionResource.doc?.member_name }}
</div>
</div>
<div class="text-sm text-ink-gray-7 font-medium mb-2">
{{ __('Question') }}:
<div class="text-ink-gray-9 font-semibold mb-5">
{{ __('Assignment Question') }}
</div>
<div
v-html="assignment.data.question"
@@ -25,9 +25,9 @@
></div>
</div>
<div class="flex flex-col">
<div class="p-5">
<div class="flex items-center justify-between mb-4">
<div class="flex flex-col overflow-y-auto">
<div class="p-5 space-y-5">
<div class="flex items-center justify-between">
<div class="font-semibold text-ink-gray-9">
{{ __('Submission') }}
</div>
@@ -42,7 +42,11 @@
>
{{ submissionResource.doc?.status }}
</Badge>
<Button variant="solid" @click="submitAssignment()">
<Button
v-if="canModifyAssignment"
variant="solid"
@click="submitAssignment()"
>
{{ __('Save') }}
</Button>
</div>
@@ -53,7 +57,7 @@
!['Pass', 'Fail'].includes(submissionResource.doc?.status) &&
submissionResource.doc?.owner == user.data?.name
"
class="bg-surface-blue-2 text-ink-blue-2 p-3 rounded-md leading-5 text-sm mb-4"
class="bg-surface-blue-2 text-ink-blue-2 p-3 rounded-md leading-5 text-sm"
>
{{ __("You've successfully submitted the assignment.") }}
{{
@@ -63,17 +67,24 @@
}}
{{ __('Feel free to make edits to your submission if needed.') }}
</div>
<div v-if="showUploader()">
<div class="text-xs text-ink-gray-5 mt-1 mb-2">
{{ __('Add your assignment as {0}').format(assignment.data.type) }}
<div v-if="showUploader()" class="border rounded-lg p-3">
<div class="font-semibold mb-2">
{{ __('Upload Assignment') }}
</div>
<div class="text-ink-gray-5 text-sm mt-1 mb-4">
{{
__('You can only upload {0} files').format(assignment.data.type)
}}
</div>
<FileUploader
v-if="!submissionFile"
v-if="!attachment"
:fileTypes="getType()"
:uploadArgs="{
private: true,
}"
:validateFile="validateFile"
:validateFile="
(file) => validateFile(file, assignment.data.type.toLowerCase())
"
@success="(file) => saveSubmission(file)"
>
<template #default="{ uploading, progress, openFileSelector }">
@@ -87,21 +98,20 @@
</template>
</FileUploader>
<div v-else>
<div class="flex text-ink-gray-7">
<div class="border self-start rounded-md p-2 mr-2">
<FileText class="h-5 w-5 stroke-1.5" />
</div>
<div class="flex items-center text-ink-gray-7">
<a
:href="submissionFile.file_url"
:href="attachment"
target="_blank"
class="flex flex-col cursor-pointer !no-underline"
class="cursor-pointer !no-underline text-sm leading-5"
>
<span class="text-sm leading-5">
{{ submissionFile.file_name }}
</span>
<span class="text-sm text-ink-gray-5 mt-1">
{{ getFileSize(submissionFile.file_size) }}
</span>
<div class="flex items-center">
<div class="border rounded-md p-2 mr-2">
<FileText class="h-5 w-5 stroke-1.5" />
</div>
<span>
{{ attachment.split('/').pop() }}
</span>
</div>
</a>
<X
v-if="canModifyAssignment"
@@ -130,10 +140,11 @@
@change="(val) => (answer = val)"
:editable="true"
:fixedMenu="true"
:readonly="!canModifyAssignment"
:uploadArgs="{
private: true,
}"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
@@ -142,13 +153,13 @@
user.data?.name == submissionResource.doc?.owner &&
submissionResource.doc?.comments
"
class="mt-8 p-3 bg-surface-blue-2 rounded-md"
class="mt-8 p-3 border rounded-lg bg-surface-gray-2"
>
<div class="text-sm text-ink-gray-5 font-medium mb-2">
{{ __('Comments by Evaluator') }}:
<div class="text-ink-gray-5 mb-4">
{{ __('Comments by Evaluator') }}
</div>
<div
class="leading-5 text-ink-gray-9"
class="leading-6 text-ink-gray-9"
v-html="submissionResource.doc.comments"
></div>
</div>
@@ -179,7 +190,10 @@
"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
:uploadArgs="{
private: true,
}"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
</div>
@@ -201,11 +215,11 @@ import {
} from 'frappe-ui'
import { computed, inject, onMounted, onBeforeUnmount, ref, watch } from 'vue'
import { FileText, X } from 'lucide-vue-next'
import { getFileSize } from '@/utils'
import { useRouter } from 'vue-router'
import { validateFile } from '@/utils'
const submissionFile = ref(null)
const answer = ref(null)
const attachment = ref(null)
const comments = ref(null)
const router = useRouter()
const user = inject('$user')
@@ -255,129 +269,98 @@ const assignment = createResource({
},
})
const newSubmission = createResource({
url: 'frappe.client.insert',
makeParams(values) {
let doc = {
doctype: 'LMS Assignment Submission',
assignment: props.assignmentID,
member: user.data?.name,
}
if (showUploader()) {
doc.assignment_attachment = submissionFile.value.file_url
} else {
doc.answer = answer.value
}
return {
doc: doc,
}
},
})
const imageResource = createResource({
url: 'lms.lms.api.get_file_info',
makeParams(values) {
return {
file_url: values.image,
}
},
auto: false,
onSuccess(data) {
submissionFile.value = data
},
})
const submissionResource = createDocumentResource({
doctype: 'LMS Assignment Submission',
name: props.submissionName,
auto: false,
onError(err) {
toast.error(err.messages?.[0] || err)
},
auto: false,
cache: [user.data?.name, props.assignmentID],
})
watch(submissionResource, () => {
if (submissionResource.doc) {
if (submissionResource.doc.assignment_attachment) {
imageResource.reload({
image: submissionResource.doc.assignment_attachment,
})
}
if (submissionResource.doc.answer) {
answer.value = submissionResource.doc.answer
}
if (submissionResource.doc.comments) {
comments.value = submissionResource.doc.comments
}
if (submissionResource.isDirty) {
isDirty.value = true
} else if (showUploader() && !submissionFile.value) {
isDirty.value = true
} else if (!showUploader() && !answer.value) {
isDirty.value = true
} else {
isDirty.value = false
}
if (!submissionResource.doc) return
console.log(submissionResource.doc)
if (submissionResource.doc.answer) {
answer.value = submissionResource.doc.answer
}
})
watch(submissionFile, () => {
if (props.submissionName == 'new' && submissionFile.value) {
isDirty.value = true
if (submissionResource.doc.assignment_attachment) {
attachment.value = submissionResource.doc.assignment_attachment
}
if (submissionResource.doc.comments) {
comments.value = submissionResource.doc.comments
}
})
const submitAssignment = () => {
if (props.submissionName != 'new') {
let evaluator =
submissionResource.doc && submissionResource.doc.owner != user.data?.name
? user.data?.name
: null
submissionResource.setValue.submit(
{
...submissionResource.doc,
assignment_attachment: submissionFile.value?.file_url,
evaluator: evaluator,
comments: comments.value,
answer: answer.value,
},
{
onSuccess(data) {
toast.success(__('Changes saved successfully'))
},
}
)
updateSubmission()
} else {
addNewSubmission()
}
}
const addNewSubmission = () => {
newSubmission.submit(
{},
let doc = {
doctype: 'LMS Assignment Submission',
assignment: props.assignmentID,
member: user.data?.name,
}
if (!showUploader()) {
doc.answer = answer.value
} else {
doc.assignment_attachment = attachment.value
}
call('frappe.client.insert', {
doc: doc,
})
.then((data) => {
toast.success(__('Assignment submitted successfully'))
if (router.currentRoute.value.name == 'AssignmentSubmission') {
router.push({
name: 'AssignmentSubmission',
params: {
assignmentID: props.assignmentID,
submissionName: data.name,
},
query: { fromLesson: router.currentRoute.value.query.fromLesson },
})
} else {
markLessonProgress()
router.go()
}
isDirty.value = false
submissionResource.name = data.name
submissionResource.reload()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const updateSubmission = () => {
let evaluator =
submissionResource.doc && submissionResource.doc.owner != user.data?.name
? user.data?.name
: null
submissionResource.setValue.submit(
{
...submissionResource.doc,
evaluator: evaluator,
comments: comments.value,
answer: answer.value,
assignment_attachment: attachment.value,
},
{
onSuccess(data) {
toast.success(__('Assignment submitted successfully'))
if (router.currentRoute.value.name == 'AssignmentSubmission') {
router.push({
name: 'AssignmentSubmission',
params: {
assignmentID: props.assignmentID,
submissionName: data.name,
},
query: { fromLesson: router.currentRoute.value.query.fromLesson },
})
} else {
markLessonProgress()
router.go()
}
submissionResource.name = data.name
submissionResource.reload()
isDirty.value = false
toast.success(__('Changes saved successfully'))
},
onError(err) {
toast.error(err.messages?.[0] || err)
console.error(err)
},
}
)
@@ -385,7 +368,7 @@ const addNewSubmission = () => {
const saveSubmission = (file) => {
isDirty.value = true
submissionFile.value = file
attachment.value = file.file_url
}
const markLessonProgress = () => {
@@ -419,24 +402,9 @@ const getType = () => {
}
}
const validateFile = (file) => {
let type = assignment.data?.type
let extension = file.name.split('.').pop().toLowerCase()
if (type == 'Image' && !['jpg', 'jpeg', 'png'].includes(extension)) {
return 'Only image file is allowed.'
} else if (
type == 'Document' &&
!['doc', 'docx', 'xml'].includes(extension)
) {
return 'Only document file is allowed.'
} else if (type == 'PDF' && !['pdf'].includes(extension)) {
return 'Only PDF file is allowed.'
}
}
const removeSubmission = () => {
isDirty.value = true
submissionFile.value = null
submissionResource.doc.assignment_attachment = ''
}
const canGradeSubmission = computed(() => {
@@ -1,26 +0,0 @@
<template>
<div class="space-y-10">
<UpcomingEvaluations
:batch="batch.data.name"
:endDate="batch.data.evaluation_end_date"
:courses="batch.data.courses"
/>
<Assessments :batch="batch.data.name" />
<!-- <StudentHeatmap /> -->
</div>
</template>
<script setup>
import UpcomingEvaluations from '@/components/UpcomingEvaluations.vue'
import Assessments from '@/components/Assessments.vue'
const props = defineProps({
batch: {
type: Object,
default: null,
},
isStudent: {
type: Boolean,
default: false,
},
})
</script>
-354
View File
@@ -1,354 +0,0 @@
<template>
<div v-if="batch.data" class="">
<div class="w-full flex items-center justify-between pb-4">
<div class="font-medium text-ink-gray-7">
{{ __('Statistics') }}
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-5 mb-8">
<NumberChart
class="border rounded-md"
:config="{ title: __('Students'), value: students.data?.length || 0 }"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Certified'),
value: certificationCount.data || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Courses'),
value: batch.data.courses?.length || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{ title: __('Assessments'), value: assessmentCount || 0 }"
/>
</div>
<AxisChart
v-if="showProgressChart"
:config="{
data: chartData,
title: __('Batch Summary'),
subtitle: __('Progress of students in courses and assessments'),
xAxis: {
key: 'task',
title: 'Tasks',
type: 'category',
},
yAxis: {
title: __('Number of Students'),
echartOptions: {
minInterval: 1,
},
},
swapXY: true,
series: [
{
name: 'value',
type: 'bar',
},
],
}"
/>
</div>
<div>
<div class="flex items-center justify-between mb-4">
<div class="text-ink-gray-7 font-medium">
{{ __('Students') }}
</div>
<Button v-if="!readOnlyMode" @click="openStudentModal()">
<template #prefix>
<Plus class="h-4 w-4" />
</template>
{{ __('Add') }}
</Button>
</div>
<div v-if="students.data?.length">
<ListView
:columns="getStudentColumns()"
:rows="students.data"
row-key="name"
:options="{
showTooltip: false,
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
>
<ListHeaderItem
:item="item"
v-for="item in getStudentColumns()"
:title="item.label"
>
<template #prefix="{ item }">
<FeatherIcon
v-if="item.icon"
:name="item.icon"
class="h-4 w-4 stroke-1.5"
/>
</template>
</ListHeaderItem>
</ListHeader>
<ListRows>
<ListRow
:row="row"
v-for="row in students.data"
class="group cursor-pointer"
@click="openStudentProgressModal(row)"
>
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
:align="column.align"
class="text-sm"
>
<template #prefix>
<div v-if="column.key == 'full_name'">
<Avatar
class="flex items-center"
:image="row['user_image']"
:label="item"
size="sm"
/>
</div>
</template>
<div
v-if="column.key == 'progress'"
class="flex items-center space-x-4 w-full"
>
<ProgressBar :progress="row[column.key]" size="sm" />
<div class="text-xs">{{ row[column.key] }}%</div>
</div>
<div v-else>
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
<ListSelectBanner>
<template #actions="{ unselectAll, selections }">
<div class="flex gap-2">
<Button
variant="ghost"
@click="removeStudents(selections, unselectAll)"
>
<Trash2 class="h-4 w-4 stroke-1.5" />
</Button>
</div>
</template>
</ListSelectBanner>
</ListView>
</div>
<div v-else class="text-sm italic text-ink-gray-5">
{{ __('There are no students in this batch.') }}
</div>
</div>
<StudentModal
:batch="props.batch.data.name"
v-model="showStudentModal"
v-model:reloadStudents="students"
v-model:batchModal="props.batch"
/>
<BatchStudentProgress
:student="selectedStudent"
v-model="showStudentProgressModal"
/>
</template>
<script setup>
import {
Avatar,
AxisChart,
Button,
createResource,
FeatherIcon,
ListHeader,
ListHeaderItem,
ListSelectBanner,
ListRow,
ListRows,
ListView,
ListRowItem,
NumberChart,
toast,
} from 'frappe-ui'
import {
BookOpen,
GraduationCap,
Plus,
ShieldCheck,
Trash2,
User,
} from 'lucide-vue-next'
import { ref, watch } from 'vue'
import StudentModal from '@/components/Modals/StudentModal.vue'
import ProgressBar from '@/components/ProgressBar.vue'
import BatchStudentProgress from '@/components/Modals/BatchStudentProgress.vue'
import ApexChart from 'vue3-apexcharts'
import { theme } from '@/utils/theme'
const showStudentModal = ref(false)
const showStudentProgressModal = ref(false)
const selectedStudent = ref(null)
const chartData = ref(null)
const showProgressChart = ref(false)
const assessmentCount = ref(0)
const readOnlyMode = window.read_only_mode
const props = defineProps({
batch: {
type: Object,
default: null,
},
})
const students = createResource({
url: 'lms.lms.utils.get_batch_students',
params: {
batch: props.batch?.data?.name,
},
auto: true,
onSuccess(data) {
chartData.value = getChartData()
showProgressChart.value =
data.length &&
(props.batch?.data?.courses?.length || assessmentCount.value)
},
})
const getStudentColumns = () => {
let columns = [
{
label: 'Full Name',
key: 'full_name',
width: '20rem',
icon: 'user',
},
{
label: 'Progress',
key: 'progress',
width: '15rem',
icon: 'activity',
},
{
label: 'Last Active',
key: 'last_active',
width: '10rem',
align: 'center',
icon: 'clock',
},
]
return columns
}
const openStudentModal = () => {
showStudentModal.value = true
}
const openStudentProgressModal = (row) => {
showStudentProgressModal.value = true
selectedStudent.value = row
}
const deleteStudents = createResource({
url: 'lms.lms.api.delete_documents',
makeParams(values) {
return {
doctype: 'LMS Batch Enrollment',
documents: values.students,
}
},
})
const removeStudents = (selections, unselectAll) => {
deleteStudents.submit(
{
students: Array.from(selections),
},
{
onSuccess(data) {
students.reload()
props.batch.reload()
toast.success(__('Students deleted successfully'))
unselectAll()
},
}
)
}
const getChartData = () => {
let tasks = []
let data = []
students.data.forEach((row) => {
tasks = countAssessments(row, tasks)
tasks = countCourses(row, tasks)
})
tasks.forEach((task) => {
data.push({
task: task.label,
value: task.value,
})
})
return data
}
const countAssessments = (row, tasks) => {
Object.keys(row.assessments).forEach((assessment) => {
if (row.assessments[assessment].result === 'Pass') {
tasks.filter((task) => task.label === assessment).length
? tasks.filter((task) => task.label === assessment)[0].value++
: tasks.push({
value: 1,
label: assessment,
})
}
})
return tasks
}
const countCourses = (row, tasks) => {
Object.keys(row.courses).forEach((course) => {
if (row.courses[course] === 100) {
tasks.filter((task) => task.label === course).length
? tasks.filter((task) => task.label === course)[0].value++
: tasks.push({
value: 1,
label: course,
})
}
})
return tasks
}
watch(students, () => {
if (students.data?.length) {
assessmentCount.value = Object.keys(students.data?.[0].assessments).length
}
})
const certificationCount = createResource({
url: 'frappe.client.get_count',
params: {
doctype: 'LMS Certificate',
filters: {
batch_name: props.batch?.data?.name,
},
},
auto: true,
})
</script>
@@ -68,11 +68,12 @@ const props = defineProps({
const certification = createResource({
url: 'lms.lms.api.get_certification_details',
params: {
course: props.courseName,
makeParams(values) {
return {
course: props.courseName,
}
},
auto: user.data ? true : false,
cache: ['certificationData', user.data?.name],
})
const downloadCertificate = () => {
@@ -0,0 +1,272 @@
<template>
<Dialog v-model="show" :options="{ size: '2xl' }">
<template #body>
<div class="text-base">
<div class="flex items-center space-x-2 pl-4.5 border-b">
<Search class="size-4 text-ink-gray-4" />
<input
ref="inputRef"
type="text"
placeholder="Search"
class="w-full border-none bg-transparent py-3 !pl-2 pr-4.5 text-base text-ink-gray-7 placeholder-ink-gray-4 focus:ring-0"
@input="onInput"
v-model="query"
autocomplete="off"
/>
</div>
<div class="max-h-96 overflow-auto mb-2">
<div v-if="query.length" class="mt-5 space-y-5">
<CommandPaletteGroup
:list="searchResults"
@navigateTo="navigateTo"
/>
</div>
<div v-else class="mt-5 space-y-5">
<CommandPaletteGroup
:list="jumpToOptions"
@navigateTo="navigateTo"
/>
</div>
</div>
<div
class="flex items-center space-x-5 w-full border-t py-2 text-sm text-ink-gray-7 px-4.5"
>
<div class="flex items-center space-x-2">
<MoveUp
class="size-5 stroke-1.5 bg-surface-gray-2 p-1 rounded-sm"
/>
<MoveDown
class="size-5 stroke-1.5 bg-surface-gray-2 p-1 rounded-sm"
/>
<span>
{{ __('to navigate') }}
</span>
</div>
<div class="flex items-center space-x-2">
<CornerDownLeft
class="size-5 stroke-1.5 bg-surface-gray-2 p-1 rounded-sm"
/>
<span>
{{ __('to select') }}
</span>
</div>
<div class="flex items-center space-x-2">
<span class="bg-surface-gray-2 p-1 rounded-sm"> esc </span>
<span>
{{ __('to close') }}
</span>
</div>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { createResource, debounce, Dialog } from 'frappe-ui'
import { nextTick, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import {
BookOpen,
Briefcase,
CornerDownLeft,
FileSearch,
MoveUp,
MoveDown,
Search,
Users,
} from 'lucide-vue-next'
import CommandPaletteGroup from './CommandPaletteGroup.vue'
const show = defineModel<boolean>({ required: true, default: false })
const router = useRouter()
const query = ref<string>('')
const searchResults = ref<Array<any>>([])
const search = createResource({
url: 'lms.command_palette.search_sqlite',
makeParams: () => ({
query: query.value,
}),
onSuccess() {
generateSearchResults()
},
})
const debouncedSearch = debounce(() => {
if (query.value.length > 2) {
search.reload()
}
}, 500)
const onInput = () => {
debouncedSearch()
}
const generateSearchResults = () => {
search.data?.forEach((type: any) => {
let result: { title: string; items: any[] } = { title: '', items: [] }
result.title = type.title
type.items.forEach((item: any) => {
let paramName = item.doctype === 'LMS Course' ? 'courseName' : 'batchName'
item.route = {
name: item.doctype === 'LMS Course' ? 'CourseDetail' : 'BatchDetail',
params: {
[paramName]: item.name,
},
}
item.isActive = false
})
result.items = type.items
searchResults.value.push(result)
})
}
const appendSearchPage = () => {
let searchPage: { title: string; items: Array<any> } = {
title: '',
items: [],
}
searchPage.title = __('Jump to')
searchPage.items = [
{
title: __('Search for ') + `"${query.value}"`,
route: {
name: 'Search',
query: {
q: query.value,
},
},
icon: FileSearch,
isActive: true,
},
]
searchResults.value = [searchPage]
}
watch(
query,
() => {
appendSearchPage()
},
{ immediate: true }
)
watch(show, () => {
if (!show.value) {
query.value = ''
searchResults.value = []
}
})
onMounted(() => {
addKeyboardShortcuts()
})
const addKeyboardShortcuts = () => {
window.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'ArrowUp' && show.value) {
e.preventDefault()
shortcutForArrowKey(-1)
} else if (e.key === 'ArrowDown' && show.value) {
shortcutForArrowKey(1)
} else if (e.key === 'Enter' && show.value) {
shortcutForEnter()
} else if (e.key === 'Escape' && show.value) {
show.value = false
}
})
}
const shortcutForArrowKey = (direction: number) => {
let currentList = query.value.length
? searchResults.value
: jumpToOptions.value
let allItems = currentList.flatMap((result: any) => result.items)
let indexOfActive = allItems.findIndex((option: any) => option.isActive)
let newIndex = indexOfActive + direction
if (newIndex < 0) newIndex = allItems.length - 1
if (newIndex >= allItems.length) newIndex = 0
allItems[indexOfActive].isActive = false
allItems[newIndex].isActive = true
nextTick(scrollActiveItemIntoView)
}
const scrollActiveItemIntoView = () => {
const activeItem = document.querySelector(
'.hover\\:bg-surface-gray-2.bg-surface-gray-2'
) as HTMLElement
if (activeItem) {
activeItem.scrollIntoView({ block: 'nearest' })
}
}
const shortcutForEnter = () => {
let currentList = query.value.length
? searchResults.value
: jumpToOptions.value
let allItems = currentList.flatMap((result: any) => result.items)
let activeOption = allItems.find((option) => option.isActive)
if (activeOption) {
navigateTo(activeOption.route)
}
}
const navigateTo = (route: {
name: string
params?: Record<string, any>
query?: Record<string, any>
}) => {
show.value = false
query.value = ''
router.replace({ name: route.name, params: route.params, query: route.query })
}
const jumpToOptions = ref([
{
title: __('Jump to'),
items: [
{
title: 'Advanced Search',
icon: Search,
route: {
name: 'Search',
},
isActive: true,
},
{
title: 'Courses',
icon: BookOpen,
route: {
name: 'Courses',
},
isActive: false,
},
{
title: 'Batches',
icon: Users,
route: {
name: 'Batches',
},
isActive: false,
},
{
title: 'Jobs',
icon: Briefcase,
route: {
name: 'Jobs',
},
isActive: false,
},
],
},
])
</script>
<style>
mark {
background-color: theme('colors.amber.100');
font-weight: 500;
}
</style>
@@ -0,0 +1,45 @@
<template>
<div v-for="result in list" class="px-2.5 space-y-2">
<div class="text-ink-gray-5 px-2">
{{ result.title }}
</div>
<div class="">
<div
v-for="item in result.items"
class="flex items-center justify-between p-2 rounded hover:bg-surface-gray-2 cursor-pointer"
:class="{ 'bg-surface-gray-2': item.isActive }"
@click="emit('navigateTo', item.route)"
>
<div class="flex items-center space-x-3">
<component
v-if="item.icon"
:is="item.icon"
class="size-4 stroke-1.5 text-ink-gray-6"
/>
<div v-html="item.title"></div>
</div>
<div v-if="item.modified" class="text-ink-gray-5">
{{ dayjs.unix(item.modified).fromNow(true) }}
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { inject } from 'vue'
const dayjs = inject<any>('$dayjs')
const emit = defineEmits(['navigateTo'])
const props = defineProps<{
list: Array<{
title: string
items: Array<{
title: string
icon?: any
isActive?: boolean
modified?: string
}>
}>
}>()
</script>
+1 -1
View File
@@ -48,7 +48,7 @@ const settingsStore = useSettings()
const sendMail = (close: Function) => {
call('frappe.core.doctype.communication.email.make', {
recipients: settingsStore.contactUsEmail?.data,
recipients: settingsStore.settings?.data?.contact_us_email,
subject: subject.value,
content: message.value,
send_email: true,
@@ -16,13 +16,18 @@
<button
class="flex w-full items-center justify-between focus:outline-none"
:class="inputClasses"
@click="() => togglePopover()"
@click="
() => {
showOptions = !showOptions
togglePopover()
}
"
:disabled="attrs.readonly"
>
<div class="flex items-center">
<div class="flex items-center w-[90%]">
<slot name="prefix" />
<span
class="overflow-hidden text-ellipsis whitespace-nowrap text-base leading-5"
class="block truncate text-base leading-5"
v-if="selectedValue"
>
{{ displayValue(selectedValue) }}
@@ -99,18 +104,17 @@
name="item-label"
v-bind="{ active, selected, option }"
>
<div class="flex flex-col space-y-1 text-ink-gray-8">
<div>
{{ option.label }}
<div class="flex flex-col gap-1 p-1">
<div class="text-base font-medium text-ink-gray-8">
{{
option.value == option.label && option.description
? option.description
: option.label
}}
</div>
<div class="text-sm text-ink-gray-5">
{{ option.value }}
</div>
<div
v-if="
option.description &&
option.description != option.label
"
class="text-xs text-ink-gray-7"
v-html="option.description"
></div>
</div>
</slot>
</li>
@@ -120,7 +124,7 @@
v-if="groups.length == 0"
class="mt-1.5 rounded-md px-2.5 py-1.5 text-base text-ink-gray-5"
>
No results found
{{ __('No results found') }}
</li>
</ComboboxOptions>
<div v-if="slots.footer" class="border-t p-1.5 pb-0.5">
@@ -284,7 +288,7 @@ const inputClasses = computed(() => {
let variant = props.disabled ? 'disabled' : props.variant
let variantClasses = {
subtle:
'border border-gray-100 bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
'border border-outline-gray-modals bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
outline:
'border border-outline-gray-2 bg-surface-white placeholder-ink-gray-4 hover:border-outline-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
disabled: [
+79 -57
View File
@@ -3,59 +3,67 @@
<div class="text-xs text-ink-gray-5 mb-2">
{{ label }}
</div>
<div class="overflow-x-auto border rounded-md">
<div
class="grid items-center space-x-4 p-2 border-b"
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
>
<div class="overflow-visible border border-outline-gray-modals rounded-md">
<div class="overflow-x-auto">
<div
v-for="(column, index) in columns"
:key="index"
class="text-sm text-ink-gray-5"
class="grid items-center space-x-4 p-2 border-b border-outline-gray-modals"
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
>
{{ column }}
</div>
<div></div>
</div>
<div
v-for="(row, rowIndex) in rows"
:key="rowIndex"
class="grid items-center space-x-4 p-2"
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
>
<template v-for="key in Object.keys(row)" :key="key">
<input
v-if="showKey(key)"
v-model="row[key]"
class="py-1.5 px-2 border-none focus:ring-0 focus:border focus:border-gray-300 focus:bg-surface-gray-2 rounded-sm text-sm focus:outline-none"
/>
</template>
<div class="relative" ref="menuRef">
<Button
variant="ghost"
@click="(event: MouseEvent) => toggleMenu(rowIndex, event)"
>
<template #icon>
<Ellipsis
class="size-4 text-ink-gray-7 stroke-1.5 cursor-pointer"
/>
</template>
</Button>
<div
v-if="menuOpenIndex === rowIndex"
class="absolute right-[30px] top-5 mt-1 w-32 bg-surface-white border border-outline-gray-1 rounded-md shadow-sm"
v-for="(column, index) in columns"
:key="index"
class="text-sm text-ink-gray-5"
>
<button
@click="deleteRow(rowIndex)"
class="flex items-center space-x-2 w-full text-left px-3 py-2 text-sm text-ink-red-3"
{{ column }}
</div>
<div></div>
</div>
<div
v-for="(row, rowIndex) in rows"
:key="rowIndex"
class="grid items-center space-x-4 p-2"
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
>
<template v-for="key in Object.keys(row)" :key="key">
<input
v-if="showKey(key)"
v-model="row[key]"
class="py-1.5 px-2 w-full border-none bg-transparent text-ink-gray-8 focus:ring-0 focus:border focus:border-outline-gray-3 focus:bg-surface-gray-2 rounded-md text-sm focus:outline-none"
/>
</template>
<div class="relative">
<Button
variant="ghost"
@click="(event: MouseEvent) => toggleMenu(rowIndex, event)"
>
<Trash2 class="size-4 stroke-1.5" />
<span>
{{ __('Delete') }}
</span>
</button>
<template #icon>
<Ellipsis
class="size-4 text-ink-gray-7 stroke-1.5 cursor-pointer"
/>
</template>
</Button>
<div
v-if="menuOpenIndex === rowIndex"
ref="menuRef"
class="absolute right-0 w-32 z-50 bg-surface-modal border border-outline-gray-modals rounded-md shadow-sm"
:class="
rowIndex == (rows?.length ?? 0) - 1
? 'bottom-full mb-1'
: 'top-full mt-1'
"
>
<button
@click="deleteRow(rowIndex)"
class="flex items-center space-x-2 w-full text-left px-3 py-2 text-sm text-ink-red-3"
>
<Trash2 class="size-4 stroke-1.5" />
<span>
{{ __('Delete') }}
</span>
</button>
</div>
</div>
</div>
</div>
@@ -73,17 +81,19 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { nextTick, ref, watch } from 'vue'
import { Button } from 'frappe-ui'
import { Ellipsis, Plus, Trash2 } from 'lucide-vue-next'
import { onClickOutside } from '@vueuse/core'
const rows = defineModel<Cell[][]>()
const rows = defineModel<Record<string, string>[]>()
const menuRef = ref(null)
const menuOpenIndex = ref<number | null>(null)
const menuTopPosition = ref<string>('')
const menuLeftPosition = ref('0px')
const emit = defineEmits<{
(e: 'update:modelValue', value: Cell[][]): void
(e: 'update:modelValue', value: Record<string, string>[]): void
}>()
type Cell = {
@@ -93,19 +103,19 @@ type Cell = {
const props = withDefaults(
defineProps<{
modelValue?: Cell[][]
modelValue?: Record<string, string>[]
columns?: string[]
label?: string
}>(),
{
columns: [],
columns: () => [] as string[],
}
)
const columns = ref(props.columns)
watch(rows, () => {
if (rows.value?.length < 1) {
if (rows.value && rows.value.length < 1) {
addRow()
}
})
@@ -119,12 +129,25 @@ const addRow = () => {
newRow[column.toLowerCase().split(' ').join('_')] = ''
})
rows.value.push(newRow)
focusNewRowInput()
emit('update:modelValue', rows.value)
}
const focusNewRowInput = () => {
nextTick(() => {
const rowElements = document.querySelectorAll('.overflow-x-auto .grid')[
rows.value!.length
]
const firstInput = rowElements.querySelector('input')
if (firstInput) {
;(firstInput as HTMLInputElement).focus()
}
})
}
const deleteRow = (index: number) => {
rows.value.splice(index, 1)
emit('update:modelValue', rows.value)
rows.value?.splice(index, 1)
emit('update:modelValue', rows.value ?? [])
}
const getGridTemplateColumns = () => {
@@ -133,7 +156,6 @@ const getGridTemplateColumns = () => {
const toggleMenu = (index: number, event: MouseEvent) => {
menuOpenIndex.value = menuOpenIndex.value === index ? null : index
menuTopPosition.value = `${event.clientY + 10}px`
}
onClickOutside(menuRef, () => {
+1 -1
View File
@@ -107,7 +107,7 @@ async function setLanguageExtension() {
if (!languageImport) return
const module = await languageImport()
languageExtension.value = (module as any)[props.language]()
languageExtension.value = (module as any)[props.language]?.()
if (props.completions) {
const languageData = (module as any)[`${props.language}Language`]
@@ -21,8 +21,10 @@
:style="
modelValue
? {
backgroundColor:
theme.backgroundColor[modelValue.toLowerCase()][400],
backgroundColor: getColor(
modelValue.toLowerCase(),
400
),
}
: {}
"
@@ -55,8 +57,7 @@
:key="color"
class="size-5 rounded-full cursor-pointer"
:style="{
backgroundColor:
theme.backgroundColor[color.toLowerCase()][400],
backgroundColor: getColor(color.toLowerCase(), 400),
}"
@click="
(e) => {
@@ -79,7 +80,7 @@
import { Button, FormControl, Popover } from 'frappe-ui'
import { computed } from 'vue'
import { Palette, X } from 'lucide-vue-next'
import { theme } from '@/utils/theme'
import { getColor } from '@/utils'
const emit = defineEmits(['update:modelValue', 'change'])
@@ -20,7 +20,7 @@
class="w-4 h-4 text-ink-gray-7 stroke-1.5"
:is="icons.Folder"
/>
<span v-if="selectedIcon">
<span v-if="selectedIcon" class="text-ink-gray-7">
{{ selectedIcon }}
</span>
<span v-else class="text-ink-gray-5">
+13 -2
View File
@@ -11,7 +11,6 @@
:size="attrs.size || 'sm'"
:variant="attrs.variant"
:placeholder="attrs.placeholder"
:filterable="false"
:readonly="attrs.readonly"
>
<template #target="{ open, togglePopover }">
@@ -67,6 +66,7 @@ import { watchDebounced } from '@vueuse/core'
import { createResource, Button } from 'frappe-ui'
import { Plus, X } from 'lucide-vue-next'
import { useAttrs, computed, ref } from 'vue'
import { useSettings } from '@/stores/settings'
const props = defineProps({
doctype: {
@@ -96,13 +96,14 @@ const value = computed({
set: (val) => {
return (
val?.value &&
emit(valuePropPassed.value ? 'change' : 'update:modelValue', val?.value)
emit(valuePropPassed.value ? 'change' : 'update:modelValue', val.value)
)
},
})
const autocomplete = ref(null)
const text = ref('')
const settingsStore = useSettings()
watchDebounced(
() => autocomplete.value?.query,
@@ -121,6 +122,16 @@ watchDebounced(
{ debounce: 300, immediate: true }
)
watchDebounced(
() => settingsStore.isSettingsOpen,
(isOpen, wasOpen) => {
if (wasOpen && !isOpen) {
reload('')
}
},
{ debounce: 200 }
)
const options = createResource({
url: 'frappe.desk.search.search_link',
cache: [props.doctype, text.value],
+140 -179
View File
@@ -1,160 +1,145 @@
<template>
<div>
<label class="block mb-1" :class="labelClasses" v-if="label">
<label v-if="label" class="block mb-1" :class="labelClasses">
{{ label }}
<span class="text-ink-red-3" v-if="required">*</span>
<span v-if="required" class="text-ink-red-3">*</span>
</label>
<div class="w-full">
<Combobox v-model="selectedValue" nullable>
<Popover class="w-full" v-model:show="showOptions">
<template #target="{ togglePopover }">
<ComboboxInput
ref="search"
class="search-input form-input w-full focus-visible:!ring-0"
type="text"
:value="query"
@change="
(e) => {
query = e.target.value
showOptions = true
}
"
autocomplete="off"
@focus="() => togglePopover()"
@keydown.delete.capture.stop="removeLastValue"
/>
</template>
<template #body="{ isOpen, close }">
<div v-show="isOpen">
<div
class="mt-1 rounded-lg bg-surface-white py-1 text-base border-2"
<Combobox v-model="selectedValue" nullable v-slot="{ open }">
<div class="relative w-full">
<ComboboxInput
ref="search"
class="form-input w-full focus-visible:!ring-0"
type="text"
@change="
(e) => {
query = e.target.value
}
"
autocomplete="off"
@focus="onFocus"
/>
<ComboboxButton ref="trigger" class="hidden" />
<ComboboxOptions
v-show="open"
static
class="absolute z-20 mt-1 w-full rounded-lg bg-surface-modal border-2 border-outline-gray-modals max-h-[13rem] flex flex-col"
>
<div
class="flex-1 my-1 overflow-y-auto px-1.5"
:class="options.length ? 'min-h-[6rem]' : 'min-h-[3.8rem]'"
>
<template v-if="options.length">
<ComboboxOption
v-for="option in options"
:key="option.value"
:value="option"
v-slot="{ active }"
>
<ComboboxOptions
class="my-1 min-h-[6rem] max-h-[12rem] overflow-y-auto px-1.5"
static
<li
:class="[
'flex cursor-pointer items-center rounded px-2 py-1 text-base',
{ 'bg-surface-gray-2': active },
]"
>
<ComboboxOption
v-for="option in options"
:key="option.value"
:value="option"
v-slot="{ active }"
>
<li
:class="[
'flex cursor-pointer items-center rounded px-2 py-1 text-base',
{ 'bg-surface-gray-2': active },
]"
>
<div class="flex flex-col gap-1 p-1">
<div class="text-base font-medium text-ink-gray-8">
{{ option.description }}
</div>
<div class="text-sm text-ink-gray-5">
{{ option.value }}
</div>
</div>
</li>
</ComboboxOption>
<div class="h-10"></div>
<div
v-if="attrs.onCreate"
class="absolute bottom-2 left-1 w-[99%] pt-2 bg-white border-t"
>
<Button
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
@click="attrs.onCreate(close)"
>
<template #prefix>
<Plus class="h-4 w-4 stroke-1.5" />
</template>
</Button>
<div class="flex flex-col gap-1 p-1">
<div class="text-base font-medium text-ink-gray-8">
{{
option.value === option.label
? option.description
: option.label
}}
</div>
<div class="text-sm text-ink-gray-5">
{{ option.value }}
</div>
</div>
</ComboboxOptions>
</div>
</li>
</ComboboxOption>
</template>
<div v-else class="text-ink-gray-7 px-4 py-2">
{{ __('No results found') }}
</div>
</template>
</Popover>
</Combobox>
</div>
<div v-if="values.length" class="grid grid-cols-2 gap-2 mt-1">
</div>
<div
v-if="attrs.onCreate"
class="p-1 bg-surface-white border-t rounded-b-lg"
>
<Button
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
@click="attrs.onCreate()"
>
<template #prefix>
<Plus class="h-4 w-4 stroke-1.5" />
</template>
</Button>
</div>
</ComboboxOptions>
</div>
</Combobox>
<!-- Selected values -->
<div v-if="values?.length" class="grid grid-cols-2 gap-2 mt-1">
<div
v-for="value in values"
class="flex items-center justify-between break-all bg-surface-gray-2 text-ink-gray-7 word-wrap p-2 rounded-md mr-2"
:key="value"
class="flex items-center justify-between break-all bg-surface-gray-2 text-ink-gray-7 p-2 rounded-md"
>
<span class="break-all">
{{ value }}
</span>
<span>{{ value }}</span>
<X
class="size-4 stroke-1.5 cursor-pointer"
@click="removeValue(value)"
/>
</div>
</div>
<!-- <ErrorMessage class="mt-2 pl-2" v-if="error" :message="error" /> -->
</div>
</template>
<script setup>
import {
Combobox,
ComboboxButton,
ComboboxInput,
ComboboxOptions,
ComboboxOption,
} from '@headlessui/vue'
import { createResource, Popover, Button } from 'frappe-ui'
import { ref, computed, nextTick, useAttrs } from 'vue'
import { createResource, Button } from 'frappe-ui'
import { ref, computed, useAttrs, watch } from 'vue'
import { watchDebounced } from '@vueuse/core'
import { X, Plus } from 'lucide-vue-next'
const props = defineProps({
label: {
type: String,
},
size: {
type: String,
default: 'sm',
},
doctype: {
type: String,
required: true,
},
filters: {
type: Object,
default: () => ({}),
},
validate: {
type: Function,
default: null,
},
label: String,
size: { type: String, default: 'sm' },
doctype: { type: String, required: true },
filters: { type: Object, default: () => ({}) },
validate: Function,
errorMessage: {
type: Function,
default: (value) => `${value} is an Invalid value`,
},
required: {
type: Boolean,
},
required: Boolean,
})
const values = defineModel()
const attrs = useAttrs()
const emails = ref([])
const search = ref(null)
const error = ref(null)
const trigger = ref(null)
const query = ref('')
const text = ref('')
const showOptions = ref(false)
const selectedValue = ref(null)
const error = ref(null)
const selectedValue = computed({
get: () => query.value || '',
set: (val) => {
query.value = ''
if (val) {
showOptions.value = false
}
val?.value && addValue(val.value)
},
const emit = defineEmits(['update:modelValue'])
watch(selectedValue, (val) => {
if (!val?.value) return
query.value = ''
addValue(val.value)
selectedValue.value = null
emit('update:modelValue', values.value)
})
watchDebounced(
@@ -171,7 +156,6 @@ watchDebounced(
const filterOptions = createResource({
url: 'frappe.desk.search.search_link',
method: 'POST',
cache: [text.value, props.doctype],
auto: true,
params: {
txt: text.value,
@@ -180,7 +164,8 @@ const filterOptions = createResource({
})
const options = computed(() => {
return filterOptions.data || []
const allOptions = filterOptions.data || []
return allOptions.filter((option) => !values.value?.includes(option.value))
})
function reload(val) {
@@ -193,70 +178,46 @@ function reload(val) {
filterOptions.reload()
}
const addValue = (value) => {
function onFocus() {
if (!filterOptions.data?.length) {
reload('')
}
trigger.value?.$el.click()
}
function addValue(value) {
error.value = null
if (value) {
const splitValues = value.split(',')
splitValues.forEach((value) => {
value = value.trim()
if (value) {
// check if value is not already in the values array
if (!values.value?.includes(value)) {
// check if value is valid
if (value && props.validate && !props.validate(value)) {
error.value = props.errorMessage(value)
return
}
// add value to values array
if (!values.value) {
values.value = [value]
} else {
values.value.push(value)
}
value = value.replace(value, '')
}
}
})
!error.value && (value = '')
if (!value) return
const splitValues = value.split(',')
splitValues.forEach((val) => {
val = val.trim()
if (!val) return
if (values.value?.includes(val)) return
if (props.validate && !props.validate(val)) {
error.value = props.errorMessage(val)
return
}
if (!values.value) values.value = [val]
else values.value.push(val)
})
}
function removeValue(value) {
let indexToRemove = values.value.indexOf(value)
if (indexToRemove > -1) {
values.value.splice(indexToRemove, 1)
}
emit('update:modelValue', values.value)
}
const removeValue = (value) => {
values.value = values.value.filter((v) => v !== value)
}
const removeLastValue = () => {
if (query.value) return
let emailRef = emails.value[emails.value.length - 1]?.$el
if (document.activeElement === emailRef) {
values.value.pop()
nextTick(() => {
if (values.value.length) {
emailRef = emails.value[emails.value.length - 1].$el
emailRef?.focus()
} else {
setFocus()
}
})
} else {
emailRef?.focus()
}
}
function setFocus() {
search.value.$el.focus()
}
defineExpose({ setFocus })
const labelClasses = computed(() => {
return [
{
sm: 'text-xs',
md: 'text-base',
}[props.size || 'sm'],
'text-ink-gray-5',
]
})
const labelClasses = computed(() => [
{ sm: 'text-xs', md: 'text-base' }[props.size || 'sm'],
'text-ink-gray-5',
])
</script>
+36 -9
View File
@@ -2,18 +2,21 @@
<div class="mb-4">
<div v-if="label" class="text-xs text-ink-gray-5 mb-2">
{{ __(label) }}
<span class="text-ink-red-3">*</span>
<span v-if="required" class="text-ink-red-3">*</span>
</div>
<FileUploader
v-if="!modelValue"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file: File) => saveImage(file)"
:fileTypes="[fileType]"
:validateFile="(file: File) => validateFile(file, true, type)"
@success="(file: File) => saveFile(file)"
>
<template v-slot="{ file, progress, uploading, openFileSelector }">
<div class="flex items-center">
<div class="border rounded-md w-fit py-7 px-20">
<Image class="size-5 stroke-1 text-ink-gray-7" />
<component
:is="props.type === 'image' ? Image : Video"
class="size-5 stroke-1 text-ink-gray-7"
/>
</div>
<div class="ml-4">
<Button @click="openFileSelector">
@@ -28,7 +31,20 @@
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img :src="modelValue" class="border rounded-md w-44 h-auto" />
<img
v-if="type == 'image'"
:src="modelValue"
:class="[
'border object-cover',
shape === 'circle'
? 'w-20 h-20 rounded-full'
: 'w-44 h-auto min-h-20 max-h-32 rounded-md',
]"
/>
<video v-else controls class="border rounded-md w-44 h-auto">
<source :src="modelValue" />
{{ __('Your browser does not support the video tag.') }}
</video>
<div class="ml-4">
<Button @click="removeImage()">
{{ __('Remove') }}
@@ -47,7 +63,8 @@
<script setup lang="ts">
import { validateFile } from '@/utils'
import { Button, FileUploader } from 'frappe-ui'
import { Image } from 'lucide-vue-next'
import { Image, Video } from 'lucide-vue-next'
import { computed } from 'vue'
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
@@ -55,18 +72,28 @@ const emit = defineEmits<{
const props = withDefaults(
defineProps<{
modelValue: string
modelValue: string | null
label?: string
description?: string
type?: 'image' | 'video'
required?: boolean
shape?: 'square' | 'circle'
}>(),
{
modelValue: '',
label: '',
description: '',
type: 'image',
required: true,
shape: 'square',
}
)
const saveImage = (file: any) => {
const fileType = computed(() => {
return props.type === 'image' ? 'image/*' : 'video/*'
})
const saveFile = (file: any) => {
emit('update:modelValue', file.file_url)
}
+3 -12
View File
@@ -136,11 +136,11 @@
import { Award, BookOpen, GraduationCap, Star, Users } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session'
import { Tooltip } from 'frappe-ui'
import { theme } from '@/utils/theme'
import { formatAmount } from '@/utils'
import CourseInstructors from '@/components/CourseInstructors.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import ProgressBar from '@/components/ProgressBar.vue'
import colors from '@/utils/frappe-ui-colors.json'
const { user } = sessionStore()
@@ -152,19 +152,10 @@ const props = defineProps({
})
const getGradientColor = () => {
let theme = localStorage.getItem('theme') == 'dark' ? 'darkMode' : 'lightMode'
let color = props.course.card_gradient?.toLowerCase() || 'blue'
let colorMap = theme.backgroundColor[color]
let colorMap = colors[theme][color]
return `linear-gradient(to top right, black, ${colorMap[400]})`
/* return `bg-gradient-to-br from-${color}-100 via-${color}-200 to-${color}-400` */
/* return `linear-gradient(to bottom right, ${colorMap[100]}, ${colorMap[400]})` */
/* return `radial-gradient(ellipse at 80% 20%, black 20%, ${colorMap[500]} 100%)` */
/* return `radial-gradient(ellipse at 30% 70%, black 50%, ${colorMap[500]} 100%)` */
/* return `radial-gradient(ellipse at 80% 20%, ${colorMap[100]} 0%, ${colorMap[300]} 50%, ${colorMap[500]} 100%)` */
/* return `conic-gradient(from 180deg at 50% 50%, ${colorMap[100]} 0%, ${colorMap[200]} 50%, ${colorMap[400]} 100%)` */
/* return `linear-gradient(135deg, ${colorMap[100]}, ${colorMap[300]}), linear-gradient(120deg, rgba(255,255,255,0.4) 0%, transparent 60%) ` */
/* return `radial-gradient(circle at 20% 30%, ${colorMap[100]} 0%, transparent 40%),
radial-gradient(circle at 80% 40%, ${colorMap[200]} 0%, transparent 50%),
linear-gradient(135deg, ${colorMap[300]} 0%, ${colorMap[400]} 100%);` */
}
</script>
<style>
+17 -48
View File
@@ -37,7 +37,7 @@
<CertificationLinks :courseName="course.data.name" class="w-full" />
</div>
<router-link
v-else-if="course.data.paid_course"
v-else-if="course.data.paid_course && !isAdmin"
:to="{
name: 'Billing',
params: {
@@ -56,14 +56,15 @@
</Button>
</router-link>
<Badge
v-else-if="course.data.disable_self_learning"
v-else-if="course.data.disable_self_learning && !isAdmin"
theme="blue"
size="lg"
class="mb-4"
>
{{ __('Contact the Administrator to enroll for this course.') }}
{{ __('Contact the Administrator to enroll for this course') }}
</Badge>
<Button
v-else-if="!user.data?.is_moderator && !is_instructor()"
v-else-if="!isAdmin"
@click="enrollStudent()"
variant="solid"
class="w-full"
@@ -88,35 +89,6 @@
</template>
{{ __('Get Certificate') }}
</Button>
<Button
v-if="user.data?.is_moderator || is_instructor()"
class="w-full mt-2"
size="md"
@click="showProgressSummary"
>
<template #prefix>
<TrendingUp class="size-4 stroke-1.5" />
{{ __('Progress Summary') }}
</template>
</Button>
<router-link
v-if="user?.data?.is_moderator || is_instructor()"
:to="{
name: 'CourseForm',
params: {
courseName: course.data.name,
},
}"
>
<Button variant="subtle" class="w-full mt-2" size="md">
<template #prefix>
<Pencil class="size-4 stroke-1.5" />
</template>
<span>
{{ __('Edit') }}
</span>
</Button>
</router-link>
</div>
<div class="space-y-4">
<div
@@ -168,12 +140,6 @@
</div>
</div>
</div>
<CourseProgressSummary
v-if="user.data?.is_moderator || is_instructor()"
v-model="showProgressModal"
:courseName="course.data.name"
:enrollments="course.data.enrollments"
/>
</template>
<script setup>
import {
@@ -189,15 +155,14 @@ import {
import { computed, inject, ref } from 'vue'
import { Badge, Button, call, createResource, toast } from 'frappe-ui'
import { formatAmount } from '@/utils/'
import { capture } from '@/telemetry'
import { useRouter } from 'vue-router'
import CertificationLinks from '@/components/CertificationLinks.vue'
import CourseProgressSummary from '@/components/Modals/CourseProgressSummary.vue'
import { useTelemetry } from 'frappe-ui/frappe'
const router = useRouter()
const user = inject('$user')
const showProgressModal = ref(false)
const readOnlyMode = window.read_only_mode
const { capture } = useTelemetry()
const props = defineProps({
course: {
@@ -215,13 +180,17 @@ const video_link = computed(() => {
function enrollStudent() {
if (!user.data) {
toast.success(__('You need to login first to enroll for this course'))
toast.warning(__('You need to login first to enroll for this course'))
setTimeout(() => {
window.location.href = `/login?redirect-to=${window.location.pathname}`
}, 500)
} else {
call('lms.lms.doctype.lms_enrollment.lms_enrollment.create_membership', {
course: props.course.data.name,
call('frappe.client.insert', {
doc: {
doctype: 'LMS Enrollment',
course: props.course.data.name,
member: user.data.name,
},
})
.then(() => {
capture('enrolled_in_course', {
@@ -290,7 +259,7 @@ const fetchCertificate = () => {
})
}
const showProgressSummary = () => {
showProgressModal.value = true
}
const isAdmin = computed(() => {
return user.data?.is_moderator || is_instructor()
})
</script>
+20 -5
View File
@@ -15,7 +15,10 @@
{{ __(title) }}
</div>
<Button size="sm" v-if="allowEdit" @click="openChapterModal()">
{{ __('Add Chapter') }}
<template #prefix>
<Plus class="size-4 stroke-1.5" />
</template>
{{ __('Add') }}
</Button>
</div>
<div
@@ -95,8 +98,8 @@
name: allowEdit ? 'LessonForm' : 'Lesson',
params: {
courseName: courseName,
chapterNumber: lesson.number.split('.')[0],
lessonNumber: lesson.number.split('.')[1],
chapterNumber: lesson.number.split('-')[0],
lessonNumber: lesson.number.split('-')[1],
},
}"
>
@@ -109,6 +112,14 @@
v-else-if="lesson.icon === 'icon-quiz'"
class="h-4 w-4 stroke-1 mr-2"
/>
<NotebookPen
v-else-if="lesson.icon === 'icon-assignment'"
class="h-4 w-4 stroke-1 mr-2"
/>
<SquareCode
v-else-if="lesson.icon === 'icon-code'"
class="h-4 w-4 stroke-1 mr-2"
/>
<FileText
v-else-if="lesson.icon === 'icon-list'"
class="h-4 w-4 text-ink-gray-9 stroke-1 mr-2"
@@ -174,7 +185,11 @@ import {
FilePenLine,
HelpCircle,
MonitorPlay,
NotebookPen,
Plus,
SquareCode,
Trash2,
Notebook,
} from 'lucide-vue-next'
import { useRoute, useRouter } from 'vue-router'
import ChapterModal from '@/components/Modals/ChapterModal.vue'
@@ -389,8 +404,8 @@ const redirectToChapter = (chapter) => {
const isActiveLesson = (lessonNumber) => {
return (
route.params.chapterNumber == lessonNumber.split('.')[0] &&
route.params.lessonNumber == lessonNumber.split('.')[1]
route.params.chapterNumber == lessonNumber.split('-')[0] &&
route.params.lessonNumber == lessonNumber.split('-')[1]
)
}
</script>
+5 -5
View File
@@ -12,7 +12,7 @@
</div>
<div class="grid gap-8 mt-10">
<div v-for="(review, index) in reviews.data">
<div class="flex items-center">
<div class="flex">
<router-link
:to="{
name: 'Profile',
@@ -46,11 +46,11 @@
"
/>
</div>
<div v-if="review.review" class="mt-4 leading-5 text-ink-gray-7">
{{ review.review }}
</div>
</div>
</div>
<div v-if="review.review" class="mt-4 leading-5 text-ink-gray-7">
{{ review.review }}
</div>
</div>
</div>
</div>
@@ -80,7 +80,7 @@ const props = defineProps({
required: true,
},
membership: {
type: Object,
type: Object || null,
required: false,
},
})
+1 -1
View File
@@ -9,5 +9,5 @@
</div>
</template>
<script setup>
import AppSidebar from './AppSidebar.vue'
import AppSidebar from '@/components/Sidebar/AppSidebar.vue'
</script>
+59 -80
View File
@@ -93,11 +93,19 @@
</div>
</template>
<script setup>
import { createResource, TextEditor, Button, Dropdown, toast } from 'frappe-ui'
import {
call,
createResource,
TextEditor,
Button,
Dropdown,
toast,
} from 'frappe-ui'
import { timeAgo } from '@/utils'
import UserAvatar from '@/components/UserAvatar.vue'
import { ChevronLeft, MoreHorizontal } from 'lucide-vue-next'
import { ref, inject, onMounted, onUnmounted } from 'vue'
import { useTelemetry } from 'frappe-ui/frappe'
const showTopics = defineModel('showTopics')
const newReply = ref('')
@@ -107,6 +115,7 @@ const allUsers = inject('$allUsers')
const mentionUsers = ref([])
const renderEditor = ref(false)
const readOnlyMode = window.read_only_mode
const { capture } = useTelemetry()
const props = defineProps({
topic: {
@@ -143,19 +152,6 @@ const replies = createResource({
auto: true,
})
const newReplyResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Discussion Reply',
reply: newReply.value,
topic: props.topic.name,
},
}
},
})
const fetchMentionUsers = () => {
if (user.data?.is_student) {
renderEditor.value = true
@@ -178,78 +174,61 @@ const fetchMentionUsers = () => {
}
const postReply = () => {
newReplyResource.submit(
{},
{
validate() {
if (!newReply.value) {
return 'Reply cannot be empty'
}
},
onSuccess() {
newReply.value = ''
replies.reload()
},
onError(err) {
toast.error(err.messages?.[0] || err)
},
}
)
}
const editReplyResource = createResource({
url: 'frappe.client.set_value',
makeParams(values) {
return {
if (!newReply.value) {
toast.error(__('Reply cannot be empty.'))
return
}
call('frappe.client.insert', {
doc: {
doctype: 'Discussion Reply',
name: values.name,
fieldname: 'reply',
value: values.reply,
}
},
})
reply: newReply.value,
topic: props.topic.name,
},
})
.then((data) => {
newReply.value = ''
replies.reload()
capture('discussion_reply_created')
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const postEdited = (reply) => {
editReplyResource.submit(
{
name: reply.name,
reply: reply.reply,
},
{
validate() {
if (!reply.reply) {
return 'Reply cannot be empty'
}
},
onSuccess() {
reply.editable = false
replies.reload()
},
}
)
if (!reply.reply) {
toast.error(__('Reply cannot be empty.'))
return
}
call('frappe.client.set_value', {
doctype: 'Discussion Reply',
name: reply.name,
fieldname: 'reply',
value: reply.reply,
})
.then(() => {
reply.editable = false
replies.reload()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const deleteReplyResource = createResource({
url: 'frappe.client.delete',
makeParams(values) {
return {
doctype: 'Discussion Reply',
name: values.name,
}
},
})
const deleteReply = (reply) => {
deleteReplyResource.submit(
{
name: reply.name,
},
{
onSuccess() {
replies.reload()
},
}
)
call('frappe.client.delete', {
doctype: 'Discussion Reply',
name: reply.name,
})
.then(() => {
replies.reload()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
onUnmounted(() => {
+8 -1
View File
@@ -76,7 +76,14 @@ const isIos = () => {
const isInStandaloneMode = () =>
'standalone' in window.navigator && window.navigator.standalone
if (isIos() && !isInStandaloneMode()) iosInstallMessage.value = true
if (
isIos() &&
!isInStandaloneMode() &&
localStorage.getItem('learningIosInstallPromptShown') !== 'true'
) {
iosInstallMessage.value = true
localStorage.setItem('learningIosInstallPromptShown', 'true')
}
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
-217
View File
@@ -1,217 +0,0 @@
<template>
<div
v-if="hasPermission() && !props.zoomAccount"
class="flex items-center space-x-2 mb-5 bg-surface-amber-1 py-1 px-2 rounded-md text-ink-amber-3"
>
<AlertCircle class="size-4 stroke-1.5" />
<span>
{{ __('Please add a zoom account to the batch to create live classes.') }}
</span>
</div>
<div class="flex items-center justify-between">
<div class="text-lg font-semibold text-ink-gray-9">
{{ __('Live Class') }}
</div>
<Button v-if="canCreateClass()" @click="openLiveClassModal">
<template #prefix>
<Plus class="h-4 w-4" />
</template>
<span>
{{ __('Add') }}
</span>
</Button>
</div>
<div
v-if="liveClasses.data?.length"
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5 mt-5"
>
<div
v-for="cls in liveClasses.data"
class="flex flex-col border rounded-md h-full text-ink-gray-7 hover:border-outline-gray-3 p-3"
:class="{
'cursor-pointer': hasPermission() && cls.attendees > 0,
}"
@click="
() => {
openAttendanceModal(cls)
}
"
>
<div class="font-semibold text-ink-gray-9 text-lg mb-1">
{{ cls.title }}
</div>
<div class="short-introduction">
{{ cls.description }}
</div>
<div class="mt-auto space-y-3">
<div class="flex items-center space-x-2">
<Calendar class="w-4 h-4 stroke-1.5" />
<span>
{{ dayjs(cls.date).format('DD MMMM YYYY') }}
</span>
</div>
<div class="flex items-center space-x-2">
<Clock class="w-4 h-4 stroke-1.5" />
<span>
{{ dayjs(getClassStart(cls)).format('hh:mm A') }} -
{{ dayjs(getClassEnd(cls)).format('hh:mm A') }}
</span>
</div>
<div
v-if="canAccessClass(cls)"
class="flex items-center space-x-2 text-ink-gray-9 mt-auto"
>
<a
v-if="user.data?.is_moderator || user.data?.is_evaluator"
:href="cls.start_url"
target="_blank"
class="cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-ink-gray-8 bg-surface-gray-2 hover:bg-surface-gray-3 active:bg-surface-gray-4 focus-visible:ring focus-visible:ring-outline-gray-3 h-7 text-base px-2 rounded"
:class="cls.join_url ? 'w-full' : 'w-1/2'"
>
<Monitor class="h-4 w-4 stroke-1.5" />
{{ __('Start') }}
</a>
<a
:href="cls.join_url"
target="_blank"
class="w-full cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-ink-gray-8 bg-surface-gray-2 hover:bg-surface-gray-3 active:bg-surface-gray-4 focus-visible:ring focus-visible:ring-outline-gray-3 h-7 text-base px-2 rounded"
>
<Video class="h-4 w-4 stroke-1.5" />
{{ __('Join') }}
</a>
</div>
<Tooltip
v-else-if="hasClassEnded(cls)"
:text="__('This class has ended')"
placement="right"
>
<div class="flex items-center space-x-2 text-ink-amber-3 w-fit">
<Info class="w-4 h-4 stroke-1.5" />
<span>
{{ __('Ended') }}
</span>
</div>
</Tooltip>
</div>
</div>
</div>
<div v-else class="text-sm italic text-ink-gray-5 mt-2">
{{ __('No live classes scheduled') }}
</div>
<LiveClassModal
:batch="props.batch"
:zoomAccount="props.zoomAccount"
v-model="showLiveClassModal"
v-model:reloadLiveClasses="liveClasses"
/>
<LiveClassAttendance v-model="showAttendance" :live_class="attendanceFor" />
</template>
<script setup>
import { createListResource, Button, Tooltip } from 'frappe-ui'
import {
Plus,
Clock,
Calendar,
Video,
Monitor,
Info,
AlertCircle,
} from 'lucide-vue-next'
import { inject, ref } from 'vue'
import { formatTime } from '@/utils/'
import LiveClassModal from '@/components/Modals/LiveClassModal.vue'
import LiveClassAttendance from '@/components/Modals/LiveClassAttendance.vue'
const user = inject('$user')
const showLiveClassModal = ref(false)
const dayjs = inject('$dayjs')
const readOnlyMode = window.read_only_mode
const showAttendance = ref(false)
const attendanceFor = ref(null)
const props = defineProps({
batch: {
type: String,
required: true,
},
zoomAccount: String,
})
const liveClasses = createListResource({
doctype: 'LMS Live Class',
filters: {
batch_name: props.batch,
},
fields: [
'title',
'description',
'time',
'date',
'duration',
'attendees',
'start_url',
'join_url',
'owner',
],
orderBy: 'date',
auto: true,
})
const openLiveClassModal = () => {
showLiveClassModal.value = true
}
const canCreateClass = () => {
if (readOnlyMode) return false
if (!props.zoomAccount) return false
return hasPermission()
}
const hasPermission = () => {
return user.data?.is_moderator || user.data?.is_evaluator
}
const canAccessClass = (cls) => {
if (cls.date < dayjs().format('YYYY-MM-DD')) return false
if (cls.date > dayjs().format('YYYY-MM-DD')) return false
if (hasClassEnded(cls)) return false
return true
}
const getClassStart = (cls) => {
return new Date(`${cls.date}T${cls.time}`)
}
const getClassEnd = (cls) => {
const classStart = getClassStart(cls)
return new Date(classStart.getTime() + cls.duration * 60000)
}
const hasClassEnded = (cls) => {
const classEnd = getClassEnd(cls)
const now = new Date()
return now > classEnd
}
const openAttendanceModal = (cls) => {
if (!hasPermission()) return
if (cls.attendees <= 0) return
showAttendance.value = true
attendanceFor.value = cls
}
</script>
<style>
.short-introduction {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
width: 100%;
overflow: hidden;
margin: 0.25rem 0 1.5rem;
line-height: 1.5;
}
</style>
+24 -6
View File
@@ -76,12 +76,20 @@ const isModerator = ref(false)
const isInstructor = ref(false)
onMounted(() => {
// Вызываем addSideBar только если userResource уже загружен
if (userResource.data) {
addSideBar()
}
addOtherLinks()
filterLinksToShow(data)
sidebarSettings.reload(
{},
{
onSuccess(data) {
if (userResource.data) {
addSideBar()
} else {
destructureSidebarLinks()
}
filterLinksToShow(data)
addOtherLinks()
},
}
)
})
const handleOutsideClick = (e) => {
@@ -100,6 +108,16 @@ watch(showMenu, (val) => {
}
})
const destructureSidebarLinks = () => {
let links = []
sidebarLinks.value.forEach((link) => {
link.items?.forEach((item) => {
links.push(item)
})
})
sidebarLinks.value = links
}
const filterLinksToShow = (data) => {
Object.keys(data).forEach((key) => {
if (!parseInt(data[key])) {
@@ -20,11 +20,15 @@
:options="assessmentTypes"
v-model="assessmentType"
:label="__('Type')"
placeholder=" "
@update:modelValue="() => (assessment = null)"
/>
<Link
v-if="assessmentType"
v-model="assessment"
:doctype="assessmentType"
:label="__('Assessment')"
placeholder=" "
:onCreate="
(value, close) => {
close()
@@ -49,7 +53,7 @@
</template>
<script setup>
import { Dialog, FormControl, createResource, toast } from 'frappe-ui'
import Link from '@/components/Controls/Link.vue'
import { Link } from 'frappe-ui/frappe'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
@@ -27,6 +27,12 @@
:label="__('Submission Type')"
:required="true"
/>
<Link
v-model="assignment.course"
:label="__('Course')"
doctype="LMS Course"
placeholder=" "
/>
<div>
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Question') }}
@@ -37,7 +43,7 @@
@change="(val) => (assignment.question = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
/>
</div>
</div>
@@ -66,6 +72,8 @@
<script setup lang="ts">
import { Button, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { computed, reactive, watch } from 'vue'
import { escapeHTML, sanitizeHTML } from '@/utils'
import { Link } from 'frappe-ui/frappe'
const show = defineModel()
const assignments = defineModel<Assignments>('assignments')
@@ -74,6 +82,7 @@ interface Assignment {
title: string
type: string
question: string
course?: string
}
interface Assignments {
@@ -88,6 +97,7 @@ const assignment = reactive({
title: '',
type: '',
question: '',
course: '',
})
const props = defineProps({
@@ -106,6 +116,7 @@ watch(
assignment.title = row.title
assignment.type = row.type
assignment.question = row.question
assignment.course = row.course || ''
}
})
}
@@ -121,35 +132,49 @@ watch(show, (newVal) => {
}
})
const validateFields = () => {
assignment.title = escapeHTML(assignment.title.trim())
assignment.question = sanitizeHTML(assignment.question)
}
const saveAssignment = () => {
validateFields()
if (props.assignmentID == 'new') {
assignments.value.insert.submit(
{
...assignment,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment created successfully'))
},
}
)
createAssignment()
} else {
assignments.value.setValue.submit(
{
...assignment,
name: props.assignmentID,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment updated successfully'))
},
}
)
updateAssignment()
}
}
const createAssignment = () => {
assignments.value.insert.submit(
{
...assignment,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment created successfully'))
},
}
)
}
const updateAssignment = () => {
assignments.value.setValue.submit(
{
...assignment,
name: props.assignmentID,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment updated successfully'))
},
}
)
}
const assignmentOptions = computed(() => {
return [
{ label: 'PDF', value: 'PDF' },
@@ -2,8 +2,8 @@
<Dialog
v-model="show"
:options="{
title: __('Add a course'),
size: 'sm',
title: __('Add a course to the batch'),
size: 'lg',
actions: [
{
label: __('Submit'),
@@ -19,14 +19,13 @@
v-model="course"
:label="__('Course')"
:required="true"
:filters="{ published: 1 }"
:onCreate="
(value, close) => {
close()
router.push({
name: 'CourseForm',
params: {
courseName: 'new',
},
name: 'Courses',
query: { newCourse: '1' },
})
}
"
@@ -42,7 +41,7 @@
</Dialog>
</template>
<script setup>
import { Dialog, createResource, toast } from 'frappe-ui'
import { Dialog, toast } from 'frappe-ui'
import { ref, inject } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { useOnboarding } from 'frappe-ui/frappe'
@@ -64,37 +63,28 @@ const props = defineProps({
},
})
const createBatchCourse = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Batch Course',
parent: props.batch,
parenttype: 'LMS Batch',
parentfield: 'courses',
course: course.value,
evaluator: evaluator.value,
},
}
},
})
const addCourse = (close) => {
createBatchCourse.submit(
{},
courses.value.insert.submit(
{
course: course.value,
evaluator: evaluator.value,
parent: props.batch,
parenttype: 'LMS Batch',
parentfield: 'courses',
},
{
onSuccess() {
if (user.data?.is_system_manager)
updateOnboardingStep('add_batch_course')
close()
courses.value.reload()
course.value = null
evaluator.value = null
toast.success(__('Course added to batch successfully'))
},
onError(err) {
toast.error(err.messages?.[0] || err)
console.log(err)
},
}
)
@@ -1,146 +0,0 @@
<template>
<Dialog
v-model="show"
:options="{
size: 'xl',
}"
>
<template #body>
<div class="p-5 space-y-10 text-base">
<div class="flex items-center space-x-2">
<Avatar :image="student.user_image" size="3xl" />
<div class="space-y-1">
<div class="flex items-center space-x-2">
<div class="text-xl font-semibold text-ink-gray-9">
{{ student.full_name }}
</div>
<Badge
v-if="
Object.keys(student.assessments).length ||
Object.keys(student.courses).length
"
:theme="student.progress === 100 ? 'green' : 'red'"
>
{{ student.progress }}% {{ __('Complete') }}
</Badge>
</div>
<div class="text-sm text-ink-gray-7">
{{ student.email }}
</div>
</div>
</div>
<div class="space-y-8">
<!-- Assessments -->
<div
v-if="Object.keys(student.assessments).length"
class="space-y-2 text-sm"
>
<div
class="flex items-center border-b pb-1 font-medium text-ink-gray-9"
>
<span class="flex-1">
{{ __('Assessment') }}
</span>
<span>
{{ __('Percentage/Status') }}
</span>
</div>
<router-link
v-for="assessment in Object.keys(student.assessments)"
class="flex items-center text-ink-gray-7 font-medium"
:to="{
name:
student.assessments[assessment].type == 'LMS Assignment'
? 'AssignmentSubmission'
: '',
params:
student.assessments[assessment].type == 'LMS Assignment'
? {
assignmentID:
student.assessments[assessment].assessment,
submissionName:
student.assessments[assessment].submission,
}
: {},
}"
>
<span class="flex-1">
{{ assessment }}
</span>
<span v-if="isAssignment(student.assessments[assessment].status)">
<Badge
:theme="
getStatusTheme(student.assessments[assessment].status)
"
>
{{ student.assessments[assessment].status }}
</Badge>
</span>
<span v-else>
{{ student.assessments[assessment].status }}
</span>
</router-link>
</div>
<!-- Courses -->
<div
v-if="Object.keys(student.courses).length"
class="space-y-2 text-sm"
>
<div
class="flex items-center border-b pb-1 font-medium text-ink-gray-9"
>
<span class="flex-1">
{{ __('Courses') }}
</span>
<span>
{{ __('Progress') }}
</span>
</div>
<div
v-for="course in Object.keys(student.courses)"
class="flex items-center text-ink-gray-7 font-medium"
>
<span class="flex-1">
{{ course }}
</span>
<span>
{{ Math.floor(student.courses[course]) }}
</span>
</div>
</div>
</div>
<!-- Heatmap -->
<StudentHeatmap :member="student.email" :days="120" />
</div>
</template>
</Dialog>
</template>
<script setup>
import { Avatar, Badge, Dialog } from 'frappe-ui'
import StudentHeatmap from '@/components/StudentHeatmap.vue'
const show = defineModel()
const props = defineProps({
student: {
type: Object,
default: null,
},
})
const isAssignment = (value) => {
return isNaN(value)
}
const getStatusTheme = (status) => {
if (status === 'Pass') {
return 'green'
} else if (status == 'Not Graded') {
return 'orange'
} else {
return 'red'
}
}
</script>
@@ -80,13 +80,13 @@ import {
} from 'frappe-ui'
import { reactive, watch, inject } from 'vue'
import { getFileSize } from '@/utils/'
import { capture } from '@/telemetry'
import { FileText, X } from 'lucide-vue-next'
import { useOnboarding } from 'frappe-ui/frappe'
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
const show = defineModel()
const outline = defineModel('outline')
const user = inject('$user')
const { capture } = useTelemetry()
const { updateOnboardingStep } = useOnboarding('learning')
const props = defineProps({
@@ -1,231 +0,0 @@
<template>
<Dialog
v-model="show"
:options="{
title: __('Course Progress Summary'),
size: '5xl',
}"
>
<template #body-content>
<div
class="flex flex-col-reverse md:flex-row justify-between md:space-x-10 text-base mt-10"
>
<div class="w-full">
<div class="flex items-center justify-between space-x-5 mb-4">
<FormControl
v-model="searchFilter"
:placeholder="__('Search by Member')"
type="text"
class="w-full"
/>
</div>
<div class="max-h-[70vh] overflow-y-auto">
<ListView
v-if="progressList.loading || progressList.data?.length"
:columns="progressColumns"
:rows="progressList.data"
rowKey="name"
:options="{
selectable: false,
showTooltip: false,
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
>
<ListHeaderItem
:item="item"
v-for="item in progressColumns"
:key="item.key"
>
<template #prefix="{ item }">
<FeatherIcon
:name="item.icon?.toString()"
class="h-4 w-4"
/>
</template>
</ListHeaderItem>
</ListHeader>
<ListRows v-for="row in progressList.data">
<router-link
:to="{
name: 'Profile',
params: { username: row.member_username },
}"
>
<ListRow :row="row">
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
:align="column.align"
>
<template #prefix>
<div v-if="column.key == 'member_name'">
<Avatar
class="flex items-center"
:image="row['member_image']"
:label="item"
size="sm"
/>
</div>
</template>
<div>
{{ row[column.key].toString() }}
</div>
</ListRowItem>
</template>
</ListRow>
</router-link>
</ListRows>
</ListView>
<div
v-if="progressList.data && progressList.hasNextPage"
class="flex justify-center my-5"
>
<Button @click="progressList.next()">
{{ __('Load More') }}
</Button>
</div>
</div>
</div>
<div class="mb-4 self-start w-full space-y-5">
<div
class="flex flex-col md:flex-row items-center space-y-2 md:space-y-0 md:space-x-4"
>
<NumberChart
class="border rounded-md w-full"
:config="{
title: __('Enrollments'),
value: memberCount || 0,
}"
/>
<NumberChart
class="border rounded-md w-full"
:config="{
title: __('Average Progress %'),
value: chartDetails.data?.average_progress || 0,
}"
/>
</div>
<DonutChart
:config="{
data: chartDetails.data?.progress_distribution || [],
title: __('Progress Distribution'),
categoryColumn: 'category',
valueColumn: 'count',
colors: [
theme.colors.red['400'],
theme.colors.amber['400'],
theme.colors.pink['400'],
theme.colors.blue['400'],
theme.colors.green['400'],
],
}"
/>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import {
Avatar,
Button,
createListResource,
createResource,
Dialog,
DonutChart,
FeatherIcon,
FormControl,
ListView,
ListHeader,
ListHeaderItem,
ListRows,
ListRow,
ListRowItem,
NumberChart,
} from 'frappe-ui'
import { computed, ref, watch } from 'vue'
import { theme } from '@/utils/theme'
const show = defineModel<boolean>({ default: false })
const searchFilter = ref<string | null>(null)
type Filters = {
course: string | undefined
member_name?: string[]
}
const props = defineProps<{
courseName?: string
enrollments?: number
}>()
const memberCount = ref<number>(props.enrollments || 0)
const chartDetails = createResource({
url: 'lms.lms.api.get_course_progress_distribution',
params: {
course: props.courseName,
},
auto: true,
})
const progressList = createListResource({
doctype: 'LMS Enrollment',
filters: {
course: props.courseName,
},
fields: [
'name',
'member',
'member_name',
'member_image',
'member_username',
'progress',
],
pageLength: 50,
auto: true,
})
watch([searchFilter], () => {
let filterApplied = false
let filters: Filters = {
course: props.courseName,
}
if (searchFilter.value) {
filters.member_name = ['like', `%${searchFilter.value}%`]
filterApplied = true
}
progressList.update({
filters: filters,
})
progressList.reload(
{},
{
onSuccess(data: any[]) {
memberCount.value = filterApplied ? data.length : props.enrollments || 0
},
}
)
})
const progressColumns = computed(() => {
return [
{
label: __('Member'),
key: 'member_name',
width: '60%',
icon: 'user',
},
{
label: __('Progress'),
key: 'progress',
align: 'right',
icon: 'trending-up',
},
]
})
</script>
@@ -26,7 +26,7 @@
@change="(val) => (topic.reply = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
</div>
@@ -34,17 +34,13 @@
</Dialog>
</template>
<script setup>
import {
Dialog,
FormControl,
TextEditor,
createResource,
toast,
} from 'frappe-ui'
import { call, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { reactive } from 'vue'
import { singularize } from '@/utils'
import { useTelemetry } from 'frappe-ui/frappe'
const topics = defineModel('reloadTopics')
const { capture } = useTelemetry()
const props = defineProps({
title: {
@@ -66,64 +62,50 @@ const topic = reactive({
reply: '',
})
const topicResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Discussion Topic',
reference_doctype: props.doctype,
reference_docname: props.docname,
title: topic.title,
},
}
},
})
const replyResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Discussion Reply',
topic: values.topic,
reply: topic.reply,
},
}
},
})
const submitTopic = (close) => {
topicResource.submit(
{},
{
validate() {
if (!topic.title) {
return 'Title cannot be empty.'
}
if (!topic.reply) {
return 'Reply cannot be empty.'
}
},
onSuccess(data) {
replyResource.submit(
{
topic: data.name,
},
{
onSuccess() {
topic.title = ''
topic.reply = ''
topics.value.reload()
close()
},
}
)
},
onError(err) {
toast.error(err.messages?.[0] || err)
},
}
)
if (!topic.title) {
toast.error(__('Title cannot be empty.'))
return
}
if (!topic.reply) {
toast.error(__('Details cannot be empty.'))
return
}
call('frappe.client.insert', {
doc: {
doctype: 'Discussion Topic',
reference_doctype: props.doctype,
reference_docname: props.docname,
title: topic.title,
},
})
.then((data) => {
createReply(data.name, close)
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const createReply = (topicName, close) => {
call('frappe.client.insert', {
doc: {
doctype: 'Discussion Reply',
topic: topicName,
reply: topic.reply,
},
})
.then((data) => {
topic.title = ''
topic.reply = ''
topics.value.reload()
capture('discussion_topic_created')
close()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
</script>
+113 -132
View File
@@ -1,91 +1,85 @@
<template>
<Dialog
v-model="show"
:options="{
title: __('Edit your profile'),
size: '3xl',
actions: [
{
label: __('Save'),
variant: 'solid',
onClick: (close) => saveProfile(close),
},
],
}"
>
<template #body-header>
<div class="flex items-center justify-between mb-5">
<div class="text-2xl font-semibold leading-6 text-ink-gray-9">
{{ __('Edit Profile') }}
</div>
<div class="space-x-2">
<Badge v-if="isDirty" theme="orange">
{{ __('Not Saved') }}
</Badge>
<div class="pb-5 float-right">
<Button variant="solid" @click="saveProfile()">
{{ __('Save') }}
</Button>
</div>
</div>
</div>
</template>
<template #body-content>
<div class="grid grid-cols-2 gap-5">
<div class="space-y-4">
<!-- <Uploader
v-model="profile.image.file_url"
label="Profile Image"
description="Your profile image to help others recognize you."
/> -->
<div>
<div class="text-xs text-ink-gray-5 mb-1">
{{ __('Profile Image') }}
</div>
<FileUploader
v-if="!profile.image"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template
v-slot="{ file, progress, uploading, openFileSelector }"
>
<div class="mb-4">
<Button @click="openFileSelector" :loading="uploading">
{{
uploading
? `Uploading ${progress}%`
: 'Upload a profile image'
}}
</Button>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img
:src="profile.image.file_url"
class="object-cover h-[50px] w-[50px] rounded-full border-4 border-white object-cover"
/>
<div class="text-base">
<div class="grid grid-cols-2 gap-10">
<div class="space-y-4">
<div class="space-y-4">
<Uploader
v-model="profile.image"
:label="__('Profile Image')"
:required="true"
shape="circle"
/>
<div class="text-base flex flex-col ml-2">
<span>
{{ profile.image.file_name }}
</span>
<span class="text-sm text-ink-gray-4 mt-1">
{{ getFileSize(profile.image.file_size) }}
</span>
</div>
<X
@click="removeImage()"
class="bg-surface-gray-3 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
/>
</div>
<FormControl
v-model="profile.first_name"
:label="__('First Name')"
/>
<FormControl
v-model="profile.last_name"
:label="__('Last Name')"
/>
<FormControl v-model="profile.headline" :label="__('Headline')" />
<FormControl
v-model="profile.linkedin"
:label="__('LinkedIn ID')"
/>
<FormControl v-model="profile.github" :label="__('GitHub ID')" />
<FormControl
v-model="profile.twitter"
:label="__('Twitter ID')"
/>
</div>
</div>
<FormControl v-model="profile.first_name" :label="__('First Name')" />
<FormControl v-model="profile.last_name" :label="__('Last Name')" />
<FormControl v-model="profile.headline" :label="__('Headline')" />
<Link
:label="__('Language')"
v-model="profile.language"
doctype="Language"
/>
</div>
<div>
<div class="mb-4">
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Bio') }}
</div>
<TextEditor
:fixedMenu="true"
@change="(val) => (profile.bio = val)"
:content="profile.bio"
editorClass="prose-sm py-2 px-2 min-h-[200px] border-outline-gray-2 hover:border-outline-gray-3 rounded-b-md bg-surface-gray-3"
<div class="space-y-4">
<FormControl
v-model="profile.open_to"
type="select"
:options="[' ', 'Work', 'Hiring']"
:label="__('Open to')"
:placeholder="__('Looking for new work or hiring talent?')"
/>
<Link
:label="__('Language')"
v-model="profile.language"
doctype="Language"
/>
<div>
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Bio') }}
</div>
<TextEditor
:fixedMenu="true"
@change="(val) => (profile.bio = val)"
:content="profile.bio"
:rows="15"
editorClass="prose-sm py-2 px-2 min-h-[280px] border-outline-gray-2 hover:border-outline-gray-3 rounded-b-md bg-surface-gray-3"
/>
</div>
</div>
</div>
</div>
@@ -94,22 +88,22 @@
</template>
<script setup>
import {
Dialog,
FormControl,
FileUploader,
Badge,
Button,
createResource,
Dialog,
FormControl,
TextEditor,
toast,
} from 'frappe-ui'
import { ref, reactive, watch } from 'vue'
import { X } from 'lucide-vue-next'
import { getFileSize, decodeEntities } from '@/utils'
import { sanitizeHTML } from '@/utils'
import Link from '@/components/Controls/Link.vue'
import DOMPurify from 'dompurify'
const show = defineModel()
const reloadProfile = defineModel('reloadProfile')
const hasLanguageChanged = ref(false)
const isDirty = ref(false)
const props = defineProps({
profile: {
@@ -124,19 +118,10 @@ const profile = reactive({
headline: '',
bio: '',
image: '',
})
const imageResource = createResource({
url: 'lms.lms.api.get_file_info',
makeParams(values) {
return {
file_url: values.image,
}
},
auto: false,
onSuccess(data) {
profile.image = data
},
open_to: '',
linkedin: '',
github: '',
twitter: '',
})
const updateProfile = createResource({
@@ -146,7 +131,7 @@ const updateProfile = createResource({
doctype: 'User',
name: props.profile.data.name,
fieldname: {
user_image: profile.image.file_url,
user_image: profile.image || null,
...profile,
},
}
@@ -156,28 +141,13 @@ const updateProfile = createResource({
},
})
const saveProfile = (close) => {
profile.bio = DOMPurify.sanitize(decodeEntities(profile.bio), {
ALLOWED_TAGS: [
'b',
'i',
'em',
'strong',
'a',
'p',
'br',
'ul',
'ol',
'li',
'img',
],
ALLOWED_ATTR: ['href', 'target', 'src'],
})
const saveProfile = () => {
profile.bio = sanitizeHTML(profile.bio)
updateProfile.submit(
{},
{
onSuccess() {
close()
show.value = false
reloadProfile.value.reload()
if (hasLanguageChanged.value) {
hasLanguageChanged.value = false
@@ -191,20 +161,26 @@ const saveProfile = (close) => {
)
}
const validateFile = (file) => {
let extension = file.name.split('.').pop().toLowerCase()
if (!['jpg', 'jpeg', 'png'].includes(extension)) {
return 'Only image file is allowed.'
}
}
const saveImage = (file) => {
profile.image = file
}
const removeImage = () => {
profile.image = null
}
watch(
() => profile,
(newVal) => {
if (!props.profile.data) return
let keys = Object.keys(newVal)
keys.splice(keys.indexOf('image'), 1)
for (let key of keys) {
if (newVal[key] !== props.profile.data[key]) {
isDirty.value = true
return
}
}
if (profile.image !== props.profile.data.user_image) {
isDirty.value = true
return
}
isDirty.value = false
},
{ deep: true }
)
watch(
() => props.profile.data,
@@ -215,15 +191,20 @@ watch(
profile.headline = newVal.headline
profile.language = newVal.language
profile.bio = newVal.bio
if (newVal.user_image) imageResource.submit({ image: newVal.user_image })
profile.open_to = newVal.open_to
profile.linkedin = newVal.linkedin
profile.github = newVal.github
profile.twitter = newVal.twitter
profile.image = newVal.user_image
isDirty.value = false
}
}
)
watch(
() => profile.language,
(newVal, oldVal) => {
if (newVal !== oldVal) {
() => {
if (profile.language !== props.profile.data.language) {
hasLanguageChanged.value = true
}
}
@@ -67,7 +67,7 @@
'Dear {{ member_name }},\n\nYou have been enrolled in our upcoming batch {{ batch_name }}.\n\nThanks,\nFrappe Learning'
)
"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
/>
</div>
</div>
@@ -2,7 +2,7 @@
<Dialog
v-model="show"
:options="{
title: __('Schedule Evaluation'),
title: __('Schedule your evaluation'),
size: 'xl',
actions: [
{
@@ -14,64 +14,71 @@
}"
>
<template #body-content>
<div class="flex flex-col gap-4">
<div>
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Course') }}
<div class="flex flex-col gap-4 text-base max-h-[60vh]">
<FormControl
v-model="evaluation.course"
type="select"
:label="__('Course')"
:options="getCourses()"
/>
<div v-if="slots.data?.length" class="space-y-4 overflow-y-auto mt-4">
<div class="text-ink-gray-9 font-medium">
{{ __('Available Slots') }}
</div>
<Select v-model="evaluation.course" :options="getCourses()" />
</div>
<div>
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Date') }}
</div>
<FormControl
type="date"
v-model="evaluation.date"
:min="
dayjs()
.add(dayjs.duration({ days: 1 }))
.format('YYYY-MM-DD')
"
/>
</div>
<div v-if="slots.data?.length">
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Select a slot') }}
</div>
<div class="grid grid-cols-2 gap-2">
<div v-for="slot in slots.data">
<div
class="text-base text-center border rounded-md text-ink-gray-8 bg-surface-gray-3 p-2 cursor-pointer"
@click="saveSlot(slot)"
:class="{
'border-outline-gray-4':
evaluation.start_time == slot.start_time,
}"
>
{{ formatTime(slot.start_time) }} -
{{ formatTime(slot.end_time) }}
<div class="space-y-5">
<div v-for="row in slots.data" class="space-y-2">
<div class="flex items-center text-ink-gray-7 space-x-2">
<Calendar class="size-3" />
<div class="text-ink-gray-9">
{{ dayjs(row.date).format('DD MMMM YYYY') }}
</div>
<div>&middot;</div>
<div class="text-ink-gray-5">
{{ row.day }}
</div>
</div>
<div class="grid grid-cols-3 gap-2">
<div
v-for="slot in row.slots"
class="text-base text-center border rounded-md text-ink-gray-8 p-2 cursor-pointer text-ink-gray-7 hover:bg-surface-gray-2 hover:border-outline-gray-3"
@click="saveSlot(slot, row)"
:class="{
'border-outline-gray-4 text-ink-gray-9':
evaluation.date == row.date &&
evaluation.start_time == slot.start_time,
}"
>
{{ formatTime(slot.start_time) }} -
{{ formatTime(slot.end_time) }}
</div>
</div>
</div>
</div>
</div>
<div
v-else-if="evaluation.course && evaluation.date"
class="text-sm italic text-ink-red-4"
>
{{ __('No slots available for this date.') }}
<div v-else-if="!evaluation.course" class="text-ink-gray-7">
{{ __('Please select a course to view available slots.') }}
</div>
<div v-else class="text-ink-red-3">
{{ __('No slots available for the selected course.') }}
</div>
</div>
</template>
</Dialog>
</template>
<script setup>
import { Dialog, createResource, Select, FormControl, toast } from 'frappe-ui'
import { reactive, watch, inject } from 'vue'
import {
call,
createResource,
dayjs,
Dialog,
FormControl,
toast,
} from 'frappe-ui'
import { ref, watch, inject } from 'vue'
import { Calendar } from 'lucide-vue-next'
import { formatTime } from '@/utils/'
const user = inject('$user')
const dayjs = inject('$dayjs')
const show = defineModel()
const evaluations = defineModel('reloadEvals')
@@ -90,7 +97,7 @@ const props = defineProps({
},
})
const evaluation = reactive({
const evaluation = ref({
course: '',
date: '',
start_time: '',
@@ -100,48 +107,28 @@ const evaluation = reactive({
member: user.data.name,
})
const createEvaluation = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'LMS Certificate Request',
batch_name: values.batch,
...values,
},
}
},
})
function submitEvaluation(close) {
createEvaluation.submit(evaluation, {
validate() {
if (!evaluation.course) {
return 'Please select a course.'
}
if (!evaluation.date) {
return 'Please select a date.'
}
if (!evaluation.start_time) {
return 'Please select a slot.'
}
if (dayjs(evaluation.date).isBefore(dayjs(), 'day')) {
return 'Please select a future date.'
}
if (dayjs(evaluation.date).isAfter(dayjs(props.endDate), 'day')) {
return `Please select a date before the end date ${dayjs(
props.endDate
).format('DD MMMM YYYY')}.`
}
},
onSuccess() {
evaluations.value.reload()
close()
},
onError(err) {
toast.warning(__(err.messages?.[0] || err))
if (!evaluation.value.date || !evaluation.value.start_time) {
toast.warning(__('Please select a slot for your evaluation.'), {
duration: 10,
})
return
}
call('frappe.client.insert', {
doc: {
doctype: 'LMS Certificate Request',
batch_name: evaluation.value.batch,
...evaluation.value,
},
})
.then(() => {
evaluations.value.reload()
close()
})
.catch((err) => {
console.log(err.messages?.[0] || err)
toast.warning(__(err.messages?.[0] || err), { duration: 20 })
})
}
const getCourses = () => {
@@ -156,7 +143,7 @@ const getCourses = () => {
}
if (courses.length === 1) {
evaluation.course = courses[0].value
evaluation.value.course = courses[0].value
}
return courses
@@ -167,34 +154,22 @@ const slots = createResource({
makeParams(values) {
return {
course: values.course,
date: values.date,
batch: props.batch,
}
},
})
watch(
() => evaluation.date,
(date) => {
evaluation.start_time = ''
if (date && evaluation.course) {
slots.submit(evaluation)
}
}
)
watch(
() => evaluation.course,
() => evaluation.value.course,
(course) => {
evaluation.date = ''
evaluation.start_time = ''
slots.reset()
slots.reload(evaluation.value)
}
)
const saveSlot = (slot) => {
evaluation.start_time = slot.start_time
evaluation.end_time = slot.end_time
evaluation.day = slot.day
const saveSlot = (slot, row) => {
evaluation.value.start_time = slot.start_time
evaluation.value.end_time = slot.end_time
evaluation.value.date = row.date
evaluation.value.day = row.day
}
</script>
+21 -6
View File
@@ -22,7 +22,10 @@
</div>
</Tooltip>
<Tooltip :text="__('Course')">
<div class="flex items-center space-x-2 w-fit">
<div
class="flex space-x-2 w-fit cursor-pointer"
@click="openLink('course', event.course)"
>
<BookOpen class="h-4 w-4 stroke-1.5" />
<span>
{{ event.course_title }}
@@ -30,7 +33,10 @@
</div>
</Tooltip>
<Tooltip v-if="event.batch_title" :text="__('Batch')">
<div class="flex items-center space-x-2 w-fit">
<div
class="flex space-x-2 w-fit cursor-pointer"
@click="openLink('batch', event.batch_name)"
>
<Users class="h-4 w-4 stroke-1.5" />
<span>
{{ event.batch_title }}
@@ -189,6 +195,8 @@ const user = inject('$user')
const dayjs = inject('$dayjs')
const tabIndex = ref(0)
const showCertification = ref(false)
const evaluation = reactive({})
const certificate = reactive({})
const props = defineProps({
event: {
@@ -197,9 +205,6 @@ const props = defineProps({
},
})
const evaluation = reactive({})
const certificate = reactive({})
watch(user, () => {
if (userIsEvaluator()) {
defaultTemplate.reload()
@@ -335,7 +340,7 @@ const certificateDetails = createResource({
}
},
onError(err) {
certificate.template = defaultTemplate.data.value
certificate.template = defaultTemplate.data?.value
},
auto: false,
})
@@ -378,6 +383,16 @@ const openCertificate = (certificate) => {
)
}
const openLink = (type, name) => {
let url = ''
if (type === 'course') {
url = `/lms/courses/${name}`
} else if (type === 'batch') {
url = `/lms/batches/${name}#students`
}
window.open(url, '_blank')
}
const statusOptions = computed(() => {
return [
{
@@ -7,7 +7,7 @@
>
<template #body>
<div class="p-5 min-h-[300px]">
<div class="text-lg font-semibold mb-4">
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
{{ __('Training Feedback') }}
</div>
<ListView
@@ -17,7 +17,7 @@
}"
>
<template #body-content>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-4 text-base">
<p class="text-ink-gray-9">
{{
__(
@@ -29,6 +29,7 @@
<FileUploader
:fileTypes="['.pdf']"
:validateFile="validateFile"
:uploadArgs="{ private: 1 }"
@success="
(file) => {
resume = file
@@ -38,6 +39,9 @@
<template v-slot="{ file, progress, uploading, openFileSelector }">
<div class="">
<Button @click="openFileSelector" :loading="uploading">
<template #prefix>
<Upload class="size-4 stroke-1.5" />
</template>
{{
uploading ? `Uploading ${progress}%` : 'Upload your resume'
}}
@@ -65,7 +69,7 @@
</template>
<script setup>
import { Dialog, FileUploader, Button, createResource, toast } from 'frappe-ui'
import { FileText } from 'lucide-vue-next'
import { FileText, Upload } from 'lucide-vue-next'
import { ref, inject } from 'vue'
import { getFileSize } from '@/utils/'
@@ -95,7 +99,7 @@ const jobApplication = createResource({
doc: {
doctype: 'LMS Job Application',
user: user.data?.name,
resume: resume.value?.file_name,
resume: resume.value?.file_url,
job: props.job,
},
}
@@ -84,16 +84,10 @@
</Dialog>
</template>
<script setup>
import {
Dialog,
createResource,
Tooltip,
FormControl,
Autocomplete,
toast,
} from 'frappe-ui'
import { Dialog, createResource, Tooltip, FormControl, toast } from 'frappe-ui'
import { reactive, inject, onMounted } from 'vue'
import { getTimezones, getUserTimezone } from '@/utils/'
import Autocomplete from '@/components/Controls/Autocomplete.vue'
const liveClasses = defineModel('reloadLiveClasses')
const show = defineModel()
+11 -10
View File
@@ -1,7 +1,6 @@
<template>
<Dialog
v-model="show"
class="text-base"
:options="{
title: __('Add web page to sidebar'),
size: 'lg',
@@ -17,15 +16,17 @@
}"
>
<template #body-content>
<Link
v-model="page.webpage"
doctype="Web Page"
:label="__('Web Page')"
:filters="{
published: 1,
}"
/>
<IconPicker v-model="page.icon" :label="__('Icon')" class="mt-4" />
<div class="text-base">
<Link
v-model="page.webpage"
doctype="Web Page"
:label="__('Web Page')"
:filters="{
published: 1,
}"
/>
<IconPicker v-model="page.icon" :label="__('Icon')" class="mt-4" />
</div>
</template>
</Dialog>
</template>
+1 -1
View File
@@ -31,7 +31,7 @@
@change="(val) => (question.question = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
<div class="grid grid-cols-2 gap-8 mt-4">
+38 -28
View File
@@ -2,8 +2,8 @@
<Dialog
v-model="show"
:options="{
title: __('Add a Student'),
size: 'sm',
title: __('Enroll a Student'),
size: 'lg',
actions: [
{
label: 'Submit',
@@ -18,10 +18,25 @@
<Link
doctype="User"
v-model="student"
:filters="{ ignore_user_type: 1 }"
placeholder=" "
:label="__('Student')"
:onCreate="
(value, close) => {
openSettings('Members', close)
() => {
openSettings('Members')
show = false
}
"
:required="true"
/>
<Link
doctype="LMS Payment"
v-model="payment"
placeholder=" "
:label="__('Payment')"
:onCreate="
() => {
openSettings('Transactions')
show = false
}
"
/>
@@ -30,54 +45,49 @@
</Dialog>
</template>
<script setup>
import { Dialog, createResource, toast } from 'frappe-ui'
import { call, Dialog, toast } from 'frappe-ui'
import { ref, inject } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { useOnboarding } from 'frappe-ui/frappe'
import { openSettings } from '@/utils'
import Link from '@/components/Controls/Link.vue'
const students = defineModel('reloadStudents')
const batchModal = defineModel('batchModal')
const student = ref()
const student = ref(null)
const payment = ref(null)
const user = inject('$user')
const { updateOnboardingStep } = useOnboarding('learning')
const show = defineModel()
const props = defineProps({
batch: {
type: String,
type: Object,
default: null,
},
students: {
type: Object,
default: null,
},
})
const studentResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'LMS Batch Enrollment',
batch: props.batch,
member: student.value,
},
}
},
})
const addStudent = (close) => {
studentResource.submit(
{},
props.students.insert.submit(
{
member: student.value,
payment: payment.value,
batch: props.batch.data?.name,
},
{
onSuccess() {
if (user.data?.is_system_manager)
updateOnboardingStep('add_batch_student')
students.value.reload()
batchModal.value.reload()
student.value = null
payment.value = null
props.batch.reload()
close()
},
onError(err) {
toast.error(err.messages?.[0] || err)
console.error(err)
},
}
)

Some files were not shown because too many files have changed in this diff Show More