Compare commits

..

848 Commits

Author SHA1 Message Date
Aradhya-Tripathi d6775e7279 chore(path): Use path relative from root project dir 2026-02-03 15:35:01 +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
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 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 7a47591967 Merge pull request #1819 from rehanrehman389/misc-fix
fix: UI improvements
2025-11-06 16:43:28 +05:30
Jannat Patel 6931ca27c3 Merge pull request #1824 from pateljannat/issues-145
fix: roles, permission and access on profile page
2025-11-06 12:51:45 +05:30
Jannat Patel d00d2de1cc fix: export livecodeURL from settings store 2025-11-06 12:28:27 +05:30
Jannat Patel b1be568991 fix: removed uncalled function 2025-11-06 12:23:18 +05:30
Jannat Patel 28be3891d2 fix: roles, permission and access on profile page 2025-11-06 12:21:12 +05:30
Jannat Patel 27d2297e2b Merge pull request #1823 from pateljannat/issues-144
fix: misc improvements
2025-11-05 12:50:21 +05:30
Jannat Patel 7212ddd5c5 fix: evaluators and modetators can now see schedule of other evaluators 2025-11-05 12:34:40 +05:30
Jannat Patel f4e9ac5bf1 fix: IPhone PWA install prompt 2025-11-05 11:59:45 +05:30
Jannat Patel 18e499e6de fix: coupon code application on billing page 2025-11-04 15:12:17 +05:30
Jannat Patel 8fec484d66 Merge pull request #1818 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-04 13:14:49 +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
Jannat Patel bcf781c37b chore: Serbian (Latin) translations 2025-11-03 00:37:46 +05:30
Jannat Patel d8a8e689d0 chore: Bosnian translations 2025-11-03 00:37:45 +05:30
Jannat Patel a844b95de3 chore: Burmese translations 2025-11-03 00:37:44 +05:30
Jannat Patel ece885f973 chore: Thai translations 2025-11-03 00:37:42 +05:30
Jannat Patel 66dd30604b chore: Tamil translations 2025-11-03 00:37:41 +05:30
Jannat Patel d0f0f4905c chore: Indonesian translations 2025-11-03 00:37:40 +05:30
Jannat Patel c9cb6702b6 chore: Portuguese, Brazilian translations 2025-11-03 00:37:39 +05:30
Jannat Patel 1ddb980242 chore: Vietnamese translations 2025-11-03 00:37:37 +05:30
Jannat Patel 94b626a4d2 chore: Chinese Simplified translations 2025-11-03 00:37:36 +05:30
Jannat Patel d2a011462d chore: Turkish translations 2025-11-03 00:37:35 +05:30
Jannat Patel 4c34926af0 chore: Serbian (Cyrillic) translations 2025-11-03 00:37:34 +05:30
Jannat Patel ce35cd1009 chore: Russian translations 2025-11-03 00:37:32 +05:30
Jannat Patel 56d072bd06 chore: Portuguese translations 2025-11-03 00:37:31 +05:30
Jannat Patel 5d336ef669 chore: Polish translations 2025-11-03 00:37:30 +05:30
Jannat Patel b47c59eac1 chore: Dutch translations 2025-11-03 00:37:29 +05:30
Jannat Patel 87285db361 chore: Italian translations 2025-11-03 00:37:27 +05:30
Jannat Patel 84312e498c chore: Hungarian translations 2025-11-03 00:37:26 +05:30
Jannat Patel bd763d9462 chore: German translations 2025-11-03 00:37:25 +05:30
Jannat Patel a00e66f786 chore: Czech translations 2025-11-03 00:37:24 +05:30
Jannat Patel 78c7b52088 chore: Arabic translations 2025-11-03 00:37:23 +05:30
Jannat Patel c3a5bee993 chore: Spanish translations 2025-11-03 00:37:21 +05:30
Jannat Patel c2b5b7c3e2 chore: French translations 2025-11-03 00:37:20 +05:30
Jannat Patel 3992f00353 chore: Persian translations 2025-11-03 00:37:19 +05:30
Jannat Patel 97d853e0d3 chore: Danish translations 2025-11-03 00:37:18 +05:30
Jannat Patel f786cec75f chore: Norwegian Bokmal translations 2025-11-03 00:37:16 +05:30
Jannat Patel 07cd08b55e chore: Croatian translations 2025-11-03 00:37:15 +05:30
Jannat Patel ca42faf14a chore: Swedish translations 2025-11-03 00:37:14 +05:30
Rehan Ansari 87f5b68279 fix: UI improvements 2025-11-02 13:43:25 +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
Jannat Patel 6b31edb687 chore: Esperanto translations 2025-10-31 23:03:40 +05:30
Jannat Patel 6a64048bb6 Merge pull request #1817 from frappe/pot_develop_2025-10-31
chore: update POT file
2025-10-31 22:03:56 +05:30
frappe-pr-bot 6cf069ee6a chore: update POT file 2025-10-31 16:04:36 +00:00
Rehan Ansari 74de43c3d6 fix: ensure options reload after updates 2025-10-31 00:10:16 +05:30
Jannat Patel 3b74bba6ab Merge pull request #1813 from rehanrehman389/missing-type-fix
fix: add missing type prop
2025-10-30 11:13:31 +05:30
Rehan Ansari 9f81bf695c fix: use file_url instead of file_name 2025-10-30 01:03:01 +05:30
Rehan Ansari 8689788523 fix: add missing type prop 2025-10-29 21:41:48 +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
372 changed files with 58122 additions and 40411 deletions
+33 -5
View File
@@ -7,6 +7,8 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
services:
redis-cache:
image: redis:alpine
@@ -30,13 +32,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 +71,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 +82,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
@@ -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
+4 -4
View File
@@ -36,9 +36,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 +48,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
+1
View File
@@ -12,4 +12,5 @@ node_modules
package-lock.json
lms/public/frontend
lms/www/lms.html
lms/www/_lms.html
frappe-ui
+1 -1
View File
@@ -1,5 +1,5 @@
{
"branches": ["develop"],
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular"
+6
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,6 +52,7 @@ 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");
@@ -155,6 +160,7 @@ describe("Batch Creation", () => {
cy.get("button:visible").contains("Manage Batch").click();
/* Add student to batch */
cy.get("button").contains("Students").click();
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);
+25 -20
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,6 +34,29 @@ describe("Course Creation", () => {
});
});
/* 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");
});
cy.get("@instructor_list_id").then((instructor_list_id) => {
cy.get(`[id^=${instructor_list_id}`)
.should("be.visible")
.within(() => {
cy.get("[id^=headlessui-combobox-option-").first().click();
});
});
cy.button("Create").last().click();
// Edit Course Details
cy.wait(500);
cy.get("label")
.contains("Preview Video")
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
@@ -49,31 +72,13 @@ describe("Course Creation", () => {
.first()
.click();
/* Instructor */
cy.get("label")
.contains("Instructors")
.parent()
.within(() => {
cy.get("input").click().type("frappe");
cy.get("input")
.invoke("attr", "aria-controls")
.as("instructor_list_id");
});
cy.get("@instructor_list_id").then((instructor_list_id) => {
cy.get(`[id^=${instructor_list_id}`)
.should("be.visible")
.within(() => {
cy.get("[id^=headlessui-combobox-option-").first().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
}
+16 -9
View File
@@ -8,11 +8,11 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AdminBatchDashboard: typeof import('./src/components/AdminBatchDashboard.vue')['default']
Annoucements: typeof import('./src/components/Annoucements.vue')['default']
AnnouncementModal: typeof import('./src/components/Modals/AnnouncementModal.vue')['default']
AppHeader: typeof import('./src/components/AppHeader.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']
@@ -42,12 +42,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']
@@ -73,7 +79,6 @@ declare module 'vue' {
InviteIcon: typeof import('./src/components/Icons/InviteIcon.vue')['default']
JobApplicationModal: typeof import('./src/components/Modals/JobApplicationModal.vue')['default']
JobCard: typeof import('./src/components/JobCard.vue')['default']
LayoutHeader: typeof import('./src/components/LayoutHeader.vue')['default']
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']
@@ -88,6 +93,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']
@@ -105,18 +111,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": "0.15.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");
}
@@ -0,0 +1,118 @@
<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: studentCount.data || 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.data || 0 }"
/>
</div>
<AxisChart
v-if="showProgressChart"
class="border"
:config="{
data: filteredChartData,
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>
</template>
<script setup lang="ts">
import { AxisChart, createResource, NumberChart } from 'frappe-ui'
import { computed } from 'vue'
const props = defineProps<{
batch: { [key: string]: any } | null
}>()
const studentCount = createResource({
url: 'frappe.client.get_count',
cache: ['batch_student_count', props.batch?.data?.name],
params: {
doctype: 'LMS Batch Enrollment',
filters: { batch: props.batch?.data?.name },
},
auto: true,
})
const assessmentCount = createResource({
url: 'lms.lms.utils.get_batch_assessment_count',
cache: ['batch_assessment_count', props.batch?.data?.name],
params: {
batch: props.batch?.data?.name,
},
auto: true,
})
const chartData = createResource({
url: 'lms.lms.utils.get_batch_chart_data',
cache: ['batch_chart_data', props.batch?.data?.name],
params: { batch: props.batch?.data?.name },
auto: true,
})
const certificationCount = createResource({
url: 'frappe.client.get_count',
cache: ['batch_certificate_count', props.batch?.data?.name],
params: {
doctype: 'LMS Certificate',
filters: { batch_name: props.batch?.data?.name },
},
auto: true,
})
const filteredChartData = computed(() =>
(chartData.data || []).filter((item: { value: number }) => item.value > 0)
)
const showProgressChart = computed(
() =>
studentCount.data &&
(props.batch?.data?.courses?.length || assessmentCount.data)
)
</script>
+39 -12
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 Link from '@/components/Controls/Link.vue'
import { Dialog, FormControl } from 'frappe-ui'
import { nextTick, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { Link } from 'frappe-ui/frappe'
import { getLmsRoute } from '@/utils/basePath'
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>
+53 -55
View File
@@ -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>
@@ -53,7 +53,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,12 +63,17 @@
}}
{{ __('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="!submissionResource.doc?.assignment_attachment"
:fileTypes="getType()"
:uploadArgs="{
private: true,
@@ -87,21 +92,24 @@
</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="submissionResource.doc.assignment_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>
{{
submissionResource.doc.assignment_attachment
.split('/')
.pop()
}}
</span>
</div>
</a>
<X
v-if="canModifyAssignment"
@@ -142,13 +150,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"
>
<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,6 +187,9 @@
"
:editable="true"
:fixedMenu="true"
: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]"
/>
</div>
@@ -201,10 +212,8 @@ 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'
const submissionFile = ref(null)
const answer = ref(null)
const comments = ref(null)
const router = useRouter()
@@ -263,9 +272,7 @@ const newSubmission = createResource({
assignment: props.assignmentID,
member: user.data?.name,
}
if (showUploader()) {
doc.assignment_attachment = submissionFile.value.file_url
} else {
if (!showUploader()) {
doc.answer = answer.value
}
return {
@@ -274,19 +281,6 @@ const newSubmission = createResource({
},
})
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,
@@ -299,11 +293,6 @@ const submissionResource = createDocumentResource({
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
}
@@ -312,7 +301,10 @@ watch(submissionResource, () => {
}
if (submissionResource.isDirty) {
isDirty.value = true
} else if (showUploader() && !submissionFile.value) {
} else if (
showUploader() &&
!submissionResource.doc.assignment_attachment
) {
isDirty.value = true
} else if (!showUploader() && !answer.value) {
isDirty.value = true
@@ -322,11 +314,17 @@ watch(submissionResource, () => {
}
})
watch(submissionFile, () => {
if (props.submissionName == 'new' && submissionFile.value) {
isDirty.value = true
watch(
() => submissionResource.doc,
() => {
if (
props.submissionName == 'new' &&
submissionResource.doc?.assignment_attachment
) {
isDirty.value = true
}
}
})
)
const submitAssignment = () => {
if (props.submissionName != 'new') {
@@ -338,13 +336,13 @@ const submitAssignment = () => {
submissionResource.setValue.submit(
{
...submissionResource.doc,
assignment_attachment: submissionFile.value?.file_url,
evaluator: evaluator,
comments: comments.value,
answer: answer.value,
},
{
onSuccess(data) {
isDirty.value = false
toast.success(__('Changes saved successfully'))
},
}
@@ -385,7 +383,7 @@ const addNewSubmission = () => {
const saveSubmission = (file) => {
isDirty.value = true
submissionFile.value = file
submissionResource.doc.assignment_attachment = file.file_url
}
const markLessonProgress = () => {
@@ -436,7 +434,7 @@ const validateFile = (file) => {
const removeSubmission = () => {
isDirty.value = true
submissionFile.value = null
submissionResource.doc.assignment_attachment = ''
}
const canGradeSubmission = computed(() => {
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<div>
<div class="flex items-center justify-between mb-4">
<div class="text-lg font-semibold text-ink-gray-9">
<div class="font-medium text-ink-gray-9">
{{ __('Courses') }}
</div>
<Button v-if="canSeeAddButton()" @click="openCourseModal()">
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<div v-if="user.data?.is_student">
<div>
<div class="leading-5 mb-4">
<div class="leading-5 mb-4 text-ink-gray-7">
<div v-if="readOnly">
{{ __('Thank you for providing your feedback.') }}
<span
+25 -14
View File
@@ -1,7 +1,7 @@
<template>
<div v-if="batch.data" class="border-2 rounded-md p-5 lg:w-72">
<div
v-if="batch.data.seat_count && seats_left > 0"
v-if="batch.data.seat_count && batch.data.seats_left > 0"
class="text-sm bg-green-100 text-green-700 px-2 py-1 rounded-md"
:class="
batch.data.amount || batch.data.courses.length
@@ -9,16 +9,16 @@
: 'w-fit mb-4'
"
>
{{ seats_left }}
<span v-if="seats_left > 1">
{{ batch.data.seats_left }}
<span v-if="batch.data.seats_left > 1">
{{ __('Seats Left') }}
</span>
<span v-else-if="seats_left == 1">
<span v-else-if="batch.data.seats_left == 1">
{{ __('Seat Left') }}
</span>
</div>
<div
v-else-if="batch.data.seat_count && seats_left <= 0"
v-else-if="batch.data.seat_count && batch.data.seats_left <= 0"
class="text-xs bg-red-100 text-red-700 float-right px-2 py-0.5 rounded-md"
>
{{ __('Sold Out') }}
@@ -54,6 +54,7 @@
{{ batch.data.timezone }}
</span>
</div>
<div v-if="!readOnlyMode">
<router-link
v-if="canAccessBatch"
@@ -113,7 +114,7 @@
{{ __('Enroll Now') }}
</Button>
<router-link
v-if="isModerator"
v-if="canEditBatch"
:to="{
name: 'BatchForm',
params: {
@@ -190,15 +191,10 @@ const enrollInBatch = () => {
)
}
const seats_left = computed(() => {
if (props.batch.data?.seat_count) {
return props.batch.data?.seat_count - props.batch.data?.students?.length
}
return null
})
const isStudent = computed(() => {
return props.batch.data?.students?.includes(user.data?.name)
return user.data
? props.batch.data?.students?.includes(user.data?.name)
: false
})
const isModerator = computed(() => {
@@ -209,7 +205,22 @@ const isEvaluator = computed(() => {
return user.data?.is_evaluator
})
const isInstructor = computed(() => {
return (
props.batch.data?.instructors?.filter(
(instructor) => instructor.name === user.data?.name
).length > 0
)
})
const canAccessBatch = computed(() => {
if (!user.data) {
return false
}
return isModerator.value || isStudent.value || isEvaluator.value
})
const canEditBatch = computed(() => {
return isModerator.value || isInstructor.value
})
</script>
+52 -180
View File
@@ -1,70 +1,8 @@
<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 class="text-ink-gray-9 font-medium">
{{ studentCount.data ?? 0 }} {{ __('Students') }}
</div>
<Button v-if="!readOnlyMode" @click="openStudentModal()">
<template #prefix>
@@ -76,7 +14,8 @@
<div v-if="students.data?.length">
<ListView
:columns="getStudentColumns()"
class="max-h-[75vh]"
:columns="studentColumns"
:rows="students.data"
row-key="name"
:options="{
@@ -88,7 +27,7 @@
>
<ListHeaderItem
:item="item"
v-for="item in getStudentColumns()"
v-for="item in studentColumns"
:title="item.label"
>
<template #prefix="{ item }">
@@ -104,7 +43,7 @@
<ListRow
:row="row"
v-for="row in students.data"
class="group cursor-pointer"
class="group cursor-pointer hover:bg-surface-gray-2 rounded"
@click="openStudentProgressModal(row)"
>
<template #default="{ column, item }">
@@ -149,9 +88,14 @@
</div>
</template>
</ListSelectBanner>
<div class="mt-4 flex justify-center" v-if="students.hasNextPage">
<Button @click="students.next()">
{{ __('Load More') }}
</Button>
</div>
</ListView>
</div>
<div v-else class="text-sm italic text-ink-gray-5">
<div v-else-if="!students.loading" class="text-sm italic text-ink-gray-5">
{{ __('There are no students in this batch.') }}
</div>
</div>
@@ -170,8 +114,8 @@
<script setup>
import {
Avatar,
AxisChart,
Button,
createListResource,
createResource,
FeatherIcon,
ListHeader,
@@ -181,30 +125,17 @@ import {
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 { Plus, Trash2 } from 'lucide-vue-next'
import { ref } 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({
@@ -214,45 +145,48 @@ const props = defineProps({
},
})
const students = createResource({
url: 'lms.lms.utils.get_batch_students',
const studentCount = createResource({
url: 'frappe.client.get_count',
cache: ['batch_student_count', props.batch?.data?.name],
params: {
doctype: 'LMS Batch Enrollment',
filters: { batch: props.batch?.data?.name },
},
auto: true,
})
const students = createListResource({
doctype: 'LMS Batch Enrollment',
url: 'lms.lms.utils.get_batch_students',
cache: ['batch_students', props.batch?.data?.name],
pageLength: 50,
filters: {
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 studentColumns = [
{
label: 'Full Name',
key: 'full_name',
width: '25rem',
icon: 'user',
},
{
label: 'Progress',
key: 'progress',
width: '15rem',
icon: 'activity',
},
{
label: 'Last Active',
key: 'last_active',
width: '10rem',
align: 'center',
icon: 'clock',
},
]
const openStudentModal = () => {
showStudentModal.value = true
@@ -281,6 +215,7 @@ const removeStudents = (selections, unselectAll) => {
{
onSuccess(data) {
students.reload()
studentCount.reload()
props.batch.reload()
toast.success(__('Students deleted successfully'))
unselectAll()
@@ -288,67 +223,4 @@ const removeStudents = (selections, 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,
@@ -19,10 +19,10 @@
@click="() => 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) }}
+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 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"
: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 border-none focus:ring-0 focus:border focus:border-gray-300 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-white border border-outline-gray-1 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">
+12
View File
@@ -67,6 +67,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: {
@@ -103,6 +104,7 @@ const value = computed({
const autocomplete = ref(null)
const text = ref('')
const settingsStore = useSettings()
watchDebounced(
() => autocomplete.value?.query,
@@ -121,6 +123,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],
@@ -19,21 +19,36 @@
showOptions = true
}
"
@click="
(e) => {
showOptions = true
nextTick(() => {
setFocus()
})
}
"
@focus="
() => {
if (!filterOptions.data || filterOptions.data.length === 0) {
reload('')
}
}
"
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"
class="flex flex-col mt-1 rounded-lg bg-surface-white py-1 text-base border-2 max-h-[13rem]"
>
<ComboboxOptions
class="my-1 min-h-[6rem] max-h-[12rem] overflow-y-auto px-1.5"
class="flex-1 my-1 overflow-y-auto px-1.5"
:class="options.length ? 'min-h-[6rem]' : 'min-h-[3.8rem]'"
static
>
<ComboboxOption
v-if="options.length"
v-for="option in options"
:key="option.value"
:value="option"
@@ -47,7 +62,11 @@
>
<div class="flex flex-col gap-1 p-1">
<div class="text-base font-medium text-ink-gray-8">
{{ option.description }}
{{
option.value == option.label
? option.description
: option.label
}}
</div>
<div class="text-sm text-ink-gray-5">
{{ option.value }}
@@ -55,23 +74,22 @@
</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 v-else class="text-ink-gray-7 px-4">
{{ __('No results found') }}
</div>
</ComboboxOptions>
<div v-if="attrs.onCreate" class="px-1 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>
</div>
</div>
</template>
@@ -105,7 +123,7 @@ import {
} from '@headlessui/vue'
import { createResource, Popover, Button } from 'frappe-ui'
import { ref, computed, nextTick, useAttrs } from 'vue'
import { watchDebounced } from '@vueuse/core'
import { set, watchDebounced } from '@vueuse/core'
import { X, Plus } from 'lucide-vue-next'
const props = defineProps({
@@ -139,21 +157,20 @@ const props = defineProps({
const values = defineModel()
const attrs = useAttrs()
const emails = ref([])
const search = ref(null)
const error = ref(null)
const query = ref('')
const text = ref('')
const showOptions = ref(false)
const emit = defineEmits(['update:modelValue'])
const selectedValue = computed({
get: () => query.value || '',
set: (val) => {
query.value = ''
if (val) {
showOptions.value = false
}
val?.value && addValue(val.value)
showOptions.value = false
emit('update:modelValue', values.value)
},
})
@@ -180,7 +197,9 @@ const filterOptions = createResource({
})
const options = computed(() => {
return filterOptions.data || []
setFocus()
const allOptions = filterOptions.data || []
return allOptions.filter((option) => !values.value?.includes(option.value))
})
function reload(val) {
@@ -223,25 +242,7 @@ const addValue = (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()
}
emit('update:modelValue', values.value)
}
function setFocus() {
+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 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>
+18 -49
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,40 +89,11 @@
</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
class="font-medium text-ink-gray-9"
:class="{ 'mt-8': !readOnlyMode }"
:class="{ 'mt-8': course.data.membership && !readOnlyMode }"
>
{{ __('This course has:') }}
</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>
+9 -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],
},
}"
>
@@ -174,6 +177,7 @@ import {
FilePenLine,
HelpCircle,
MonitorPlay,
Plus,
Trash2,
} from 'lucide-vue-next'
import { useRoute, useRouter } from 'vue-router'
@@ -389,8 +393,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>
+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>
+11 -4
View File
@@ -20,10 +20,10 @@
</template>
</Dialog>
<Popover :show="iosInstallMessage" placement="top">
<Popover :show="iosInstallMessage" placement="top-start">
<template #body>
<div
class="fixed bottom-[4rem] left-1/2 -translate-x-1/2 z-20 w-[90%] flex flex-col gap-3 rounded bg-blue-100 py-5 drop-shadow-xl"
class="fixed top-[20rem] translate-x-1/3 z-20 flex flex-col gap-3 rounded bg-surface-white py-5 drop-shadow-xl"
>
<div
class="mb-1 flex flex-row items-center justify-between px-3 text-center"
@@ -41,7 +41,7 @@
</div>
<div class="px-3 text-xs text-gray-800">
<span class="flex flex-col gap-2">
<span>
<span class="leading-5">
{{
__(
'Get the app on your iPhone for easy access & a better experience'
@@ -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()
+6 -2
View File
@@ -1,7 +1,7 @@
<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"
class="flex items-center space-x-2 mb-5 bg-surface-amber-1 py-1 px-2 rounded-md text-ink-amber-3 text-xs"
>
<AlertCircle class="size-4 stroke-1.5" />
<span>
@@ -107,7 +107,11 @@
v-model:reloadLiveClasses="liveClasses"
/>
<LiveClassAttendance v-model="showAttendance" :live_class="attendanceFor" />
<LiveClassAttendance
v-if="showAttendance"
v-model="showAttendance"
:live_class="attendanceFor"
/>
</template>
<script setup>
import { createListResource, Button, Tooltip } from 'frappe-ui'
+11
View File
@@ -80,6 +80,7 @@ onMounted(() => {
{},
{
onSuccess(data) {
destructureSidebarLinks()
filterLinksToShow(data)
addOtherLinks()
},
@@ -103,6 +104,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])) {
@@ -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') }}
@@ -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 || ''
}
})
}
@@ -113,33 +124,55 @@ watch(
{ flush: 'post' }
)
const saveAssignment = () => {
if (props.assignmentID == 'new') {
assignments.value.insert.submit(
{
...assignment,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment created successfully'))
},
}
)
} else {
assignments.value.setValue.submit(
{
...assignment,
name: props.assignmentID,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment updated successfully'))
},
}
)
watch(show, (newVal) => {
if (newVal && props.assignmentID === 'new') {
assignment.title = ''
assignment.type = ''
assignment.question = ''
}
})
const validateFields = () => {
assignment.title = escapeHTML(assignment.title.trim())
assignment.question = sanitizeHTML(assignment.question)
}
const saveAssignment = () => {
validateFields()
if (props.assignmentID == 'new') {
createAssignment()
} else {
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(() => {
@@ -23,10 +23,8 @@
(value, close) => {
close()
router.push({
name: 'CourseForm',
params: {
courseName: 'new',
},
name: 'Courses',
query: { newCourse: '1' },
})
}
"
@@ -11,7 +11,7 @@
<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">
<div class="text-xl font-semibold text-ink-gray-9">
{{ student.full_name }}
</div>
<Badge
@@ -36,7 +36,9 @@
v-if="Object.keys(student.assessments).length"
class="space-y-2 text-sm"
>
<div class="flex items-center border-b pb-1 font-medium">
<div
class="flex items-center border-b pb-1 font-medium text-ink-gray-9"
>
<span class="flex-1">
{{ __('Assessment') }}
</span>
@@ -86,7 +88,9 @@
v-if="Object.keys(student.courses).length"
class="space-y-2 text-sm"
>
<div class="flex items-center border-b pb-1 font-medium">
<div
class="flex items-center border-b pb-1 font-medium text-ink-gray-9"
>
<span class="flex-1">
{{ __('Courses') }}
</span>
@@ -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>
+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
}
}
@@ -2,7 +2,7 @@
<Dialog
v-model="show"
:options="{
title: __('Schedule Evaluation'),
title: __('Schedule your evaluation'),
size: 'xl',
actions: [
{
@@ -14,64 +14,68 @@
}"
>
<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 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 +94,7 @@ const props = defineProps({
},
})
const evaluation = reactive({
const evaluation = ref({
course: '',
date: '',
start_time: '',
@@ -100,48 +104,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 +140,7 @@ const getCourses = () => {
}
if (courses.length === 1) {
evaluation.course = courses[0].value
evaluation.value.course = courses[0].value
}
return courses
@@ -167,34 +151,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>
+56 -10
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 }}
@@ -66,7 +72,11 @@
</template>
{{ __('View Certificate') }}
</Button>
<Button v-else @click="openCallLink(event.venue)" class="w-full">
<Button
v-else-if="userIsEvaluator()"
@click="openCallLink(event.venue)"
class="w-full"
>
<template #prefix>
<Video class="h-4 w-4 stroke-1.5" />
</template>
@@ -83,21 +93,31 @@
class="flex flex-col space-y-4 p-5"
>
<div class="flex items-center justify-between">
<Rating v-model="evaluation.rating" :label="__('Rating')" />
<Rating
v-model="evaluation.rating"
:label="__('Rating')"
:disabled="!userIsEvaluator()"
/>
<FormControl
type="select"
:options="statusOptions"
v-model="evaluation.status"
:label="__('Status')"
class="w-1/2"
:disabled="!userIsEvaluator()"
/>
</div>
<Textarea
v-model="evaluation.summary"
:label="__('Summary')"
:rows="7"
:disabled="!userIsEvaluator()"
/>
<Button variant="solid" @click="saveEvaluation()">
<Button
v-if="userIsEvaluator()"
variant="solid"
@click="saveEvaluation()"
>
{{ __('Save') }}
</Button>
</div>
@@ -106,11 +126,13 @@
type="checkbox"
v-model="certificate.published"
:label="__('Published')"
:disabled="!userIsEvaluator()"
/>
<Link
v-model="certificate.template"
:label="__('Template')"
doctype="Print Format"
:disabled="!userIsEvaluator()"
:filters="{
doc_type: 'LMS Certificate',
}"
@@ -118,14 +140,20 @@
<FormControl
type="date"
v-model="certificate.issue_date"
:disabled="!userIsEvaluator()"
:label="__('Issue Date')"
/>
<FormControl
type="date"
v-model="certificate.expiry_date"
:disabled="!userIsEvaluator()"
:label="__('Expiry Date')"
/>
<Button variant="solid" @click="saveCertificate()">
<Button
v-if="userIsEvaluator()"
variant="solid"
@click="saveCertificate()"
>
{{ __('Save') }}
</Button>
</div>
@@ -163,9 +191,12 @@ import Rating from '@/components/Controls/Rating.vue'
import Link from '@/components/Controls/Link.vue'
const show = defineModel()
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: {
@@ -174,9 +205,15 @@ const props = defineProps({
},
})
const evaluation = reactive({})
watch(user, () => {
if (userIsEvaluator()) {
defaultTemplate.reload()
}
})
const certificate = reactive({})
const userIsEvaluator = () => {
return user.data && user.data.name == props.event.evaluator
}
const defaultTemplate = createResource({
url: 'frappe.client.get_value',
@@ -190,7 +227,6 @@ const defaultTemplate = createResource({
},
}
},
auto: true,
onSuccess(data) {
certificate.template = data.value
},
@@ -304,7 +340,7 @@ const certificateDetails = createResource({
}
},
onError(err) {
certificate.template = defaultTemplate.data.value
certificate.template = defaultTemplate.data?.value
},
auto: false,
})
@@ -347,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 [
{
@@ -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,
},
}
+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>
@@ -22,6 +22,7 @@
:onCreate="
(value, close) => {
openSettings('Members', close)
show = false
}
"
/>
@@ -66,6 +66,7 @@ import { inject, reactive, watch } from 'vue'
import { User } from '@/components/Settings/types'
import { openSettings, cleanError } from '@/utils'
import Link from '@/components/Controls/Link.vue'
import { useTelemetry } from 'frappe-ui/frappe'
interface ZoomAccount {
name: string
@@ -97,6 +98,7 @@ interface ZoomAccounts {
const show = defineModel('show')
const user = inject<User | null>('$user')
const zoomAccounts = defineModel<ZoomAccounts>('zoomAccounts')
const { capture } = useTelemetry()
const account = reactive({
name: '',
@@ -117,32 +119,27 @@ const props = defineProps({
watch(
() => props.accountID,
(val) => {
if (val != 'new') {
zoomAccounts.value?.data.forEach((acc) => {
if (acc.name === val) {
account.name = acc.name
account.enabled = acc.enabled || false
account.member = acc.member
account.account_id = acc.account_id
account.client_id = acc.client_id
account.client_secret = acc.client_secret
}
})
if (val === 'new') {
account.name = ''
account.enabled = false
account.member = user?.data?.name || ''
account.account_id = ''
account.client_id = ''
account.client_secret = ''
} else if (val && val !== 'new') {
const acc = zoomAccounts.value?.data.find((acc) => acc.name === val)
if (acc) {
account.name = acc.name
account.enabled = acc.enabled || false
account.member = acc.member
account.account_id = acc.account_id
account.client_id = acc.client_id
account.client_secret = acc.client_secret
}
}
}
)
watch(show, (val) => {
if (!val) {
account.name = ''
account.enabled = false
account.member = user?.data?.name || ''
account.account_id = ''
account.client_id = ''
account.client_secret = ''
}
})
const saveAccount = (close: () => void) => {
if (props.accountID == 'new') {
createAccount(close)
@@ -159,6 +156,7 @@ const createAccount = (close: () => void) => {
},
{
onSuccess() {
capture('zoom_account_linked')
zoomAccounts.value?.reload()
close()
toast.success(__('Zoom Account created successfully'))
+34 -26
View File
@@ -1,42 +1,50 @@
<template>
<div class="border rounded-md w-1/3 mx-auto my-32">
<div class="border-b px-5 py-3 font-medium">
<span
class="inline-flex items-center before:bg-surface-red-5 before:w-2 before:h-2 before:rounded-md before:mr-2"
></span>
{{ __('Not Permitted') }}
</div>
<div v-if="user.data" class="px-5 py-3">
<div>
{{ __('You do not have permission to access this page.') }}
<div class="bg-surface-white w-full h-full">
<div class="w-fit mx-auto mt-56 text-center p-4">
<div class="text-3xl font-semibold text-ink-gray-5 pb-4 mb-2 border-b">
{{ __('Not Permitted') }}
</div>
<router-link
:to="{
name: 'Courses',
}"
>
<Button variant="solid" class="mt-2">
{{ __('Checkout Courses') }}
<div v-if="user.data" class="px-5 py-3">
<div class="text-ink-gray-5">
{{ __('You do not have permission to access this page.') }}
</div>
<router-link
:to="{
name: 'Courses',
}"
>
<Button variant="solid" class="mt-2 w-full">
{{ __('Checkout Courses') }}
</Button>
</router-link>
</div>
<div class="px-5 py-3">
<div class="text-ink-gray-5">
{{ __('You are not permitted to access this page.') }}
</div>
<Button @click="redirectToLogin()" class="mt-4 w-full" variant="solid">
{{ __('Login') }}
</Button>
</router-link>
</div>
<div class="px-5 py-3">
<div>
{{ __('Please login to access this page.') }}
</div>
<Button @click="redirectToLogin()" class="mt-4">
{{ __('Login') }}
</Button>
</div>
</div>
</template>
<script setup>
import { inject } from 'vue'
import { Button } from 'frappe-ui'
import { Button, usePageMeta } from 'frappe-ui'
import { sessionStore } from '../stores/session'
const user = inject('$user')
const { brand } = sessionStore()
const redirectToLogin = () => {
window.location.href = '/login'
}
usePageMeta(() => {
return {
title: __('Not Permitted'),
icon: brand.favicon,
}
})
</script>
@@ -20,7 +20,7 @@
<span
class="size-3 rounded-full"
:style="{
backgroundColor: theme.backgroundColor[color.toLowerCase()][400],
backgroundColor: getColor(color.toLowerCase(), 400),
}"
></span>
<span>
@@ -55,9 +55,8 @@
<script setup lang="ts">
import { computed, inject, ref, watch } from 'vue'
import { NotepadText, Trash2 } from 'lucide-vue-next'
import { theme } from '@/utils/theme'
import type { Note, Notes } from '@/components/Notes/types'
import { blockQuotesClick, highlightText } from '@/utils'
import { blockQuotesClick, getColor, highlightText } from '@/utils'
const user = inject<any>('$user')
const show = defineModel()
+3
View File
@@ -7,6 +7,9 @@
:placeholder="__('Make notes for quick revision. Press / for menu.')"
@change="(val: string) => updateNoteText(val)"
:editable="true"
:uploadArgs="{
private: true,
}"
editorClass="prose prose-sm min-h-[200px] max-w-none"
/>
</template>
@@ -0,0 +1,20 @@
<template>
<div class="border rounded-lg p-3 space-y-2">
<div class="text-ink-gray-5">
{{ __(title) }}
</div>
<div class="flex items-center space-x-2">
<slot name="prefix" />
<div class="font-semibold text-2xl">
{{ value }}
</div>
<slot name="suffix" />
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
title: string
value: number | string
}>()
</script>
+4 -1
View File
@@ -1,6 +1,9 @@
<template>
<Tooltip :text="`${props.progress}%`">
<div class="w-full bg-surface-gray-3 rounded-full h-1">
<div
class="w-full bg-surface-gray-3 rounded-full h-1"
:class="$attrs.class"
>
<div
class="bg-surface-gray-7 rounded-full"
:class="progressBarHeight"
+5 -5
View File
@@ -55,8 +55,8 @@
<div v-if="quiz.data.duration" class="flex flex-col space-x-1 my-4">
<div class="mb-2">
<span class=""> {{ __('Time') }}: </span>
<span class="font-semibold">
<span class="text-ink-gray-9"> {{ __('Time') }}: </span>
<span class="font-semibold text-ink-gray-9">
{{ formatTimer(timer) }}
</span>
</div>
@@ -165,14 +165,14 @@
</div>
</div>
<span
class="ml-2"
class="ml-2 text-ink-gray-9"
v-html="questionDetails.data[`option_${index}`]"
>
</span>
</label>
<div
v-if="questionDetails.data[`explanation_${index}`]"
class="mt-2 text-xs"
class="mt-2 text-xs text-ink-gray-7"
v-show="showAnswers.length"
>
{{ questionDetails.data[`explanation_${index}`] }}
@@ -260,7 +260,7 @@
)
}}
</div>
<div v-else>
<div v-else class="text-ink-gray-7">
{{
__(
'You got {0}% correct answers with a score of {1} out of {2}'
@@ -1,28 +1,32 @@
<template>
<div class="flex flex-col justify-between h-full">
<div class="flex flex-col h-full">
<div>
<div class="flex items-center justify-between">
<div class="font-semibold mb-1 text-ink-gray-9">
{{ __(label) }}
</div>
<Badge
v-if="isDirty"
:label="__('Not Saved')"
variant="subtle"
theme="orange"
/>
<div class="space-x-2">
<Badge
v-if="isDirty"
:label="__('Not Saved')"
variant="subtle"
theme="orange"
/>
<Button
variant="solid"
:loading="saveSettings.loading"
@click="update"
>
{{ __('Update') }}
</Button>
</div>
</div>
<div class="text-xs text-ink-gray-5">
{{ __(description) }}
</div>
</div>
<div class="overflow-y-auto">
<SettingFields :fields="fields" :data="branding.data" />
</div>
<div class="flex flex-row-reverse mt-auto">
<Button variant="solid" :loading="saveSettings.loading" @click="update">
{{ __('Update') }}
</Button>
<SettingFields :sections="sections" :data="branding.data" />
</div>
</div>
</template>
@@ -34,7 +38,7 @@ import { watch, ref } from 'vue'
const isDirty = ref(false)
const props = defineProps({
fields: {
sections: {
type: Array,
required: true,
},
@@ -65,23 +69,9 @@ const saveSettings = createResource({
})
const update = () => {
let fieldsToSave = {}
let imageFields = ['favicon', 'banner_image']
props.fields.forEach((f) => {
if (imageFields.includes(f.name)) {
fieldsToSave[f.name] =
branding.data[f.name] && branding.data[f.name].file_url
? branding.data[f.name].file_url
: null
} else {
fieldsToSave[f.name] = branding.data[f.name]
}
})
fieldsToSave['app_logo'] = fieldsToSave['banner_image']
saveSettings.submit(
{
fields: fieldsToSave,
fields: getFieldsToSave(),
},
{
onSuccess(data) {
@@ -91,18 +81,36 @@ const update = () => {
)
}
watch(branding, (updatedDoc) => {
let textFields = []
let imageFields = []
const getFieldsToSave = () => {
let imageFields = ['favicon', 'banner_image']
let fieldsToSave = {}
props.fields.forEach((f) => {
if (f.type === 'Upload') {
imageFields.push(f.name)
} else {
textFields.push(f.name)
}
props.sections.forEach((section) => {
section.columns.forEach((column) => {
column.fields.forEach((field) => {
if (imageFields.includes(field.name)) {
fieldsToSave[field.name] =
branding.data[field.name] && branding.data[field.name].file_url
? branding.data[field.name].file_url
: null
} else {
fieldsToSave[field.name] = branding.data[field.name]
}
})
})
})
fieldsToSave['app_logo'] = fieldsToSave['banner_image']
return fieldsToSave
}
watch(branding, (updatedDoc) => {
updateDirtyState(updatedDoc)
})
const updateDirtyState = (updatedDoc) => {
const { textFields, imageFields } = segregateFields()
textFields.forEach((field) => {
if (updatedDoc.data[field] != updatedDoc.previousData[field]) {
isDirty.value = true
@@ -111,11 +119,29 @@ watch(branding, (updatedDoc) => {
imageFields.forEach((field) => {
if (
updatedDoc.data[field] &&
updatedDoc.data[field].file_url != updatedDoc.previousData[field].file_url
updatedDoc.data[field]?.file_url !=
updatedDoc.previousData[field]?.file_url
) {
isDirty.value = true
}
})
})
}
const segregateFields = () => {
let textFields = []
let imageFields = []
props.sections.forEach((section) => {
section.columns.forEach((column) => {
column.fields.forEach((field) => {
if (field.type === 'Upload') {
imageFields.push(field.name)
} else {
textFields.push(field.name)
}
})
})
})
return { textFields, imageFields }
}
</script>
@@ -0,0 +1,142 @@
<template>
<div class="flex flex-col text-base h-full">
<div class="flex items-center space-x-2 mb-8 -ml-1.5">
<ChevronLeft
class="size-5 stroke-1.5 text-ink-gray-7 cursor-pointer"
@click="emit('updateStep', 'list')"
/>
<div class="text-xl font-semibold text-ink-gray-9">
{{ data?.name ? __('Edit Coupon') : __('New Coupon') }}
</div>
</div>
<div class="space-y-4 overflow-y-auto">
<div>
<FormControl
v-model="data.enabled"
:label="__('Enabled')"
type="checkbox"
/>
</div>
<div class="grid grid-cols-2 gap-4">
<FormControl
v-model="data.code"
:label="__('Coupon Code')"
:required="true"
@input="() => (data.code = data.code.toUpperCase())"
/>
<FormControl
v-model="data.discount_type"
:label="__('Discount Type')"
:required="true"
type="select"
:options="['Percentage', 'Fixed Amount']"
/>
<FormControl
v-model="data.expires_on"
:label="__('Expires On')"
type="date"
/>
<FormControl
v-if="data.discount_type === 'Percentage'"
v-model="data.percentage_discount"
:required="true"
:label="__('Discount Percentage')"
type="number"
/>
<FormControl
v-else
v-model="data.fixed_amount_discount"
:required="true"
:label="__('Discount Amount')"
type="number"
/>
<FormControl
v-model="data.usage_limit"
:label="__('Usage Limit')"
type="number"
/>
<FormControl
v-model="data.redemptions_count"
:label="__('Redemptions Count')"
type="number"
:disabled="true"
/>
</div>
<div class="py-8">
<div class="font-semibold text-ink-gray-9 mb-2">
{{ __('Applicable For') }}
</div>
<CouponItems ref="couponItems" :data="data" :coupons="coupons" />
</div>
</div>
<div class="mt-auto space-x-2 ml-auto">
<Button variant="solid" @click="saveCoupon()">
{{ __('Save') }}
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import { Button, FormControl, toast } from 'frappe-ui'
import { ref } from 'vue'
import { ChevronLeft } from 'lucide-vue-next'
import type { Coupon, Coupons } from './types'
import CouponItems from '@/components/Settings/Coupons/CouponItems.vue'
const couponItems = ref<any>(null)
const emit = defineEmits(['updateStep'])
const props = defineProps<{
coupons: Coupons
data: Coupon
}>()
const saveCoupon = () => {
if (props.data?.name) {
editCoupon()
} else {
createCoupon()
}
}
const editCoupon = () => {
props.coupons.setValue.submit(
{
...props.data,
},
{
onSuccess(data: Coupon) {
if (couponItems.value) {
couponItems.value.saveItems()
}
},
}
)
}
const createCoupon = () => {
if (couponItems.value) {
let rows = couponItems.value.saveItems()
props.data.applicable_items = rows
}
props.coupons.insert.submit(
{
...props.data,
},
{
onSuccess(data: Coupon) {
toast.success(__('Coupon created successfully'))
emit('updateStep', 'details', { ...data })
},
onError(err: any) {
toast.error(err.messages?.[0] || err.message || err)
console.error(err)
},
}
)
}
</script>

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