fix focus on ticket new, sitebranding, customer edit error
This commit is contained in:
1 parent
d72381f0ac
commit
6b841dc5fa
11 files changed
+139
-10
No files matched your search
+19
-3
@@ -682,9 +682,19 @@ def _visible_dispatches(user):
|
||||
|
||||
|
||||
def _contact_queryset(user):
|
||||
"""Contacts visible through at least one of the staff member's units."""
|
||||
"""Contacts visible through a permitted group or a viewable ticket.
|
||||
|
||||
A customer created by login or ticket intake may not have a contact group
|
||||
yet. Keeping ticket-visible customers in this queryset makes the contact
|
||||
list and its edit endpoint use the same visibility rule.
|
||||
"""
|
||||
visible_groups = ContactGroup.objects.filter(unit_id__in=_unit_ids_with_permission(user, "customers.view_customerprofile")).select_related("unit")
|
||||
return CustomerProfile.objects.filter(groups__in=visible_groups).select_related("user").prefetch_related(Prefetch("groups", queryset=visible_groups, to_attr="visible_groups")).distinct()
|
||||
visible_customer_ids = staff_ticket_rows(user).values_list("customer_id", flat=True)
|
||||
return CustomerProfile.objects.filter(
|
||||
Q(groups__in=visible_groups) | Q(user_id__in=visible_customer_ids)
|
||||
).select_related("user").prefetch_related(
|
||||
Prefetch("groups", queryset=visible_groups, to_attr="visible_groups")
|
||||
).distinct()
|
||||
|
||||
|
||||
def _has_any_unit_permission(user, permission):
|
||||
@@ -747,7 +757,13 @@ def staff_contact_create(request):
|
||||
def staff_contact_edit(request, public_id):
|
||||
profile = get_object_or_404(_contact_queryset(request.user), user__public_id=public_id)
|
||||
manageable_unit_ids = _unit_ids_with_permission(request.user, "customers.change_customerprofile")
|
||||
if not set(profile.groups.values_list("unit_id", flat=True)) & manageable_unit_ids:
|
||||
editable_through_group = profile.groups.filter(unit_id__in=manageable_unit_ids).exists()
|
||||
editable_through_ticket = staff_ticket_rows(request.user).filter(
|
||||
Q(assigned_unit_id__in=manageable_unit_ids)
|
||||
| Q(assigned_unit__isnull=True, owning_unit_id__in=manageable_unit_ids),
|
||||
customer_id=profile.user_id,
|
||||
).exists()
|
||||
if not (editable_through_group or editable_through_ticket):
|
||||
raise PermissionDenied
|
||||
form = StaffContactForm(request.POST or None, user=request.user, instance=profile, unit_ids=manageable_unit_ids)
|
||||
if request.method == "POST" and form.is_valid():
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.ticket-wizard-topbar{display:flex;min-height:3rem;align-items:center;justify-content:space-between;gap:1rem}.ticket-wizard-back{color:#1977ce;font-size:.82rem;font-weight:850}.ticket-wizard-save{display:none;align-items:center;gap:.45rem;color:rgba(8,55,107,.62);font-size:.76rem}.ticket-wizard-save i{width:.48rem;height:.48rem;border-radius:50%;background:#19a774;box-shadow:0 0 0 .23rem rgba(25,167,116,.1)}
|
||||
.ticket-wizard-intro{margin:.65rem 0 1.1rem}.ticket-wizard-intro .page-kicker{margin:0;color:#0693e3;font-size:.76rem;font-weight:900}.ticket-wizard-intro h1{margin:.25rem 0;font-size:1.5rem;line-height:1.5}.ticket-wizard-intro>p:last-child{margin:0;color:rgba(8,55,107,.62);font-size:.86rem;line-height:1.9}
|
||||
.ticket-wizard-progress{position:relative;display:grid;grid-template-columns:repeat(5,1fr);margin:0 0 .8rem;padding:0;list-style:none}.ticket-wizard-progress:before{position:absolute;top:1.05rem;right:10%;left:10%;height:2px;background:rgba(8,55,107,.1);content:""}.ticket-wizard-progress li{position:relative;display:grid;justify-items:center;color:rgba(8,55,107,.35)}.ticket-wizard-progress li>span{z-index:1;display:grid;width:2.1rem;height:2.1rem;place-items:center;border:2px solid #d5dfe4;border-radius:.7rem;background:#eee;font-size:.72rem;font-weight:900}.ticket-wizard-progress li>div{display:none}.ticket-wizard-progress li.is-active>span{border-color:#0693e3;background:#0693e3;color:#fff;box-shadow:0 8px 20px -12px #0693e3}.ticket-wizard-progress li.is-complete>span{border-color:#27a47a;background:#e5f7f0;color:#16815e}
|
||||
.ticket-wizard-card{overflow:visible;border:1px solid rgba(8,55,107,.1);border-radius:1.15rem;background:#fff;box-shadow:0 18px 55px -35px rgba(8,55,107,.35)}.ticket-wizard-step{padding:1rem}.ticket-wizard-card[data-wizard-enhanced] .ticket-wizard-step:not(.is-active){display:none}.ticket-wizard-step-head{display:flex;align-items:flex-start;gap:.7rem;border-bottom:1px solid rgba(8,55,107,.07);padding-bottom:1rem}.ticket-wizard-symbol{display:grid;width:2.45rem;height:2.45rem;flex:none;place-items:center;border-radius:.75rem;background:#e5f4ff;color:#1977ce;font-size:1.05rem;font-weight:900}.ticket-wizard-symbol.is-success{background:#e5f8ef;color:#16815e}.ticket-wizard-step-head small{color:rgba(8,55,107,.48);font-size:.7rem;font-weight:750}.ticket-wizard-step-head h2{margin:.08rem 0;color:#08376b;font-size:1.08rem;line-height:1.6}.ticket-wizard-step-head p{margin:0;color:rgba(8,55,107,.62);font-size:.82rem;line-height:1.85}
|
||||
.ticket-wizard-card{overflow:visible;scroll-margin-top:.75rem;border:1px solid rgba(8,55,107,.1);border-radius:1.15rem;background:#fff;box-shadow:0 18px 55px -35px rgba(8,55,107,.35)}.ticket-wizard-card:focus{outline:none}.ticket-wizard-step{padding:1rem}.ticket-wizard-card[data-wizard-enhanced] .ticket-wizard-step:not(.is-active){display:none}.ticket-wizard-step-head{display:flex;align-items:flex-start;gap:.7rem;border-bottom:1px solid rgba(8,55,107,.07);padding-bottom:1rem}.ticket-wizard-symbol{display:grid;width:2.45rem;height:2.45rem;flex:none;place-items:center;border-radius:.75rem;background:#e5f4ff;color:#1977ce;font-size:1.05rem;font-weight:900}.ticket-wizard-symbol.is-success{background:#e5f8ef;color:#16815e}.ticket-wizard-step-head small{color:rgba(8,55,107,.48);font-size:.7rem;font-weight:750}.ticket-wizard-step-head h2{margin:.08rem 0;color:#08376b;font-size:1.08rem;line-height:1.6}.ticket-wizard-step-head p{margin:0;color:rgba(8,55,107,.62);font-size:.82rem;line-height:1.85}
|
||||
.ticket-intent-search{display:flex;align-items:center;margin:1rem 0 .65rem;border:1px solid rgba(8,55,107,.14);border-radius:1rem;background:#fff;padding:0 .75rem;box-shadow:0 1px 3px rgba(8,55,107,.06)}.ticket-intent-search:focus-within{border-color:#0693e3;box-shadow:0 0 0 4px rgba(6,147,227,.1)}.ticket-intent-search i{color:#0693e3;font-style:normal}.ticket-intent-search input{min-height:3.2rem;border:0!important;background:transparent!important;padding:.7rem!important;font-size:1rem!important;box-shadow:none!important}.ticket-intent-search kbd{display:none}.ticket-intent-results:empty{display:none}.ticket-intent-results{margin-bottom:.65rem}.ticket-intent-results .intent-result{font-size:.84rem}.ticket-intent-results .intent-result span{font-size:.75rem}.ticket-intent-results .intent-result small,.ticket-intent-results .intent-message{font-size:.73rem;line-height:1.7}
|
||||
.ticket-manual-routing{margin:.75rem 0!important;border-color:rgba(8,55,107,.1)!important;border-radius:1rem!important}.ticket-manual-routing summary{background:#fafcfd!important;padding:.8rem!important;font-size:.8rem;font-weight:850}.ticket-manual-routing summary small{font-size:.7rem!important}.ticket-manual-body{padding:.8rem!important}.ticket-routing-grid,.ticket-two-columns{display:grid;gap:.7rem}.ticket-wizard-page .field{margin-bottom:.8rem}.ticket-wizard-page label{font-size:.82rem}.ticket-wizard-page input:not([type=checkbox]):not([type=radio]),.ticket-wizard-page textarea,.ticket-wizard-page select{min-height:3.1rem;font-size:1rem}.ticket-subject-field{margin-top:.85rem!important}.ticket-subject-field label em{color:#fa9f00;font-style:normal}.ticket-subject-field>small,.ticket-body-field>small{display:flex;justify-content:flex-end;margin-top:.35rem;color:rgba(8,55,107,.5);font-size:.7rem;font-weight:500}.ticket-body-field textarea{min-height:9rem;line-height:1.9}.ticket-wizard-page .errorlist{margin:.35rem 0 0;padding:.5rem .65rem;border-radius:.7rem;background:#fff1f3;color:#92263a;font-size:.74rem;font-weight:750;list-style:none}
|
||||
.ticket-location-fields{margin-top:1rem}.ticket-location-fields .location-hint{margin:.2rem 0;color:rgba(8,55,107,.55);font-size:.72rem}.ticket-soft-tip{display:flex;align-items:center;gap:.65rem;margin:.8rem 0;border-radius:.85rem;background:#e5f4ff;padding:.75rem;color:rgba(8,55,107,.68)}.ticket-soft-tip>span{display:grid;width:1.75rem;height:1.75rem;flex:none;place-items:center;border-radius:.55rem;background:#fff;color:#1977ce;font-weight:900}.ticket-soft-tip p{margin:0;font-size:.76rem;line-height:1.75}.ticket-soft-tip b{color:#08376b}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-leading:initial;--tw-font-weight:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-tracking:initial;--tw-border-style:solid;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:Vazirmatn,ui-sans-serif,system-ui;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-700:oklch(50.5% .213 27.518);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-800:oklch(47.3% .137 46.201);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-800:oklch(43.2% .095 166.913);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-400:oklch(70.4% .04 256.788);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--blur-sm:8px;--blur-xl:24px;--blur-2xl:40px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-ink:#08376b;--color-ink-soft:#0b4c8c;--color-cream:#eee;--color-soft:#eaeaea;--color-paper:#fff;--color-sea:#0693e3;--color-sea-dark:#1977ce;--color-blue:#1977ce;--color-mint:#e5f4ff;--color-coral:#fa9f00;--color-sun:#ffc451;--shadow-panel:0 18px 55px -35px #08376b59}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-Line truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-leading:initial;--tw-font-weight:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-tracking:initial;--tw-border-style:solid;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:Vazirmatn,ui-sans-serif,system-ui;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-700:oklch(50.5% .213 27.518);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-800:oklch(47.3% .137 46.201);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-800:oklch(43.2% .095 166.913);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-400:oklch(70.4% .04 256.788);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--blur-sm:8px;--blur-xl:24px;--blur-2xl:40px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-ink:#08376b;--color-ink-soft:#0b4c8c;--color-cream:#eee;--color-soft:#eaeaea;--color-paper:#fff;--color-sea:#0693e3;--color-sea-dark:#1977ce;--color-blue:#1977ce;--color-mint:#e5f4ff;--color-coral:#fa9f00;--color-sun:#ffc451;--shadow-panel:0 18px 55px -35px #08376b59}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-Line truncated
|
||||
@@ -349,6 +349,19 @@
|
||||
setupVoiceRecorder(voiceRoot);
|
||||
form.dataset.wizardEnhanced = '';
|
||||
|
||||
function focusWizardCard() {
|
||||
requestAnimationFrame(() => {
|
||||
// Do not steal focus if the visitor has already started interacting.
|
||||
if (document.activeElement !== document.body && document.activeElement !== document.documentElement) return;
|
||||
form.focus({ preventScroll: true });
|
||||
form.scrollIntoView({
|
||||
behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth',
|
||||
block: 'start',
|
||||
inline: 'nearest',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const serverErrorStep = steps.find(step => step.querySelector('.errorlist'));
|
||||
const initialStep = serverErrorStep ? Number(serverErrorStep.dataset.wizardStep) : 1;
|
||||
showStep(initialStep, { scroll: false });
|
||||
@@ -359,5 +372,5 @@
|
||||
.filter(Boolean);
|
||||
const number = Number(serverErrorStep.dataset.wizardStep);
|
||||
requestAnimationFrame(() => showValidation(number, { message: messages.join(' '), fields }));
|
||||
}
|
||||
} else focusWizardCard();
|
||||
})();
|
||||
@@ -92,6 +92,7 @@ body:has(.ticket-workspace) .flash{position:fixed;top:5rem;left:50%;z-index:100;
|
||||
.site-footer{display:flex;min-height:2rem;align-items:center;justify-content:center;gap:.35rem;padding:.4rem 1rem;color:#60758b;font-size:.68rem;font-weight:500;text-align:center}
|
||||
.staff-main>.site-footer{border-top:1px solid rgba(8,55,107,.06)}
|
||||
body:has(.ticket-workspace) .site-footer{display:none}body:has(.ticket-workspace) .ticket-workspace{height:calc(100dvh - 4.25rem)}body:has(.staff-ticket-workspace) .ticket-workspace{height:calc(100dvh - 4.5rem)}
|
||||
.customer-workspace.has-ticket-detail-credit{display:grid;grid-template-rows:auto minmax(0,1fr) auto}.customer-workspace.has-ticket-detail-credit .workspace-grid{height:auto;min-height:0}.ticket-detail-credit{display:flex;min-height:2rem;align-items:center;justify-content:center;border-top:1px solid rgba(8,55,107,.08);background:var(--color-paper);padding:.35rem 1rem;color:#60758b;font-size:.68rem;font-weight:500;line-height:1.6;text-align:center}.ticket-detail-credit a{color:var(--color-sea)}
|
||||
@media(max-width:639px){.logo-copy{font-size:.82rem}.header-company-logo{width:4.75rem;max-height:2.1rem}.auth-brand{gap:.5rem}.auth-brand img{width:7rem;max-height:3.1rem}.auth-brand>div{min-width:0}.auth-footer{font-size:.6rem}.ticket-company-logo{width:2.5rem;max-height:1.1rem}.site-footer{padding-inline:3.5rem;font-size:.62rem;line-height:1.6}body:has(.ticket-workspace) .ticket-workspace{height:100dvh}body:has(.staff-ticket-workspace) .ticket-workspace{height:calc(100dvh - 4.5rem)}}
|
||||
|
||||
/* Session actions, staff-created tickets and install prompt */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
{% block content %}
|
||||
{% if magic_link_access %}<div class="flash" role="status">شما از طریق لینک امن، فقط به همین تیکت دسترسی دارید. برای مشاهده سایر درخواستها و پروفایل، با شماره موبایل خود وارد شوید.</div>{% endif %}
|
||||
<div class="ticket-workspace customer-workspace" data-conversation-workspace data-poll-url="{% url 'tickets:poll' ticket.public_id %}" data-read-url="{% url 'tickets:read' ticket.public_id %}" data-poll-version="{{ poll_version }}" data-poll-cursor="{{ poll_cursor }}" data-draft-key="ticket-{{ ticket.public_id }}-customer" data-reply-result="{{ request.GET.reply_result|default:'' }}">
|
||||
<div class="ticket-workspace customer-workspace{% if magic_link_access %} has-ticket-detail-credit{% endif %}" data-conversation-workspace data-poll-url="{% url 'tickets:poll' ticket.public_id %}" data-read-url="{% url 'tickets:read' ticket.public_id %}" data-poll-version="{{ poll_version }}" data-poll-cursor="{{ poll_cursor }}" data-draft-key="ticket-{{ ticket.public_id }}-customer" data-reply-result="{{ request.GET.reply_result|default:'' }}">
|
||||
<header class="ticket-workspace-head"><div class="ticket-title"><a class="workspace-back" href="{% if magic_link_access %}{% url 'accounts:login' %}{% else %}{% url 'tickets:list' %}{% endif %}" aria-label="بازگشت">→</a><div><span class="ticket-number" dir="ltr">{{ ticket.ticket_number }}</span><h1>{{ ticket.subject }}</h1></div></div><div class="ticket-head-actions"><span class="badge" data-ticket-status>{{ ticket.get_status_display }}</span><button type="button" class="icon-button" data-toggle-panel="ticket-info" aria-label="جزئیات تیکت">ⓘ</button></div></header>
|
||||
<div class="workspace-grid customer-grid">
|
||||
<main class="conversation-pane">
|
||||
@@ -22,7 +22,9 @@
|
||||
</main>
|
||||
<aside class="workspace-side" id="ticket-info" data-workspace-panel><button type="button" class="panel-close" data-close-panel aria-label="بستن">×</button><section class="info-card"><p class="panel-index">جزئیات تیکت</p><div class="info-list"><div class="info-row"><span>دستهبندی</span><b>{{ ticket.category }}</b></div><div class="info-row"><span>موقعیت</span><b>{% if ticket.city %}{{ ticket.city }}، {{ ticket.province }}{% elif ticket.province %}{{ ticket.province }}{% else %}ثبت نشده{% endif %}</b></div><div class="info-row"><span>کارشناس</span><b>{{ ticket.assigned_expert_name }}</b></div><div class="info-row"><span>آخرین فعالیت</span><b>{{ ticket.last_activity_at|jalali_datetime }}</b></div></div></section><section class="info-card">{% if magic_link_access %}<h3>مشاهده همه درخواستها</h3><p class="muted mt-2">برای دسترسی به پروفایل و سایر درخواستها، ابتدا با شماره موبایل خود وارد شوید.</p><a class="back-link mt-3" href="{% url 'accounts:login' %}">ورود با شماره موبایل ←</a>{% else %}<h3>موضوع دیگری دارید؟</h3><p class="muted mt-2">برای هر موضوع یک درخواست جدا بسازید تا پیگیری منظم بماند.</p><a class="back-link mt-3" href="{% url 'tickets:new' %}">ثبت درخواست تازه ←</a>{% endif %}</section></aside>
|
||||
</div>
|
||||
{% if magic_link_access %}<footer class="ticket-detail-credit">{{ developer_credit }}</footer>{% endif %}
|
||||
</div>
|
||||
{% include 'tickets/_image_viewer.html' %}
|
||||
<script src="{% static 'vendor/compressor.min.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/attachment-uploader.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/image-viewer.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/conversation.js' %}?v={{ static_asset_version }}"></script>
|
||||
{% endblock %}
|
||||
{% block footer %}{% endblock %}
|
||||
@@ -25,7 +25,7 @@
|
||||
<li data-wizard-progress-item="5"><span>۵</span><div><b>بازبینی</b><small>تأیید نهایی</small></div></li>
|
||||
</ol>
|
||||
|
||||
<form class="ticket-wizard-card" method="post" enctype="multipart/form-data" novalidate
|
||||
<form class="ticket-wizard-card" method="post" enctype="multipart/form-data" novalidate tabindex="-1" aria-label="فرم ثبت درخواست"
|
||||
data-ticket-composer data-ticket-wizard
|
||||
data-unit-options-url="{% url 'tickets:new_options' %}"
|
||||
data-ticket-search-url="{% url 'tickets:new_search' %}"
|
||||
|
||||
@@ -12,6 +12,8 @@ class CustomerNewTicketPage(BasePage):
|
||||
|
||||
def open(self):
|
||||
self.goto("/tickets/new/")
|
||||
if not self.unit.is_visible():
|
||||
self.page.locator("details.ticket-manual-routing summary").click()
|
||||
expect(self.unit).to_be_visible()
|
||||
|
||||
def select_unit(self, unit_id: int, expected_category: str):
|
||||
|
||||
@@ -170,6 +170,10 @@ def test_e2e_customer_wizard_validation_preview_edit_and_floating_submit(
|
||||
with browser_page() as page:
|
||||
page.set_viewport_size({"width": 390, "height": 640})
|
||||
form = open_form(page, live_server, customer)
|
||||
wizard = page.locator('[data-ticket-wizard]')
|
||||
expect(wizard).to_be_focused()
|
||||
expect(wizard).to_be_in_viewport()
|
||||
assert wizard.bounding_box()["y"] < page.viewport_size["height"] / 3
|
||||
expect(page.locator('[data-wizard-step="1"]')).to_be_visible()
|
||||
expect(page.locator('[data-wizard-step="2"]')).to_be_hidden()
|
||||
expect(page.locator('[data-ticket-search-results]')).to_be_hidden()
|
||||
@@ -186,7 +190,7 @@ def test_e2e_customer_wizard_validation_preview_edit_and_floating_submit(
|
||||
form.select_search_result("عمومی فروش", "عمومی فروش")
|
||||
expect(page.locator('[data-ticket-subject]')).to_have_value("")
|
||||
page.locator('[data-wizard-step="1"] [data-wizard-next]').click()
|
||||
assert page.evaluate("document.activeElement === document.querySelector('[data-ticket-subject]')")
|
||||
expect(page.locator('[data-ticket-subject]')).to_be_focused()
|
||||
expect(page.locator('.ticket-subject-field')).to_have_class(re.compile(r"is-guided-field"))
|
||||
page.locator('[data-ticket-subject]').fill(subject)
|
||||
expect(page.locator('[data-ticket-subject]')).to_have_value(subject)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
|
||||
from apps.accounts.models import User
|
||||
from apps.accounts.models import TicketMagicLink, User
|
||||
from apps.core.models import PageTitle, SiteBranding
|
||||
from apps.customers.models import ContactGroup
|
||||
from apps.notifications.models import SMSTemplate
|
||||
from apps.staff.models import OrganizationalUnit
|
||||
from apps.tickets.models import GroupDispatch, TicketCategory
|
||||
from apps.tickets.services import create_ticket
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -66,6 +67,32 @@ def test_customer_new_renders_developer_credit_once_directly_below_wizard(client
|
||||
assert '<footer class="site-footer">' not in content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_magic_ticket_renders_database_developer_credit_once_inside_workspace(client):
|
||||
branding = SiteBranding.load()
|
||||
branding.developer_credit_text = "برنامه نویسی و توسعه: محمد جواد فصاحت | 09202202129"
|
||||
branding.save()
|
||||
customer = User.objects.create_user(
|
||||
username="magic-credit-customer", mobile="09120000414", account_type=User.AccountType.CUSTOMER
|
||||
)
|
||||
category = TicketCategory.objects.create(title="پیگیری لینک امضا", slug="magic-credit-category")
|
||||
ticket = create_ticket(customer=customer, category=category, subject="نمایش امضا", body="شرح")
|
||||
token = TicketMagicLink.issue(ticket)
|
||||
destination = client.get(reverse("accounts:magic", args=[token])).url
|
||||
|
||||
content = client.get(destination).content.decode()
|
||||
assert content.count(branding.developer_credit_text) == 1
|
||||
assert '<footer class="ticket-detail-credit">' in content
|
||||
assert content.index('class="workspace-grid customer-grid"') < content.index('<footer class="ticket-detail-credit">')
|
||||
assert '<footer class="site-footer">' not in content
|
||||
|
||||
branding.developer_credit_html = '<strong data-developer-signature>امضای HTML سفارشی</strong>'
|
||||
branding.use_developer_credit_html = True
|
||||
branding.save()
|
||||
html_content = client.get(destination).content.decode()
|
||||
assert '<strong data-developer-signature>امضای HTML سفارشی</strong>' in html_content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_staff_sidebar_brand_is_database_backed(client):
|
||||
user = User.objects.create_superuser(username="brand-admin", password="safe-password", account_type=User.AccountType.STAFF)
|
||||
|
||||
@@ -405,6 +405,70 @@ def test_staff_can_visually_add_and_remove_contact_group_memberships(client):
|
||||
assert set(contact.customer_profile.groups.values_list("pk", flat=True)) == {second.pk}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_staff_can_classify_and_edit_ticket_customer_without_an_existing_group(client):
|
||||
unit = OrganizationalUnit.objects.create(name="واحد مشتری خودکار", slug="ungrouped-customer-editor")
|
||||
unit.permissions.add(*Permission.objects.filter(
|
||||
content_type__app_label="customers",
|
||||
codename__in=("view_customerprofile", "change_customerprofile"),
|
||||
))
|
||||
staff = make_staff("ungrouped-customer-agent", unit)
|
||||
contact = make_contact("ungrouped-ticket-customer", "09120000515")
|
||||
category = TicketCategory.objects.create(unit=unit, title="درخواست مشتری خودکار", slug="ungrouped-customer-ticket")
|
||||
create_ticket(customer=contact, created_by=staff, unit=unit, category=category, subject="نیازمند دستهبندی", body="شرح")
|
||||
group = ContactGroup.objects.create(name="گروه انتخابشده", unit=unit)
|
||||
client.force_login(staff)
|
||||
url = reverse("staff_tickets:contact_edit", args=[contact.public_id])
|
||||
|
||||
page = client.get(url)
|
||||
assert page.status_code == 200
|
||||
assert f'value="{group.pk}"' in page.content.decode()
|
||||
|
||||
missing_group = client.post(url, {
|
||||
"mobile": contact.local_mobile,
|
||||
"first_name": "مشتری",
|
||||
"last_name": "خودکار",
|
||||
"organization_name": "",
|
||||
"stakeholder_type": CustomerProfile.StakeholderType.CUSTOMER,
|
||||
"groups": [],
|
||||
"province": "",
|
||||
"city": "",
|
||||
})
|
||||
assert missing_group.status_code == 200
|
||||
assert missing_group.context["form"]["groups"].errors
|
||||
|
||||
saved = client.post(url, {
|
||||
"mobile": contact.local_mobile,
|
||||
"first_name": "مشتری",
|
||||
"last_name": "دستهبندیشده",
|
||||
"organization_name": "",
|
||||
"stakeholder_type": CustomerProfile.StakeholderType.CUSTOMER,
|
||||
"groups": [group.pk],
|
||||
"province": "",
|
||||
"city": "",
|
||||
})
|
||||
assert saved.status_code == 302
|
||||
assert contact.customer_profile.groups.filter(pk=group.pk).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_staff_cannot_edit_ungrouped_customer_from_an_unrelated_ticket_unit(client):
|
||||
ticket_unit = OrganizationalUnit.objects.create(name="واحد مالک مشتری", slug="ungrouped-owner-unit")
|
||||
unrelated_unit = OrganizationalUnit.objects.create(name="واحد نامرتبط مشتری", slug="ungrouped-unrelated-unit")
|
||||
unrelated_unit.permissions.add(*Permission.objects.filter(
|
||||
content_type__app_label="customers",
|
||||
codename__in=("view_customerprofile", "change_customerprofile"),
|
||||
))
|
||||
owner = make_staff("ungrouped-owner-agent", ticket_unit)
|
||||
outsider = make_staff("ungrouped-outsider-agent", unrelated_unit)
|
||||
contact = make_contact("private-ungrouped-customer", "09120000516")
|
||||
category = TicketCategory.objects.create(unit=ticket_unit, title="درخواست محرمانه", slug="private-ungrouped-ticket")
|
||||
create_ticket(customer=contact, created_by=owner, unit=ticket_unit, category=category, subject="محرمانه", body="شرح")
|
||||
client.force_login(outsider)
|
||||
|
||||
assert client.get(reverse("staff_tickets:contact_edit", args=[contact.public_id])).status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_staff_ticket_create_supports_multiple_previewable_attachments(client, monkeypatch, settings, tmp_path):
|
||||
settings.MEDIA_ROOT = tmp_path
|
||||
|
||||
Reference in new issue
Block a user