fix sms and ticket user frindly
This commit is contained in:
1 parent
1ee83374fd
commit
b8b3aba855
30 files changed
+2220
-119
No files matched your search
@@ -0,0 +1,16 @@
|
||||
.ticket-wizard-page{width:min(100%,65rem);margin-inline:auto;padding:0 0 6.5rem;color:#08376b}
|
||||
.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-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}
|
||||
.ticket-expert-picker{display:grid!important;grid-template-columns:1fr!important;gap:.55rem!important;margin:1rem 0!important}.ticket-expert-picker .expert-card{display:grid!important;min-height:0!important;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:.7rem;border-radius:1rem!important;padding:.72rem!important;text-align:right!important}.ticket-expert-picker .expert-card:has(input:checked){border-color:#0693e3;background:#e5f4ff}.ticket-expert-picker .expert-card img,.ticket-expert-picker .expert-avatar{width:2.8rem!important;height:2.8rem!important;margin:0!important;border-radius:.85rem!important}.ticket-expert-picker .expert-card strong,.ticket-expert-picker .expert-card small{display:block}.ticket-expert-picker .expert-card strong{font-size:.82rem}.ticket-expert-picker .expert-card small{margin-top:.1rem;font-size:.7rem;line-height:1.6}.ticket-expert-picker .expert-card:after{display:none;place-items:center;width:1.3rem;height:1.3rem;border-radius:50%;background:#0693e3;color:#fff;font-size:.6rem;font-weight:900;content:"✓"}.ticket-expert-picker .expert-card:has(input:checked):after{display:grid}
|
||||
.ticket-attachment-zone{display:grid;gap:.7rem;margin-top:.8rem!important;border-color:rgba(8,55,107,.14)!important;border-radius:1rem!important;background:rgba(229,244,255,.45)!important;padding:.85rem!important;text-align:right!important}.ticket-attachment-head{display:flex;align-items:center;gap:.65rem}.ticket-attachment-head>span{display:grid;width:2.35rem;height:2.35rem;flex:none;place-items:center;border-radius:.72rem;background:#fff;color:#1977ce;font-size:1.05rem;font-weight:900}.ticket-attachment-head b,.ticket-attachment-head small{display:block}.ticket-attachment-head b{font-size:.82rem}.ticket-attachment-head small{margin-top:.1rem;color:rgba(8,55,107,.58);font-size:.7rem;line-height:1.65}.ticket-attachment-actions{display:grid!important;gap:.5rem!important}.ticket-attachment-actions label{display:grid;min-height:3rem;place-items:center;border:1px solid rgba(6,147,227,.38);border-radius:.9rem;background:#fff;color:#0b5f9b;font-size:.8rem;font-weight:900}.ticket-attachment-actions .gallery-button{background:#e5f4ff}.ticket-attachment-zone .compression-note{font-size:.7rem;line-height:1.65}.ticket-attachment-zone .file-chip{font-size:.72rem}.ticket-attachment-zone .file-chip button{min-width:1.7rem;min-height:1.7rem}.ticket-attachment-zone .canonical-files{display:none}
|
||||
.ticket-voice-recorder{margin-top:.8rem;overflow:hidden;border:1px solid rgba(6,147,227,.2);border-radius:1.05rem;background:linear-gradient(145deg,#f7fcff,#edf7ff);box-shadow:0 12px 32px -28px rgba(8,55,107,.75)}.ticket-voice-head{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:.65rem;padding:.8rem}.ticket-voice-icon{display:grid;width:2.35rem;height:2.35rem;place-items:center;border-radius:.75rem;background:#fff;color:#0693e3;font-size:1rem}.ticket-voice-head b,.ticket-voice-head small,.ticket-voice-ready b,.ticket-voice-ready small{display:block}.ticket-voice-head b{font-size:.84rem}.ticket-voice-head small{margin-top:.12rem;color:rgba(8,55,107,.58);font-size:.72rem;line-height:1.7}.ticket-voice-timer{direction:ltr;border:1px solid rgba(6,147,227,.16);border-radius:999px;background:#fff;padding:.28rem .5rem;font-size:.75rem;font-weight:900;font-variant-numeric:tabular-nums}.ticket-voice-idle,.ticket-voice-live,.ticket-voice-preview{border-top:1px solid rgba(8,55,107,.07);padding:.75rem .8rem}.ticket-voice-idle{display:grid;gap:.35rem}.ticket-voice-idle>small{color:rgba(8,55,107,.58);font-size:.69rem;text-align:center}.ticket-voice-start{display:flex;min-height:3rem;align-items:center;justify-content:center;gap:.45rem;border:0;border-radius:.9rem;background:#0693e3;color:#fff;font-size:.82rem;font-weight:900}.ticket-voice-wave{display:flex;height:2.8rem;align-items:center;justify-content:center;gap:.24rem}.ticket-voice-wave i{width:.22rem;height:.75rem;border-radius:999px;background:#0693e3;animation:ticket-voice-wave .9s ease-in-out infinite}.ticket-voice-wave i:nth-child(2),.ticket-voice-wave i:nth-child(6){height:1.35rem;animation-delay:.12s}.ticket-voice-wave i:nth-child(3),.ticket-voice-wave i:nth-child(5){height:2rem;animation-delay:.24s}.ticket-voice-wave i:nth-child(4){height:2.5rem;animation-delay:.36s}.ticket-voice-recorder[data-state="paused"] .ticket-voice-wave i{animation-play-state:paused;opacity:.4}@keyframes ticket-voice-wave{0%,100%{transform:scaleY(.55)}50%{transform:scaleY(1)}}.ticket-voice-controls,.ticket-voice-preview-actions{display:grid;grid-template-columns:1fr 1.5fr;gap:.45rem}.ticket-voice-controls button,.ticket-voice-preview-actions button{min-height:2.65rem;border:1px solid rgba(8,55,107,.1);border-radius:.8rem;background:#fff;color:#08376b;font-size:.75rem;font-weight:850}.ticket-voice-controls .ticket-voice-stop{border-color:#0693e3;background:#0693e3;color:#fff}.ticket-voice-controls .ticket-voice-cancel{grid-column:1/-1;min-height:2rem;border:0;background:transparent;color:#9c3042}.ticket-voice-preview{display:grid;gap:.65rem;background:#fff}.ticket-voice-ready{display:flex;align-items:center;gap:.55rem}.ticket-voice-ready>span{display:grid;width:1.8rem;height:1.8rem;flex:none;place-items:center;border-radius:.55rem;background:#e5f8ef;color:#16815e;font-size:.65rem;font-weight:900}.ticket-voice-ready b{font-size:.8rem}.ticket-voice-ready small{color:rgba(8,55,107,.58);font-size:.7rem}.ticket-voice-preview audio,.ticket-review-audio{width:100%;height:2.6rem;accent-color:#0693e3}.ticket-voice-preview-actions{grid-template-columns:1fr 1fr}.ticket-voice-preview-actions [data-voice-delete]{border-color:rgba(169,39,60,.18);color:#a9273c}.ticket-voice-note{display:flex;gap:.35rem;margin:0;border-top:1px dashed rgba(8,55,107,.1);padding:.6rem .8rem;color:rgba(8,55,107,.58);font-size:.68rem;line-height:1.7}.ticket-voice-note span{color:#1977ce}.ticket-voice-error{margin:0;padding:.55rem .8rem;background:#fff0f1;color:#9c3042;font-size:.72rem;font-weight:800}
|
||||
.ticket-server-errors:empty{display:none}.ticket-wizard-validation:empty{display:none}.ticket-wizard-validation.is-visible{position:relative;margin:.8rem 0 0;border:1px solid rgba(169,39,60,.2);border-radius:.85rem;background:#fff1f3;padding:.72rem 2.2rem .72rem .75rem;color:#92263a;font-size:.78rem;font-weight:850;line-height:1.8}.ticket-wizard-validation.is-visible:before{position:absolute;top:.75rem;right:.75rem;display:grid;width:1.15rem;height:1.15rem;place-items:center;border-radius:50%;background:#a9273c;color:#fff;font-size:.66rem;content:"!"}.ticket-wizard-validation.is-visible:focus{box-shadow:0 0 0 4px rgba(169,39,60,.13)}.ticket-wizard-page [aria-invalid="true"]{border-color:#b8324a!important;background:#fffafb!important;box-shadow:0 0 0 3px rgba(184,50,74,.1)!important}
|
||||
.ticket-wizard-actions{position:sticky;z-index:10;bottom:4.8rem;display:flex;justify-content:flex-end;gap:.55rem;margin:1rem -1rem -1rem;border-top:1px solid rgba(8,55,107,.07);background:rgba(255,255,255,.97);padding:.7rem 1rem;backdrop-filter:blur(12px)}.ticket-wizard-actions>span{display:none}.ticket-wizard-actions button{min-height:3rem;border-radius:1rem;padding:.65rem .9rem;font-size:.82rem;font-weight:900}.ticket-wizard-actions .primary{border:0;background:#0693e3;color:#fff}.ticket-wizard-actions .secondary{border:1px solid rgba(8,55,107,.1);background:#fff;color:rgba(8,55,107,.72)}.ticket-wizard-card:not([data-wizard-enhanced]) [data-wizard-next],.ticket-wizard-card:not([data-wizard-enhanced]) [data-wizard-prev]{display:none}
|
||||
.ticket-review-callout{display:flex;align-items:center;gap:.65rem;margin:.85rem 0 .65rem;border-radius:.9rem;background:#f0faf6;padding:.7rem}.ticket-review-callout>span{display:grid;width:2rem;height:2rem;flex:none;place-items:center;border-radius:.65rem;background:#fff;color:#16815e;font-weight:900}.ticket-review-callout b,.ticket-review-callout small{display:block}.ticket-review-callout b{font-size:.82rem}.ticket-review-callout small{margin-top:.1rem;color:#66877b;font-size:.7rem;line-height:1.65}.ticket-review-list{overflow:hidden;border:1px solid rgba(8,55,107,.1);border-radius:1rem;background:#fff}.ticket-review-row{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:start;gap:.6rem;padding:.75rem}.ticket-review-row+.ticket-review-row{border-top:1px solid rgba(8,55,107,.07)}.ticket-review-icon{display:grid;width:1.8rem;height:1.8rem;place-items:center;border-radius:.55rem;background:#e5f4ff;color:#1977ce;font-size:.65rem;font-weight:900}.ticket-review-content{min-width:0}.ticket-review-content>small{color:rgba(8,55,107,.58);font-size:.7rem;font-weight:800}.ticket-review-content h3{overflow-wrap:anywhere;margin:.16rem 0 .08rem;color:#08376b;font-size:.9rem;line-height:1.7}.ticket-review-content [data-review-subject]{font-size:1rem;font-weight:900;line-height:1.8}.ticket-review-content p{display:flex;flex-wrap:wrap;align-items:center;gap:.3rem;margin:0;color:rgba(8,55,107,.62);font-size:.72rem;line-height:1.8}.ticket-review-content p i{font-style:normal}.ticket-review-row>button{display:flex;min-height:2rem;align-items:center;gap:.25rem;border:0;border-radius:.65rem;background:#e5f4ff;padding:.35rem .5rem;color:#1977ce;font-size:.68rem;font-weight:900}.ticket-review-details .ticket-review-content{grid-column:2/-1}.ticket-review-details>button{grid-column:3;grid-row:1}.ticket-review-body{display:block!important;margin-top:.2rem!important;color:rgba(8,55,107,.82)!important;font-size:.88rem!important;font-weight:550;line-height:2!important;white-space:pre-wrap}.ticket-review-files{display:flex;flex-wrap:wrap;gap:.3rem;margin-top:.45rem}.ticket-review-files span{border-radius:999px;background:#e5f4ff;padding:.28rem .5rem;color:#1977ce;font-size:.68rem}.ticket-review-audio{margin-top:.55rem}.ticket-review-step{padding-bottom:6.75rem}.ticket-wizard-card[data-wizard-enhanced] .ticket-final-actions{position:fixed;z-index:45;right:.75rem;bottom:5rem;left:.75rem;display:grid;grid-template-columns:minmax(0,.78fr) minmax(0,1.4fr);gap:.5rem;margin:0;border:1px solid rgba(8,55,107,.12);border-radius:1.1rem;background:rgba(255,255,255,.96);padding:.55rem;box-shadow:0 20px 50px -20px rgba(8,55,107,.55);backdrop-filter:blur(14px)}
|
||||
@media(min-width:640px){.ticket-wizard-page{padding-bottom:3rem}.ticket-wizard-topbar{min-height:3.8rem}.ticket-wizard-save{display:flex}.ticket-wizard-intro{margin:.8rem 0 1.35rem}.ticket-wizard-intro h1{font-size:2rem}.ticket-wizard-intro>p:last-child{font-size:.9rem}.ticket-wizard-progress{margin-bottom:1rem}.ticket-wizard-progress:before{top:1.12rem}.ticket-wizard-progress li{display:flex;justify-content:center;gap:.5rem}.ticket-wizard-progress li>span{width:2.3rem;height:2.3rem}.ticket-wizard-progress li>div{display:block}.ticket-wizard-progress li b,.ticket-wizard-progress li small{display:block}.ticket-wizard-progress li b{font-size:.78rem}.ticket-wizard-progress li small{font-size:.67rem}.ticket-wizard-card{border-radius:1.6rem}.ticket-wizard-step{padding:1.35rem 1.5rem}.ticket-wizard-step-head h2{font-size:1.2rem}.ticket-intent-search kbd{display:inline;border:1px solid rgba(8,55,107,.1);border-radius:.4rem;background:#eee;padding:.2rem .4rem;color:rgba(8,55,107,.58);font-size:.68rem}.ticket-routing-grid,.ticket-two-columns{grid-template-columns:1fr 1fr}.ticket-manual-body{padding:1rem!important}.ticket-expert-picker{grid-template-columns:repeat(3,1fr)!important}.ticket-expert-picker .expert-card{display:grid!important;grid-template-columns:1fr;justify-items:center;padding:.9rem!important;text-align:center!important}.ticket-expert-picker .expert-card img,.ticket-expert-picker .expert-avatar{width:3.3rem!important;height:3.3rem!important}.ticket-expert-picker .expert-card:after{position:absolute;top:.5rem;left:.5rem}.ticket-attachment-actions{grid-template-columns:1fr 1fr}.ticket-voice-idle{grid-template-columns:auto 1fr;align-items:center}.ticket-voice-idle>small{text-align:right}.ticket-voice-start{min-width:11rem}.ticket-voice-live{display:grid;grid-template-columns:1fr auto;align-items:center}.ticket-voice-controls{grid-template-columns:auto auto auto}.ticket-voice-controls .ticket-voice-cancel{grid-column:auto;min-height:2.65rem}.ticket-wizard-actions{position:static;margin:1.15rem -1.5rem -1.35rem;padding:.8rem 1.5rem}.ticket-wizard-actions>span{display:block;margin-left:auto;color:rgba(8,55,107,.45);font-size:.72rem}.ticket-wizard-actions button{min-width:8.5rem}.ticket-review-row{gap:.8rem;padding:.9rem 1rem}.ticket-review-details .ticket-review-content{grid-column:2}.ticket-review-content h3{font-size:.96rem}.ticket-review-content [data-review-subject]{font-size:1.08rem}.ticket-review-body{font-size:.94rem!important}.ticket-review-step{padding-bottom:6rem}.ticket-wizard-card[data-wizard-enhanced] .ticket-final-actions{position:fixed;right:auto;bottom:1rem;left:50%;width:min(calc(100% - 2rem),42rem);transform:translateX(-50%);padding:.6rem}}
|
||||
@media(prefers-reduced-motion:reduce){.ticket-voice-wave i{animation:none}}
|
||||
@@ -1,2 +1,19 @@
|
||||
.dispatch-metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.7rem}.dispatch-metrics article{display:grid;gap:.25rem;border:1px solid #dce7ef;border-radius:1rem;background:#fff;padding:.9rem 1rem;box-shadow:0 12px 30px -28px #08376b}.dispatch-metrics small{color:#718698;font-size:.62rem}.dispatch-metrics b{color:#123e62;font-size:1.25rem}.dispatch-filter{display:grid;gap:.65rem;border-bottom:1px solid #e3ebf1;background:#f8fbfd;padding:.8rem}.dispatch-filter>div{display:grid;grid-template-columns:1fr 1fr;gap:.5rem}.dispatch-filter label{display:grid;gap:.25rem;color:#536d81;font-size:.58rem;font-weight:900}.dispatch-filter input,.dispatch-filter select{width:100%;min-height:2.35rem;border:1px solid #cbd9e5;border-radius:.6rem;background:#fff;padding:.45rem .55rem;color:#173f60;font-size:.62rem}.dispatch-filter footer{display:flex;align-items:center;gap:.5rem}.dispatch-filter button{min-height:2.35rem;flex:1;border:0;border-radius:.6rem;background:#0875aa;color:#fff;font-size:.62rem;font-weight:900}.dispatch-filter footer a{color:#647b8d;font-size:.58rem;font-weight:900}.dispatch-results{display:grid;max-height:34rem;overflow:auto}.operations-pagination{display:flex;align-items:center;justify-content:space-between;border-top:1px solid #e5edf3;padding:.7rem .8rem;color:#667e91;font-size:.6rem}.operations-pagination a{border-radius:.55rem;background:#e8f4fb;padding:.4rem .65rem;color:#0873a9;font-weight:900}
|
||||
@media(max-width:700px){.dispatch-metrics{display:flex;overflow-x:auto}.dispatch-metrics article{min-width:8.5rem}}
|
||||
.dispatch-detail-head{align-items:flex-start}.dispatch-head-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.55rem}.dispatch-head-actions form{margin:0}.button-danger-soft{background:#fff1f2!important;color:#a51d35!important;border:1px solid #fecdd3!important}.dispatch-original-message{display:grid;gap:1rem;border:1px solid #d8e6ef;border-radius:1rem;background:linear-gradient(145deg,#fff,#f5fbff);padding:1.1rem 1.2rem;box-shadow:0 16px 40px -36px #164f75}.dispatch-original-message header{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.dispatch-original-message h2{margin:.18rem 0 0;color:#173f60;font-size:1rem}.dispatch-original-message header>span{color:#73889a;font-size:.62rem;direction:ltr}.dispatch-original-message>p{margin:0;border-right:3px solid #1490bb;padding:.7rem .9rem;color:#294c66;line-height:2;white-space:normal}.dispatch-detail-metrics{grid-template-columns:repeat(6,minmax(0,1fr))}.dispatch-detail-metrics small{display:block;margin-top:.25rem;color:#788c9c;font-size:.55rem}.dispatch-sms-state{min-width:15rem}.delivery-badge{display:inline-flex;border-radius:99rem;background:#e8f0f5;padding:.3rem .58rem;color:#435f73;font-size:.58rem}.delivery-delivered{background:#dcfce7;color:#166534}.delivery-undelivered,.delivery-expired,.delivery-blocked,.delivery-error,.delivery-failed{background:#fee2e2;color:#991b1b}.delivery-pending,.delivery-processing,.delivery-retrying{background:#fef3c7;color:#92400e}.delivery-unknown,.delivery-check_failed{background:#f1f5f9;color:#475569}.delivery-detail{max-width:24rem;margin:.45rem 0 0;color:#4b6477;font-size:.58rem;line-height:1.7}.sms-error-detail{max-width:24rem;margin-top:.45rem;border:1px solid #fecaca;border-radius:.55rem;background:#fff7f7;padding:.4rem .5rem;color:#8d1e2d;font-size:.57rem}.sms-error-detail summary{cursor:pointer;font-weight:900}.sms-error-detail p{margin:.35rem 0 0;line-height:1.7}.inline-retry-form{margin-top:.5rem}.inline-retry-form button{border:1px solid #f1b7bf;border-radius:.5rem;background:#fff;color:#a51d35;padding:.38rem .55rem;font:inherit;font-size:.56rem;font-weight:900;cursor:pointer}
|
||||
@media(max-width:1100px){.dispatch-detail-metrics{grid-template-columns:repeat(3,minmax(0,1fr))}}
|
||||
@media(max-width:700px){.dispatch-detail-head,.dispatch-original-message header{display:grid}.dispatch-head-actions{width:100%}.dispatch-head-actions>*{flex:1}.dispatch-detail-metrics{display:flex;overflow-x:auto}.dispatch-detail-metrics article{min-width:9rem}}
|
||||
|
||||
/* Dispatch report: human-readable status first, technical gateway data second. */
|
||||
.dispatch-report-hero{display:flex;align-items:flex-start;justify-content:space-between;gap:1.25rem;border:1px solid #d8e6ef;border-radius:1.2rem;background:linear-gradient(135deg,#f8fcff 0%,#fff 55%,#eff9fc 100%);padding:1.35rem;box-shadow:0 20px 50px -42px #0c4269}.dispatch-report-title{display:grid;gap:.7rem}.dispatch-title-line{display:flex;align-items:center;gap:.55rem}.dispatch-report-title h1{margin:0;color:#123b5b;font-size:1.45rem}.report-status{display:inline-flex;border-radius:99rem;background:#e7f0f5;padding:.34rem .7rem;color:#426177;font-size:.58rem;font-weight:900}.report-status.status-sent{background:#dcfce7;color:#166534}.report-status.status-partial{background:#fff0e1;color:#9a4f06}.dispatch-meta-list{display:flex;flex-wrap:wrap;gap:.5rem}.dispatch-meta-list span{display:flex;align-items:center;gap:.35rem;border:1px solid #deebf2;border-radius:.65rem;background:#fff;padding:.42rem .6rem;color:#557084;font-size:.59rem}.dispatch-meta-list b{color:#204c6d}.dispatch-original-message blockquote{margin:0;border:0;border-right:3px solid #1490bb;border-radius:.35rem;background:rgba(255,255,255,.72);padding:.8rem 1rem;color:#294c66;line-height:2}.dispatch-original-message footer{display:flex;flex-wrap:wrap;justify-content:space-between;gap:.5rem;border-top:1px dashed #d9e7ef;padding-top:.75rem;color:#6d8292;font-size:.57rem}.delivery-overview{display:grid;grid-template-columns:auto minmax(12rem,1fr) minmax(14rem,.7fr);align-items:center;gap:1rem;border:1px solid #d8e7ee;border-radius:1rem;background:#fff;padding:1rem 1.15rem}.delivery-progress-copy{min-width:12rem}.delivery-progress-copy strong{display:block;margin:.1rem 0;color:#0878a8;font-size:1.8rem}.delivery-progress-copy p{margin:0;color:#688092;font-size:.58rem}.delivery-progress-track{height:.75rem;overflow:hidden;border-radius:99rem;background:#e9f1f5;box-shadow:inset 0 1px 2px #cfdde6}.delivery-progress-track span{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,#13a36f,#42c895);transition:width .3s ease}.delivery-overview-note{display:grid;gap:.2rem;border-right:1px solid #e1eaf0;padding-right:1rem}.delivery-overview-note b{color:#294f69;font-size:.66rem}.delivery-overview-note span{color:#728797;font-size:.56rem;line-height:1.7}.dispatch-kpi-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:.7rem}.dispatch-kpi{display:flex;align-items:center;gap:.7rem;border:1px solid #dce8ef;border-radius:.9rem;background:#fff;padding:.85rem;box-shadow:0 14px 34px -32px #123f61}.kpi-icon{display:grid;width:2rem;height:2rem;flex:0 0 auto;place-items:center;border-radius:.65rem;background:#eaf2f7;color:#44657a;font-weight:900}.dispatch-kpi>div{min-width:0}.dispatch-kpi small,.dispatch-kpi p{display:block;margin:0;color:#778b99;font-size:.53rem}.dispatch-kpi strong{display:block;margin:.08rem 0;color:#183f5c;font-size:1.22rem}.kpi-success .kpi-icon{background:#dcfce7;color:#16734c}.kpi-info .kpi-icon{background:#e0f2fe;color:#0878a8}.kpi-danger .kpi-icon{background:#fee2e2;color:#a51d35}.kpi-view .kpi-icon{background:#ede9fe;color:#6d3fc0}.kpi-reply .kpi-icon{background:#fef3c7;color:#96600d}.dispatch-recipient-report{overflow:hidden}.dispatch-detail-table{border-collapse:separate;border-spacing:0;width:100%}.dispatch-detail-table thead th{position:sticky;top:0;z-index:2;background:#f5f9fc;color:#526c80;font-size:.56rem}.dispatch-detail-table tbody tr{transition:background .15s ease}.dispatch-detail-table tbody tr:hover{background:#f9fcfe}.dispatch-detail-table td{vertical-align:top;padding:.9rem .7rem;border-bottom:1px solid #e8eff3}.recipient-cell{display:flex;align-items:center;gap:.55rem;min-width:9rem}.recipient-avatar{display:grid;width:2rem;height:2rem;flex:0 0 auto;place-items:center;border-radius:.7rem;background:linear-gradient(145deg,#dff4fb,#bfe8f5);color:#0875a3;font-weight:900}.recipient-cell div{display:grid;gap:.17rem}.recipient-cell strong{color:#1e4866;font-size:.63rem}.recipient-cell small{color:#738896;font-size:.55rem}.ticket-reference{display:grid;gap:.22rem;text-decoration:none}.ticket-reference strong{color:#176993;font-size:.6rem}.ticket-reference span{color:#7b8d99;font-size:.52rem}.dispatch-sms-state{min-width:18rem}.sms-state-card{display:flex;align-items:flex-start;gap:.55rem;border:1px solid #dfe9ef;border-radius:.75rem;background:#f8fbfd;padding:.65rem}.sms-state-icon{display:grid;width:1.65rem;height:1.65rem;flex:0 0 auto;place-items:center;border-radius:.55rem;background:#e5eef4;color:#3f6075;font-weight:900}.sms-state-card>div{display:grid;gap:.18rem}.sms-state-card strong{color:#254c68;font-size:.61rem}.sms-state-card p,.sms-state-card small{margin:0;color:#718694;font-size:.53rem;line-height:1.65}.tone-success{border-color:#bcebd3;background:#f2fcf7}.tone-success .sms-state-icon{background:#d6f7e6;color:#17734d}.tone-warning{border-color:#f2d7a0;background:#fffaf0}.tone-warning .sms-state-icon{background:#feefc9;color:#93600c}.tone-danger{border-color:#f3c7cd;backgroundLine truncated
|
||||
@media(max-width:1100px){.dispatch-kpi-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.delivery-overview{grid-template-columns:auto 1fr}.delivery-overview-note{grid-column:1/-1;border-top:1px solid #e5edf2;border-right:0;padding: .7rem 0 0}}
|
||||
@media(max-width:760px){.dispatch-report-hero{display:grid}.dispatch-report-title h1{font-size:1.15rem}.dispatch-head-actions{display:grid}.dispatch-kpi-grid{display:flex;overflow-x:auto;padding-bottom:.25rem}.dispatch-kpi{min-width:10.5rem}.delivery-overview{grid-template-columns:1fr}.delivery-progress-copy{min-width:0}.dispatch-detail-table thead{display:none}.dispatch-detail-table,.dispatch-detail-table tbody,.dispatch-detail-table tr,.dispatch-detail-table td{display:block;width:100%}.dispatch-detail-table tr{border-bottom:8px solid #edf3f6}.dispatch-detail-table td{display:grid;grid-template-columns:6.5rem 1fr;gap:.6rem;border-bottom:1px solid #edf2f5}.dispatch-detail-table td:before{content:attr(data-label);color:#8799a5;font-size:.52rem;font-weight:900}.dispatch-sms-state{min-width:0}.dispatch-detail-table td>*{min-width:0}}
|
||||
|
||||
/* Readable report typography and a compact delivery summary. */
|
||||
.operations-dispatch-detail{font-size:1rem}.operations-dispatch-detail .eyebrow,.operations-dispatch-detail .panel-index{font-size:.72rem}.operations-dispatch-detail .report-status{font-size:.72rem}.operations-dispatch-detail .dispatch-meta-list span{gap:.45rem;font-size:.76rem}.operations-dispatch-detail .dispatch-original-message header>span,.operations-dispatch-detail .dispatch-original-message footer{font-size:.72rem}.operations-dispatch-detail .dispatch-original-message blockquote{font-size:.86rem}.operations-dispatch-detail .delivery-overview{display:grid;grid-template-columns:minmax(0,1fr) minmax(12rem,.42fr);gap:.7rem 1rem;padding:.75rem 1rem}.operations-dispatch-detail .delivery-progress-copy{display:flex;align-items:center;justify-content:space-between;gap:1rem;min-width:0}.operations-dispatch-detail .delivery-progress-copy>div{display:flex;align-items:center;gap:.55rem}.operations-dispatch-detail .delivery-progress-copy strong{display:inline;margin:0;font-size:1rem}.operations-dispatch-detail .delivery-progress-copy p{font-size:.74rem}.operations-dispatch-detail .delivery-progress-track{grid-column:1;height:.42rem}.operations-dispatch-detail .delivery-overview-note{grid-column:2;grid-row:1/3;align-content:center;border-right:1px solid #e1eaf0;padding-right:1rem}.operations-dispatch-detail .delivery-overview-note b{font-size:.78rem}.operations-dispatch-detail .delivery-overview-note span{font-size:.7rem}.operations-dispatch-detail .dispatch-kpi small,.operations-dispatch-detail .dispatch-kpi p{font-size:.7rem;line-height:1.65}.operations-dispatch-detail .dispatch-detail-table thead th{font-size:.72rem}.operations-dispatch-detail .recipient-cell strong,.operations-dispatch-detail .ticket-reference strong,.operations-dispatch-detail .sms-state-card strong{font-size:.78rem}.operations-dispatch-detail .recipient-cell small,.operations-dispatch-detail .ticket-reference span,.operations-dispatch-detail .sms-state-card p,.operations-dispatch-detail .sms-state-card small{font-size:.7rem}.operations-dispatch-detail .sms-technical-details,.operations-dispatch-detail .sms-error-panel,.operations-dispatch-detail .ticket-state-pill,.operations-dispatch-detail .engagement-state strong,.operations-dispatch-detail .recipient-reply p{font-size:.72rem}.operations-dispatch-detail .engagement-state small,.operations-dispatch-detail .recipient-reply small,.operations-dispatch-detail .no-reply-state{font-size:.7rem}
|
||||
@media(max-width:1100px){.operations-dispatch-detail .delivery-overview{grid-template-columns:1fr}.operations-dispatch-detail .delivery-progress-track{grid-column:1}.operations-dispatch-detail .delivery-overview-note{grid-column:1;grid-row:auto;border-top:1px solid #e5edf2;border-right:0;padding:.65rem 0 0}}
|
||||
@media(max-width:760px){.operations-dispatch-detail .delivery-progress-copy{align-items:flex-start;flex-direction:column;gap:.35rem}.operations-dispatch-detail .dispatch-detail-table td:before{font-size:.66rem}}
|
||||
|
||||
/* Shared attachments for isolated group dispatches. */
|
||||
.dispatch-attachment-picker{display:grid;gap:.8rem;margin:0 1rem 1rem;border:1px dashed #8fb7d1;border-radius:.9rem;background:linear-gradient(145deg,#f8fcff,#f2f9fd);padding:1rem}.dispatch-attachment-picker .attachment-picker-copy{display:flex;align-items:center;gap:.75rem}.dispatch-attachment-picker .attachment-icon{display:grid;width:2.45rem;height:2.45rem;flex:none;place-items:center;border-radius:.72rem;background:#dff2ff;color:#1977ce;font-size:1.2rem;font-weight:900}.dispatch-attachment-picker b,.dispatch-attachment-picker small{display:block}.dispatch-attachment-picker b{color:#173f60;font-size:.78rem}.dispatch-attachment-picker .attachment-picker-copy small{margin-top:.15rem;color:#647d91;font-size:.65rem;line-height:1.7}.dispatch-attachment-picker .media-picker-actions{margin:0}.dispatch-attachment-picker .file-preview{margin-top:0}.dispatch-attachment-picker .file-preview:empty{display:none}.dispatch-attachment-picker .file-chip{box-shadow:0 10px 25px -22px #08376b}.dispatch-attachment-picker .compression-note{color:#688095;font-size:.62rem;line-height:1.7}
|
||||
@media(max-width:700px){.dispatch-attachment-picker{margin:.8rem;padding:.8rem}.dispatch-attachment-picker .media-picker-actions{display:grid;grid-template-columns:1fr}.dispatch-attachment-picker .media-picker-button{justify-content:center}}
|
||||
@@ -0,0 +1,320 @@
|
||||
(() => {
|
||||
const form = document.querySelector('[data-ticket-wizard]');
|
||||
if (!form) return;
|
||||
|
||||
const steps = [...form.querySelectorAll('[data-wizard-step]')];
|
||||
const progress = [...document.querySelectorAll('[data-wizard-progress-item]')];
|
||||
const unit = form.querySelector('[data-ticket-unit]');
|
||||
const category = form.querySelector('[data-ticket-category]');
|
||||
const subject = form.querySelector('[data-ticket-subject]');
|
||||
const province = form.querySelector('[data-province-input]');
|
||||
const city = form.querySelector('[data-city-input]');
|
||||
const body = form.querySelector('textarea[name="body"]');
|
||||
const canonicalFiles = form.querySelector('input[name="files"]');
|
||||
const voiceRoot = form.querySelector('[data-ticket-voice-recorder]');
|
||||
let currentStep = 1;
|
||||
|
||||
const toPersianDigits = value => String(value).replace(/\d/g, digit => '۰۱۲۳۴۵۶۷۸۹'[digit]);
|
||||
|
||||
function updateProgress(number) {
|
||||
progress.forEach(item => {
|
||||
const itemNumber = Number(item.dataset.wizardProgressItem);
|
||||
item.classList.toggle('is-active', itemNumber === number);
|
||||
item.classList.toggle('is-complete', itemNumber < number);
|
||||
item.querySelector('span').textContent = itemNumber < number ? '✓' : toPersianDigits(itemNumber);
|
||||
});
|
||||
}
|
||||
|
||||
function showStep(number, { scroll = true } = {}) {
|
||||
currentStep = number;
|
||||
steps.forEach(step => {
|
||||
const active = Number(step.dataset.wizardStep) === number;
|
||||
step.hidden = !active;
|
||||
step.classList.toggle('is-active', active);
|
||||
});
|
||||
updateProgress(number);
|
||||
if (scroll) window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function clearValidation(number) {
|
||||
const step = form.querySelector(`[data-wizard-step="${number}"]`);
|
||||
const error = form.querySelector(`[data-wizard-error="${number}"]`);
|
||||
step?.querySelectorAll('[aria-invalid="true"]').forEach(field => field.removeAttribute('aria-invalid'));
|
||||
if (error) {
|
||||
error.textContent = '';
|
||||
error.classList.remove('is-visible');
|
||||
error.removeAttribute('tabindex');
|
||||
}
|
||||
}
|
||||
|
||||
function showValidation(number, problem) {
|
||||
const step = form.querySelector(`[data-wizard-step="${number}"]`);
|
||||
const error = form.querySelector(`[data-wizard-error="${number}"]`);
|
||||
if (number === 1 && problem.fields.some(field => field === unit || field === category)) {
|
||||
const manualRouting = form.querySelector('.ticket-manual-routing');
|
||||
if (manualRouting) manualRouting.open = true;
|
||||
}
|
||||
step?.querySelectorAll('[aria-invalid="true"]').forEach(field => field.removeAttribute('aria-invalid'));
|
||||
problem.fields.forEach(field => field?.setAttribute('aria-invalid', 'true'));
|
||||
error.textContent = problem.message;
|
||||
error.tabIndex = -1;
|
||||
error.classList.add('is-visible');
|
||||
requestAnimationFrame(() => {
|
||||
error.focus({ preventScroll: true });
|
||||
error.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
||||
});
|
||||
}
|
||||
|
||||
function problemForStep(number) {
|
||||
if (number === 1) {
|
||||
const missing = [];
|
||||
const fields = [];
|
||||
if (!unit?.value) { missing.push('واحد سازمانی'); fields.push(unit); }
|
||||
if (!category?.value) { missing.push('دستهبندی'); fields.push(category); }
|
||||
if ((subject?.value.trim().length || 0) < 5) { missing.push('موضوع دقیق حداقل پنجحرفی'); fields.push(subject); }
|
||||
if (missing.length) return { message: `برای ادامه، ${missing.join('، ')} را تکمیل کنید.`, fields };
|
||||
}
|
||||
if (number === 2) {
|
||||
if (city?.value && !province?.value) return { message: 'برای شهر انتخابشده، استان را نیز مشخص کنید.', fields: [province, city] };
|
||||
}
|
||||
if (number === 4 && !body?.value.trim() && !(canonicalFiles?.files.length)) {
|
||||
return { message: 'برای ادامه، شرح درخواست بنویسید یا یک فایل/پیام صوتی اضافه کنید.', fields: [body] };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateStep(number) {
|
||||
const problem = problemForStep(number);
|
||||
if (problem) {
|
||||
showValidation(number, problem);
|
||||
return false;
|
||||
}
|
||||
clearValidation(number);
|
||||
return true;
|
||||
}
|
||||
|
||||
function selectedText(select, fallback) {
|
||||
return select?.selectedOptions?.[0]?.textContent?.trim() || fallback;
|
||||
}
|
||||
|
||||
function buildReview() {
|
||||
form.querySelector('[data-review-subject]').textContent = subject.value.trim();
|
||||
form.querySelector('[data-review-unit]').textContent = selectedText(unit, '—');
|
||||
form.querySelector('[data-review-category]').textContent = selectedText(category, '—');
|
||||
form.querySelector('[data-review-location]').textContent = province?.value
|
||||
? [province.value, city?.value].filter(Boolean).join('، ')
|
||||
: 'ثبت نشده';
|
||||
const selectedExpert = form.querySelector('input[name="selected_expert"]:checked');
|
||||
form.querySelector('[data-review-expert]').textContent = selectedExpert?.closest('label')?.querySelector('strong')?.textContent?.trim() || 'صف عمومی واحد';
|
||||
form.querySelector('[data-review-body]').textContent = body.value.trim() || 'شرح متنی ثبت نشده است.';
|
||||
const fileList = form.querySelector('[data-review-files]');
|
||||
fileList.replaceChildren();
|
||||
const visibleFiles = [...(canonicalFiles?.files || [])].filter(file => !file.type.startsWith('audio/'));
|
||||
visibleFiles.forEach(file => {
|
||||
const chip = document.createElement('span');
|
||||
chip.textContent = file.name;
|
||||
fileList.append(chip);
|
||||
});
|
||||
const reviewAudio = form.querySelector('[data-review-audio]');
|
||||
const voiceApi = voiceRoot?.ticketVoiceRecorder;
|
||||
reviewAudio.hidden = !voiceApi?.hasVoice();
|
||||
if (voiceApi?.url()) reviewAudio.src = voiceApi.url();
|
||||
if (!visibleFiles.length && !voiceApi?.hasVoice()) {
|
||||
const chip = document.createElement('span');
|
||||
chip.textContent = 'بدون پیوست';
|
||||
fileList.append(chip);
|
||||
}
|
||||
}
|
||||
|
||||
async function leaveCurrentStep(targetStep) {
|
||||
const button = form.querySelector(`[data-wizard-step="${currentStep}"] [data-wizard-next]`);
|
||||
if (button) button.disabled = true;
|
||||
if (currentStep === 4) await voiceRoot?.ticketVoiceRecorder?.stopAndSave({ automatic: true });
|
||||
if (button) button.disabled = false;
|
||||
if (!validateStep(currentStep)) return;
|
||||
if (currentStep === 4) buildReview();
|
||||
showStep(targetStep);
|
||||
}
|
||||
|
||||
form.querySelectorAll('[data-wizard-next]').forEach(button => button.addEventListener('click', () => leaveCurrentStep(Math.min(5, currentStep + 1))));
|
||||
form.querySelectorAll('[data-wizard-prev]').forEach(button => button.addEventListener('click', async () => {
|
||||
if (currentStep === 4) await voiceRoot?.ticketVoiceRecorder?.stopAndSave({ automatic: true });
|
||||
showStep(Math.max(1, currentStep - 1));
|
||||
}));
|
||||
form.querySelectorAll('[data-wizard-edit]').forEach(button => button.addEventListener('click', () => showStep(Number(button.dataset.wizardEdit))));
|
||||
|
||||
form.addEventListener('input', event => {
|
||||
event.target.removeAttribute?.('aria-invalid');
|
||||
if (event.target === body) form.querySelector('[data-body-count]').textContent = toPersianDigits(body.value.length);
|
||||
});
|
||||
form.addEventListener('change', event => event.target.removeAttribute?.('aria-invalid'));
|
||||
|
||||
form.addEventListener('submit', event => {
|
||||
const invalidStep = [1, 2, 3, 4].find(number => problemForStep(number));
|
||||
if (!invalidStep) return;
|
||||
event.preventDefault();
|
||||
form.querySelectorAll('button[type="submit"]').forEach(button => { button.disabled = false; });
|
||||
showStep(invalidStep);
|
||||
requestAnimationFrame(() => validateStep(invalidStep));
|
||||
});
|
||||
|
||||
function setupVoiceRecorder(root) {
|
||||
if (!root) return;
|
||||
const ui = {
|
||||
idle: root.querySelector('[data-voice-idle]'), live: root.querySelector('[data-voice-live]'),
|
||||
preview: root.querySelector('[data-voice-preview]'), record: root.querySelector('[data-voice-record]'),
|
||||
pause: root.querySelector('[data-voice-pause]'), stop: root.querySelector('[data-voice-stop]'),
|
||||
cancel: root.querySelector('[data-voice-cancel]'), rerecord: root.querySelector('[data-voice-rerecord]'),
|
||||
remove: root.querySelector('[data-voice-delete]'), audio: root.querySelector('[data-voice-audio]'),
|
||||
timer: root.querySelector('[data-voice-timer]'), status: root.querySelector('[data-voice-status]'),
|
||||
meta: root.querySelector('[data-voice-meta]'), error: root.querySelector('[data-voice-error]'),
|
||||
};
|
||||
let recorder = null;
|
||||
let stream = null;
|
||||
let chunks = [];
|
||||
let voiceFile = null;
|
||||
let voiceUrl = '';
|
||||
let startedAt = 0;
|
||||
let pausedAt = 0;
|
||||
let pausedMs = 0;
|
||||
let timer = null;
|
||||
let stopResolver = null;
|
||||
let discardOnStop = false;
|
||||
let automaticStop = false;
|
||||
|
||||
const elapsed = () => Math.max(0, Date.now() - startedAt - pausedMs - (recorder?.state === 'paused' ? Date.now() - pausedAt : 0));
|
||||
const formatDuration = milliseconds => {
|
||||
const seconds = Math.floor(milliseconds / 1000);
|
||||
return toPersianDigits(`${String(Math.floor(seconds / 60)).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`);
|
||||
};
|
||||
const stopTracks = () => { stream?.getTracks().forEach(track => track.stop()); stream = null; };
|
||||
const clearTimer = () => { clearInterval(timer); timer = null; };
|
||||
const showState = (state, status) => {
|
||||
ui.idle.hidden = state !== 'idle';
|
||||
ui.live.hidden = state !== 'live' && state !== 'paused';
|
||||
ui.preview.hidden = state !== 'preview';
|
||||
root.dataset.state = state;
|
||||
if (status) ui.status.textContent = status;
|
||||
};
|
||||
const removeVoice = ({ notify = true } = {}) => {
|
||||
if (notify && voiceFile) form.dispatchEvent(new CustomEvent('voice-removed', { detail: voiceFile }));
|
||||
voiceFile = null;
|
||||
if (voiceUrl) URL.revokeObjectURL(voiceUrl);
|
||||
voiceUrl = '';
|
||||
ui.audio.removeAttribute('src');
|
||||
ui.timer.textContent = '۰۰:۰۰';
|
||||
ui.pause.textContent = 'مکث';
|
||||
ui.error.hidden = true;
|
||||
showState('idle', 'برای توضیح سریعتر، صدای خود را ضبط کنید.');
|
||||
};
|
||||
const complete = (blob, duration) => {
|
||||
if (!blob.size) {
|
||||
removeVoice();
|
||||
ui.error.textContent = 'صدایی ثبت نشد؛ لطفاً دوباره تلاش کنید.';
|
||||
ui.error.hidden = false;
|
||||
return;
|
||||
}
|
||||
if (voiceUrl) URL.revokeObjectURL(voiceUrl);
|
||||
voiceUrl = URL.createObjectURL(blob);
|
||||
ui.audio.src = voiceUrl;
|
||||
const type = blob.type || 'audio/webm';
|
||||
const extension = type.includes('ogg') ? 'ogg' : type.includes('mp4') ? 'm4a' : type.includes('wav') ? 'wav' : 'webm';
|
||||
voiceFile = new File([blob], `voice-${Date.now()}.${extension}`, { type });
|
||||
form.dispatchEvent(new CustomEvent('voice-added', { detail: voiceFile }));
|
||||
ui.timer.textContent = formatDuration(duration);
|
||||
ui.meta.textContent = `${formatDuration(duration)} · آماده پخش و ارسال`;
|
||||
showState('preview', automaticStop ? 'ضبط هنگام ادامهدادن متوقف و ذخیره شد.' : 'ضبط ذخیره شد؛ پیش از ارسال آن را بررسی کنید.');
|
||||
};
|
||||
const start = async () => {
|
||||
ui.error.hidden = true;
|
||||
if (!window.isSecureContext || !navigator.mediaDevices?.getUserMedia || !window.MediaRecorder) {
|
||||
ui.error.textContent = 'مرورگر یا اتصال فعلی امکان ضبط امن صدا را ندارد.';
|
||||
ui.error.hidden = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (voiceFile) removeVoice();
|
||||
stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } });
|
||||
const mimeType = ['audio/webm;codecs=opus', 'audio/mp4', 'audio/ogg;codecs=opus', 'audio/webm', 'audio/wav'].find(type => MediaRecorder.isTypeSupported?.(type));
|
||||
recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
|
||||
chunks = [];
|
||||
pausedMs = 0;
|
||||
discardOnStop = false;
|
||||
automaticStop = false;
|
||||
recorder.ondataavailable = event => { if (event.data.size) chunks.push(event.data); };
|
||||
recorder.onerror = () => {
|
||||
stopTracks();
|
||||
clearTimer();
|
||||
ui.error.textContent = 'ضبط صدا متوقف شد. دوباره تلاش کنید.';
|
||||
ui.error.hidden = false;
|
||||
showState('idle');
|
||||
};
|
||||
recorder.onstop = () => {
|
||||
const duration = elapsed();
|
||||
clearTimer();
|
||||
stopTracks();
|
||||
if (discardOnStop) removeVoice();
|
||||
else complete(new Blob(chunks, { type: recorder.mimeType?.split(';')[0] || 'audio/webm' }), duration);
|
||||
recorder = null;
|
||||
stopResolver?.();
|
||||
stopResolver = null;
|
||||
};
|
||||
recorder.start(500);
|
||||
startedAt = Date.now();
|
||||
ui.timer.textContent = '۰۰:۰۰';
|
||||
showState('live', 'در حال ضبط؛ واضح و نزدیک به میکروفن صحبت کنید.');
|
||||
timer = setInterval(() => { ui.timer.textContent = formatDuration(elapsed()); }, 250);
|
||||
} catch (error) {
|
||||
stopTracks();
|
||||
ui.error.textContent = error.name === 'NotAllowedError'
|
||||
? 'دسترسی میکروفن رد شد؛ آن را از تنظیمات مرورگر فعال کنید.'
|
||||
: 'میکروفن در دسترس نیست یا برنامه دیگری از آن استفاده میکند.';
|
||||
ui.error.hidden = false;
|
||||
}
|
||||
};
|
||||
const stopAndSave = ({ discard = false, automatic = false } = {}) => {
|
||||
if (!recorder || recorder.state === 'inactive') return Promise.resolve();
|
||||
discardOnStop = discard;
|
||||
automaticStop = automatic;
|
||||
if (recorder.state === 'paused') {
|
||||
pausedMs += Date.now() - pausedAt;
|
||||
recorder.resume();
|
||||
}
|
||||
return new Promise(resolve => { stopResolver = resolve; recorder.stop(); });
|
||||
};
|
||||
const togglePause = () => {
|
||||
if (recorder?.state === 'recording') {
|
||||
recorder.pause();
|
||||
pausedAt = Date.now();
|
||||
ui.pause.textContent = 'ادامه ضبط';
|
||||
showState('paused', 'ضبط موقتاً متوقف شده است.');
|
||||
} else if (recorder?.state === 'paused') {
|
||||
pausedMs += Date.now() - pausedAt;
|
||||
recorder.resume();
|
||||
ui.pause.textContent = 'مکث';
|
||||
showState('live', 'در حال ضبط؛ واضح و نزدیک به میکروفن صحبت کنید.');
|
||||
}
|
||||
};
|
||||
|
||||
ui.record.addEventListener('click', start);
|
||||
ui.pause.addEventListener('click', togglePause);
|
||||
ui.stop.addEventListener('click', () => stopAndSave());
|
||||
ui.cancel.addEventListener('click', () => stopAndSave({ discard: true }));
|
||||
ui.remove.addEventListener('click', removeVoice);
|
||||
ui.rerecord.addEventListener('click', start);
|
||||
root.ticketVoiceRecorder = { stopAndSave, hasVoice: () => Boolean(voiceFile), url: () => voiceUrl };
|
||||
window.addEventListener('pagehide', () => { stopTracks(); clearTimer(); });
|
||||
}
|
||||
|
||||
setupVoiceRecorder(voiceRoot);
|
||||
form.dataset.wizardEnhanced = '';
|
||||
|
||||
const serverErrorStep = steps.find(step => step.querySelector('.errorlist'));
|
||||
const initialStep = serverErrorStep ? Number(serverErrorStep.dataset.wizardStep) : 1;
|
||||
showStep(initialStep, { scroll: false });
|
||||
if (serverErrorStep) {
|
||||
const messages = [...serverErrorStep.querySelectorAll('.errorlist li')].map(item => item.textContent.trim()).filter(Boolean);
|
||||
const number = Number(serverErrorStep.dataset.wizardStep);
|
||||
requestAnimationFrame(() => showValidation(number, { message: messages.join(' '), fields: [] }));
|
||||
}
|
||||
})();
|
||||
Reference in new issue
Block a user