element', () => {
- // https://on.cypress.io/select
-
- // at first, no option should be selected
- cy.get('.action-select')
- .should('have.value', '--Select a fruit--')
-
- // Select option(s) with matching text content
- cy.get('.action-select').select('apples')
- // confirm the apples were selected
- // note that each value starts with "fr-" in our HTML
- cy.get('.action-select').should('have.value', 'fr-apples')
-
- cy.get('.action-select-multiple')
- .select(['apples', 'oranges', 'bananas'])
- // when getting multiple values, invoke "val" method first
- .invoke('val')
- .should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
-
- // Select option(s) with matching value
- cy.get('.action-select').select('fr-bananas')
- // can attach an assertion right away to the element
- .should('have.value', 'fr-bananas')
-
- cy.get('.action-select-multiple')
- .select(['fr-apples', 'fr-oranges', 'fr-bananas'])
- .invoke('val')
- .should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
-
- // assert the selected values include oranges
- cy.get('.action-select-multiple')
- .invoke('val').should('include', 'fr-oranges')
- })
-
- it('.scrollIntoView() - scroll an element into view', () => {
- // https://on.cypress.io/scrollintoview
-
- // normally all of these buttons are hidden,
- // because they're not within
- // the viewable area of their parent
- // (we need to scroll to see them)
- cy.get('#scroll-horizontal button')
- .should('not.be.visible')
-
- // scroll the button into view, as if the user had scrolled
- cy.get('#scroll-horizontal button').scrollIntoView()
- .should('be.visible')
-
- cy.get('#scroll-vertical button')
- .should('not.be.visible')
-
- // Cypress handles the scroll direction needed
- cy.get('#scroll-vertical button').scrollIntoView()
- .should('be.visible')
-
- cy.get('#scroll-both button')
- .should('not.be.visible')
-
- // Cypress knows to scroll to the right and down
- cy.get('#scroll-both button').scrollIntoView()
- .should('be.visible')
- })
-
- it('.trigger() - trigger an event on a DOM element', () => {
- // https://on.cypress.io/trigger
-
- // To interact with a range input (slider)
- // we need to set its value & trigger the
- // event to signal it changed
-
- // Here, we invoke jQuery's val() method to set
- // the value and trigger the 'change' event
- cy.get('.trigger-input-range')
- .invoke('val', 25)
- .trigger('change')
- .get('input[type=range]').siblings('p')
- .should('have.text', '25')
- })
-
- it('cy.scrollTo() - scroll the window or element to a position', () => {
- // https://on.cypress.io/scrollto
-
- // You can scroll to 9 specific positions of an element:
- // -----------------------------------
- // | topLeft top topRight |
- // | |
- // | |
- // | |
- // | left center right |
- // | |
- // | |
- // | |
- // | bottomLeft bottom bottomRight |
- // -----------------------------------
-
- // if you chain .scrollTo() off of cy, we will
- // scroll the entire window
- cy.scrollTo('bottom')
-
- cy.get('#scrollable-horizontal').scrollTo('right')
-
- // or you can scroll to a specific coordinate:
- // (x axis, y axis) in pixels
- cy.get('#scrollable-vertical').scrollTo(250, 250)
-
- // or you can scroll to a specific percentage
- // of the (width, height) of the element
- cy.get('#scrollable-both').scrollTo('75%', '25%')
-
- // control the easing of the scroll (default is 'swing')
- cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' })
-
- // control the duration of the scroll (in ms)
- cy.get('#scrollable-both').scrollTo('center', { duration: 2000 })
- })
-})
diff --git a/cypress/integration/2-advanced-examples/aliasing.spec.js b/cypress/integration/2-advanced-examples/aliasing.spec.js
deleted file mode 100644
index a02fb2bb93d..00000000000
--- a/cypress/integration/2-advanced-examples/aliasing.spec.js
+++ /dev/null
@@ -1,39 +0,0 @@
-///
-
-context('Aliasing', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/aliasing')
- })
-
- it('.as() - alias a DOM element for later use', () => {
- // https://on.cypress.io/as
-
- // Alias a DOM element for use later
- // We don't have to traverse to the element
- // later in our code, we reference it with @
-
- cy.get('.as-table').find('tbody>tr')
- .first().find('td').first()
- .find('button').as('firstBtn')
-
- // when we reference the alias, we place an
- // @ in front of its name
- cy.get('@firstBtn').click()
-
- cy.get('@firstBtn')
- .should('have.class', 'btn-success')
- .and('contain', 'Changed')
- })
-
- it('.as() - alias a route for later use', () => {
- // Alias the route to wait for its response
- cy.intercept('GET', '**/comments/*').as('getComment')
-
- // we have code that gets a comment when
- // the button is clicked in scripts.js
- cy.get('.network-btn').click()
-
- // https://on.cypress.io/wait
- cy.wait('@getComment').its('response.statusCode').should('eq', 200)
- })
-})
diff --git a/cypress/integration/2-advanced-examples/assertions.spec.js b/cypress/integration/2-advanced-examples/assertions.spec.js
deleted file mode 100644
index 5ba93d1db6d..00000000000
--- a/cypress/integration/2-advanced-examples/assertions.spec.js
+++ /dev/null
@@ -1,177 +0,0 @@
-///
-
-context('Assertions', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/assertions')
- })
-
- describe('Implicit Assertions', () => {
- it('.should() - make an assertion about the current subject', () => {
- // https://on.cypress.io/should
- cy.get('.assertion-table')
- .find('tbody tr:last')
- .should('have.class', 'success')
- .find('td')
- .first()
- // checking the text of the element in various ways
- .should('have.text', 'Column content')
- .should('contain', 'Column content')
- .should('have.html', 'Column content')
- // chai-jquery uses "is()" to check if element matches selector
- .should('match', 'td')
- // to match text content against a regular expression
- // first need to invoke jQuery method text()
- // and then match using regular expression
- .invoke('text')
- .should('match', /column content/i)
-
- // a better way to check element's text content against a regular expression
- // is to use "cy.contains"
- // https://on.cypress.io/contains
- cy.get('.assertion-table')
- .find('tbody tr:last')
- // finds first element with text content matching regular expression
- .contains('td', /column content/i)
- .should('be.visible')
-
- // for more information about asserting element's text
- // see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-elementโs-text-contents
- })
-
- it('.and() - chain multiple assertions together', () => {
- // https://on.cypress.io/and
- cy.get('.assertions-link')
- .should('have.class', 'active')
- .and('have.attr', 'href')
- .and('include', 'cypress.io')
- })
- })
-
- describe('Explicit Assertions', () => {
- // https://on.cypress.io/assertions
- it('expect - make an assertion about a specified subject', () => {
- // We can use Chai's BDD style assertions
- expect(true).to.be.true
- const o = { foo: 'bar' }
-
- expect(o).to.equal(o)
- expect(o).to.deep.equal({ foo: 'bar' })
- // matching text using regular expression
- expect('FooBar').to.match(/bar$/i)
- })
-
- it('pass your own callback function to should()', () => {
- // Pass a function to should that can have any number
- // of explicit assertions within it.
- // The ".should(cb)" function will be retried
- // automatically until it passes all your explicit assertions or times out.
- cy.get('.assertions-p')
- .find('p')
- .should(($p) => {
- // https://on.cypress.io/$
- // return an array of texts from all of the p's
- // @ts-ignore TS6133 unused variable
- const texts = $p.map((i, el) => Cypress.$(el).text())
-
- // jquery map returns jquery object
- // and .get() convert this to simple array
- const paragraphs = texts.get()
-
- // array should have length of 3
- expect(paragraphs, 'has 3 paragraphs').to.have.length(3)
-
- // use second argument to expect(...) to provide clear
- // message with each assertion
- expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([
- 'Some text from first p',
- 'More text from second p',
- 'And even more text from third p',
- ])
- })
- })
-
- it('finds element by class name regex', () => {
- cy.get('.docs-header')
- .find('div')
- // .should(cb) callback function will be retried
- .should(($div) => {
- expect($div).to.have.length(1)
-
- const className = $div[0].className
-
- expect(className).to.match(/heading-/)
- })
- // .then(cb) callback is not retried,
- // it either passes or fails
- .then(($div) => {
- expect($div, 'text content').to.have.text('Introduction')
- })
- })
-
- it('can throw any error', () => {
- cy.get('.docs-header')
- .find('div')
- .should(($div) => {
- if ($div.length !== 1) {
- // you can throw your own errors
- throw new Error('Did not find 1 element')
- }
-
- const className = $div[0].className
-
- if (!className.match(/heading-/)) {
- throw new Error(`Could not find class "heading-" in ${className}`)
- }
- })
- })
-
- it('matches unknown text between two elements', () => {
- /**
- * Text from the first element.
- * @type {string}
- */
- let text
-
- /**
- * Normalizes passed text,
- * useful before comparing text with spaces and different capitalization.
- * @param {string} s Text to normalize
- */
- const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase()
-
- cy.get('.two-elements')
- .find('.first')
- .then(($first) => {
- // save text from the first element
- text = normalizeText($first.text())
- })
-
- cy.get('.two-elements')
- .find('.second')
- .should(($div) => {
- // we can massage text before comparing
- const secondText = normalizeText($div.text())
-
- expect(secondText, 'second text').to.equal(text)
- })
- })
-
- it('assert - assert shape of an object', () => {
- const person = {
- name: 'Joe',
- age: 20,
- }
-
- assert.isObject(person, 'value is object')
- })
-
- it('retries the should callback until assertions pass', () => {
- cy.get('#random-number')
- .should(($div) => {
- const n = parseFloat($div.text())
-
- expect(n).to.be.gte(1).and.be.lte(10)
- })
- })
- })
-})
diff --git a/cypress/integration/2-advanced-examples/connectors.spec.js b/cypress/integration/2-advanced-examples/connectors.spec.js
deleted file mode 100644
index ae8799181d1..00000000000
--- a/cypress/integration/2-advanced-examples/connectors.spec.js
+++ /dev/null
@@ -1,97 +0,0 @@
-///
-
-context('Connectors', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/connectors')
- })
-
- it('.each() - iterate over an array of elements', () => {
- // https://on.cypress.io/each
- cy.get('.connectors-each-ul>li')
- .each(($el, index, $list) => {
- console.log($el, index, $list)
- })
- })
-
- it('.its() - get properties on the current subject', () => {
- // https://on.cypress.io/its
- cy.get('.connectors-its-ul>li')
- // calls the 'length' property yielding that value
- .its('length')
- .should('be.gt', 2)
- })
-
- it('.invoke() - invoke a function on the current subject', () => {
- // our div is hidden in our script.js
- // $('.connectors-div').hide()
-
- // https://on.cypress.io/invoke
- cy.get('.connectors-div').should('be.hidden')
- // call the jquery method 'show' on the 'div.container'
- .invoke('show')
- .should('be.visible')
- })
-
- it('.spread() - spread an array as individual args to callback function', () => {
- // https://on.cypress.io/spread
- const arr = ['foo', 'bar', 'baz']
-
- cy.wrap(arr).spread((foo, bar, baz) => {
- expect(foo).to.eq('foo')
- expect(bar).to.eq('bar')
- expect(baz).to.eq('baz')
- })
- })
-
- describe('.then()', () => {
- it('invokes a callback function with the current subject', () => {
- // https://on.cypress.io/then
- cy.get('.connectors-list > li')
- .then(($lis) => {
- expect($lis, '3 items').to.have.length(3)
- expect($lis.eq(0), 'first item').to.contain('Walk the dog')
- expect($lis.eq(1), 'second item').to.contain('Feed the cat')
- expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
- })
- })
-
- it('yields the returned value to the next command', () => {
- cy.wrap(1)
- .then((num) => {
- expect(num).to.equal(1)
-
- return 2
- })
- .then((num) => {
- expect(num).to.equal(2)
- })
- })
-
- it('yields the original subject without return', () => {
- cy.wrap(1)
- .then((num) => {
- expect(num).to.equal(1)
- // note that nothing is returned from this callback
- })
- .then((num) => {
- // this callback receives the original unchanged value 1
- expect(num).to.equal(1)
- })
- })
-
- it('yields the value yielded by the last Cypress command inside', () => {
- cy.wrap(1)
- .then((num) => {
- expect(num).to.equal(1)
- // note how we run a Cypress command
- // the result yielded by this Cypress command
- // will be passed to the second ".then"
- cy.wrap(2)
- })
- .then((num) => {
- // this callback receives the value yielded by "cy.wrap(2)"
- expect(num).to.equal(2)
- })
- })
- })
-})
diff --git a/cypress/integration/2-advanced-examples/cookies.spec.js b/cypress/integration/2-advanced-examples/cookies.spec.js
deleted file mode 100644
index 31587ff907d..00000000000
--- a/cypress/integration/2-advanced-examples/cookies.spec.js
+++ /dev/null
@@ -1,77 +0,0 @@
-///
-
-context('Cookies', () => {
- beforeEach(() => {
- Cypress.Cookies.debug(true)
-
- cy.visit('https://example.cypress.io/commands/cookies')
-
- // clear cookies again after visiting to remove
- // any 3rd party cookies picked up such as cloudflare
- cy.clearCookies()
- })
-
- it('cy.getCookie() - get a browser cookie', () => {
- // https://on.cypress.io/getcookie
- cy.get('#getCookie .set-a-cookie').click()
-
- // cy.getCookie() yields a cookie object
- cy.getCookie('token').should('have.property', 'value', '123ABC')
- })
-
- it('cy.getCookies() - get browser cookies', () => {
- // https://on.cypress.io/getcookies
- cy.getCookies().should('be.empty')
-
- cy.get('#getCookies .set-a-cookie').click()
-
- // cy.getCookies() yields an array of cookies
- cy.getCookies().should('have.length', 1).should((cookies) => {
- // each cookie has these properties
- expect(cookies[0]).to.have.property('name', 'token')
- expect(cookies[0]).to.have.property('value', '123ABC')
- expect(cookies[0]).to.have.property('httpOnly', false)
- expect(cookies[0]).to.have.property('secure', false)
- expect(cookies[0]).to.have.property('domain')
- expect(cookies[0]).to.have.property('path')
- })
- })
-
- it('cy.setCookie() - set a browser cookie', () => {
- // https://on.cypress.io/setcookie
- cy.getCookies().should('be.empty')
-
- cy.setCookie('foo', 'bar')
-
- // cy.getCookie() yields a cookie object
- cy.getCookie('foo').should('have.property', 'value', 'bar')
- })
-
- it('cy.clearCookie() - clear a browser cookie', () => {
- // https://on.cypress.io/clearcookie
- cy.getCookie('token').should('be.null')
-
- cy.get('#clearCookie .set-a-cookie').click()
-
- cy.getCookie('token').should('have.property', 'value', '123ABC')
-
- // cy.clearCookies() yields null
- cy.clearCookie('token').should('be.null')
-
- cy.getCookie('token').should('be.null')
- })
-
- it('cy.clearCookies() - clear browser cookies', () => {
- // https://on.cypress.io/clearcookies
- cy.getCookies().should('be.empty')
-
- cy.get('#clearCookies .set-a-cookie').click()
-
- cy.getCookies().should('have.length', 1)
-
- // cy.clearCookies() yields null
- cy.clearCookies()
-
- cy.getCookies().should('be.empty')
- })
-})
diff --git a/cypress/integration/2-advanced-examples/cypress_api.spec.js b/cypress/integration/2-advanced-examples/cypress_api.spec.js
deleted file mode 100644
index ec8ceaeda4e..00000000000
--- a/cypress/integration/2-advanced-examples/cypress_api.spec.js
+++ /dev/null
@@ -1,202 +0,0 @@
-///
-
-context('Cypress.Commands', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- // https://on.cypress.io/custom-commands
-
- it('.add() - create a custom command', () => {
- Cypress.Commands.add('console', {
- prevSubject: true,
- }, (subject, method) => {
- // the previous subject is automatically received
- // and the commands arguments are shifted
-
- // allow us to change the console method used
- method = method || 'log'
-
- // log the subject to the console
- // @ts-ignore TS7017
- console[method]('The subject is', subject)
-
- // whatever we return becomes the new subject
- // we don't want to change the subject so
- // we return whatever was passed in
- return subject
- })
-
- // @ts-ignore TS2339
- cy.get('button').console('info').then(($button) => {
- // subject is still $button
- })
- })
-})
-
-context('Cypress.Cookies', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- // https://on.cypress.io/cookies
- it('.debug() - enable or disable debugging', () => {
- Cypress.Cookies.debug(true)
-
- // Cypress will now log in the console when
- // cookies are set or cleared
- cy.setCookie('fakeCookie', '123ABC')
- cy.clearCookie('fakeCookie')
- cy.setCookie('fakeCookie', '123ABC')
- cy.clearCookie('fakeCookie')
- cy.setCookie('fakeCookie', '123ABC')
- })
-
- it('.preserveOnce() - preserve cookies by key', () => {
- // normally cookies are reset after each test
- cy.getCookie('fakeCookie').should('not.be.ok')
-
- // preserving a cookie will not clear it when
- // the next test starts
- cy.setCookie('lastCookie', '789XYZ')
- Cypress.Cookies.preserveOnce('lastCookie')
- })
-
- it('.defaults() - set defaults for all cookies', () => {
- // now any cookie with the name 'session_id' will
- // not be cleared before each new test runs
- Cypress.Cookies.defaults({
- preserve: 'session_id',
- })
- })
-})
-
-context('Cypress.arch', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- it('Get CPU architecture name of underlying OS', () => {
- // https://on.cypress.io/arch
- expect(Cypress.arch).to.exist
- })
-})
-
-context('Cypress.config()', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- it('Get and set configuration options', () => {
- // https://on.cypress.io/config
- let myConfig = Cypress.config()
-
- expect(myConfig).to.have.property('animationDistanceThreshold', 5)
- expect(myConfig).to.have.property('baseUrl', null)
- expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
- expect(myConfig).to.have.property('requestTimeout', 5000)
- expect(myConfig).to.have.property('responseTimeout', 30000)
- expect(myConfig).to.have.property('viewportHeight', 660)
- expect(myConfig).to.have.property('viewportWidth', 1000)
- expect(myConfig).to.have.property('pageLoadTimeout', 60000)
- expect(myConfig).to.have.property('waitForAnimations', true)
-
- expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
-
- // this will change the config for the rest of your tests!
- Cypress.config('pageLoadTimeout', 20000)
-
- expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
-
- Cypress.config('pageLoadTimeout', 60000)
- })
-})
-
-context('Cypress.dom', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- // https://on.cypress.io/dom
- it('.isHidden() - determine if a DOM element is hidden', () => {
- let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
- let visibleP = Cypress.$('.dom-p p.visible').get(0)
-
- // our first paragraph has css class 'hidden'
- expect(Cypress.dom.isHidden(hiddenP)).to.be.true
- expect(Cypress.dom.isHidden(visibleP)).to.be.false
- })
-})
-
-context('Cypress.env()', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- // We can set environment variables for highly dynamic values
-
- // https://on.cypress.io/environment-variables
- it('Get environment variables', () => {
- // https://on.cypress.io/env
- // set multiple environment variables
- Cypress.env({
- host: 'veronica.dev.local',
- api_server: 'http://localhost:8888/v1/',
- })
-
- // get environment variable
- expect(Cypress.env('host')).to.eq('veronica.dev.local')
-
- // set environment variable
- Cypress.env('api_server', 'http://localhost:8888/v2/')
- expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
-
- // get all environment variable
- expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
- expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
- })
-})
-
-context('Cypress.log', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- it('Control what is printed to the Command Log', () => {
- // https://on.cypress.io/cypress-log
- })
-})
-
-context('Cypress.platform', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- it('Get underlying OS name', () => {
- // https://on.cypress.io/platform
- expect(Cypress.platform).to.be.exist
- })
-})
-
-context('Cypress.version', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- it('Get current version of Cypress being run', () => {
- // https://on.cypress.io/version
- expect(Cypress.version).to.be.exist
- })
-})
-
-context('Cypress.spec', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/cypress-api')
- })
-
- it('Get current spec information', () => {
- // https://on.cypress.io/spec
- // wrap the object so we can inspect it easily by clicking in the command log
- cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute'])
- })
-})
diff --git a/cypress/integration/2-advanced-examples/files.spec.js b/cypress/integration/2-advanced-examples/files.spec.js
deleted file mode 100644
index b8273430c49..00000000000
--- a/cypress/integration/2-advanced-examples/files.spec.js
+++ /dev/null
@@ -1,88 +0,0 @@
-///
-
-/// JSON fixture file can be loaded directly using
-// the built-in JavaScript bundler
-// @ts-ignore
-const requiredExample = require('../../fixtures/example')
-
-context('Files', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/files')
- })
-
- beforeEach(() => {
- // load example.json fixture file and store
- // in the test context object
- cy.fixture('example.json').as('example')
- })
-
- it('cy.fixture() - load a fixture', () => {
- // https://on.cypress.io/fixture
-
- // Instead of writing a response inline you can
- // use a fixture file's content.
-
- // when application makes an Ajax request matching "GET **/comments/*"
- // Cypress will intercept it and reply with the object in `example.json` fixture
- cy.intercept('GET', '**/comments/*', { fixture: 'example.json' }).as('getComment')
-
- // we have code that gets a comment when
- // the button is clicked in scripts.js
- cy.get('.fixture-btn').click()
-
- cy.wait('@getComment').its('response.body')
- .should('have.property', 'name')
- .and('include', 'Using fixtures to represent data')
- })
-
- it('cy.fixture() or require - load a fixture', function () {
- // we are inside the "function () { ... }"
- // callback and can use test context object "this"
- // "this.example" was loaded in "beforeEach" function callback
- expect(this.example, 'fixture in the test context')
- .to.deep.equal(requiredExample)
-
- // or use "cy.wrap" and "should('deep.equal', ...)" assertion
- cy.wrap(this.example)
- .should('deep.equal', requiredExample)
- })
-
- it('cy.readFile() - read file contents', () => {
- // https://on.cypress.io/readfile
-
- // You can read a file and yield its contents
- // The filePath is relative to your project's root.
- cy.readFile('cypress.json').then((json) => {
- expect(json).to.be.an('object')
- })
- })
-
- it('cy.writeFile() - write to a file', () => {
- // https://on.cypress.io/writefile
-
- // You can write to a file
-
- // Use a response from a request to automatically
- // generate a fixture file for use later
- cy.request('https://jsonplaceholder.cypress.io/users')
- .then((response) => {
- cy.writeFile('cypress/fixtures/users.json', response.body)
- })
-
- cy.fixture('users').should((users) => {
- expect(users[0].name).to.exist
- })
-
- // JavaScript arrays and objects are stringified
- // and formatted into text.
- cy.writeFile('cypress/fixtures/profile.json', {
- id: 8739,
- name: 'Jane',
- email: 'jane@example.com',
- })
-
- cy.fixture('profile').should((profile) => {
- expect(profile.name).to.eq('Jane')
- })
- })
-})
diff --git a/cypress/integration/2-advanced-examples/local_storage.spec.js b/cypress/integration/2-advanced-examples/local_storage.spec.js
deleted file mode 100644
index 534d8bd9d33..00000000000
--- a/cypress/integration/2-advanced-examples/local_storage.spec.js
+++ /dev/null
@@ -1,52 +0,0 @@
-///
-
-context('Local Storage', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/local-storage')
- })
- // Although local storage is automatically cleared
- // in between tests to maintain a clean state
- // sometimes we need to clear the local storage manually
-
- it('cy.clearLocalStorage() - clear all data in local storage', () => {
- // https://on.cypress.io/clearlocalstorage
- cy.get('.ls-btn').click().should(() => {
- expect(localStorage.getItem('prop1')).to.eq('red')
- expect(localStorage.getItem('prop2')).to.eq('blue')
- expect(localStorage.getItem('prop3')).to.eq('magenta')
- })
-
- // clearLocalStorage() yields the localStorage object
- cy.clearLocalStorage().should((ls) => {
- expect(ls.getItem('prop1')).to.be.null
- expect(ls.getItem('prop2')).to.be.null
- expect(ls.getItem('prop3')).to.be.null
- })
-
- cy.get('.ls-btn').click().should(() => {
- expect(localStorage.getItem('prop1')).to.eq('red')
- expect(localStorage.getItem('prop2')).to.eq('blue')
- expect(localStorage.getItem('prop3')).to.eq('magenta')
- })
-
- // Clear key matching string in Local Storage
- cy.clearLocalStorage('prop1').should((ls) => {
- expect(ls.getItem('prop1')).to.be.null
- expect(ls.getItem('prop2')).to.eq('blue')
- expect(ls.getItem('prop3')).to.eq('magenta')
- })
-
- cy.get('.ls-btn').click().should(() => {
- expect(localStorage.getItem('prop1')).to.eq('red')
- expect(localStorage.getItem('prop2')).to.eq('blue')
- expect(localStorage.getItem('prop3')).to.eq('magenta')
- })
-
- // Clear keys matching regex in Local Storage
- cy.clearLocalStorage(/prop1|2/).should((ls) => {
- expect(ls.getItem('prop1')).to.be.null
- expect(ls.getItem('prop2')).to.be.null
- expect(ls.getItem('prop3')).to.eq('magenta')
- })
- })
-})
diff --git a/cypress/integration/2-advanced-examples/location.spec.js b/cypress/integration/2-advanced-examples/location.spec.js
deleted file mode 100644
index 299867da07e..00000000000
--- a/cypress/integration/2-advanced-examples/location.spec.js
+++ /dev/null
@@ -1,32 +0,0 @@
-///
-
-context('Location', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/location')
- })
-
- it('cy.hash() - get the current URL hash', () => {
- // https://on.cypress.io/hash
- cy.hash().should('be.empty')
- })
-
- it('cy.location() - get window.location', () => {
- // https://on.cypress.io/location
- cy.location().should((location) => {
- expect(location.hash).to.be.empty
- expect(location.href).to.eq('https://example.cypress.io/commands/location')
- expect(location.host).to.eq('example.cypress.io')
- expect(location.hostname).to.eq('example.cypress.io')
- expect(location.origin).to.eq('https://example.cypress.io')
- expect(location.pathname).to.eq('/commands/location')
- expect(location.port).to.eq('')
- expect(location.protocol).to.eq('https:')
- expect(location.search).to.be.empty
- })
- })
-
- it('cy.url() - get the current URL', () => {
- // https://on.cypress.io/url
- cy.url().should('eq', 'https://example.cypress.io/commands/location')
- })
-})
diff --git a/cypress/integration/2-advanced-examples/misc.spec.js b/cypress/integration/2-advanced-examples/misc.spec.js
deleted file mode 100644
index 7222bf4bd1c..00000000000
--- a/cypress/integration/2-advanced-examples/misc.spec.js
+++ /dev/null
@@ -1,104 +0,0 @@
-///
-
-context('Misc', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/misc')
- })
-
- it('.end() - end the command chain', () => {
- // https://on.cypress.io/end
-
- // cy.end is useful when you want to end a chain of commands
- // and force Cypress to re-query from the root element
- cy.get('.misc-table').within(() => {
- // ends the current chain and yields null
- cy.contains('Cheryl').click().end()
-
- // queries the entire table again
- cy.contains('Charles').click()
- })
- })
-
- it('cy.exec() - execute a system command', () => {
- // execute a system command.
- // so you can take actions necessary for
- // your test outside the scope of Cypress.
- // https://on.cypress.io/exec
-
- // we can use Cypress.platform string to
- // select appropriate command
- // https://on.cypress/io/platform
- cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`)
-
- // on CircleCI Windows build machines we have a failure to run bash shell
- // https://github.com/cypress-io/cypress/issues/5169
- // so skip some of the tests by passing flag "--env circle=true"
- const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle')
-
- if (isCircleOnWindows) {
- cy.log('Skipping test on CircleCI')
-
- return
- }
-
- // cy.exec problem on Shippable CI
- // https://github.com/cypress-io/cypress/issues/6718
- const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable')
-
- if (isShippable) {
- cy.log('Skipping test on ShippableCI')
-
- return
- }
-
- cy.exec('echo Jane Lane')
- .its('stdout').should('contain', 'Jane Lane')
-
- if (Cypress.platform === 'win32') {
- cy.exec('print cypress.json')
- .its('stderr').should('be.empty')
- } else {
- cy.exec('cat cypress.json')
- .its('stderr').should('be.empty')
-
- cy.exec('pwd')
- .its('code').should('eq', 0)
- }
- })
-
- it('cy.focused() - get the DOM element that has focus', () => {
- // https://on.cypress.io/focused
- cy.get('.misc-form').find('#name').click()
- cy.focused().should('have.id', 'name')
-
- cy.get('.misc-form').find('#description').click()
- cy.focused().should('have.id', 'description')
- })
-
- context('Cypress.Screenshot', function () {
- it('cy.screenshot() - take a screenshot', () => {
- // https://on.cypress.io/screenshot
- cy.screenshot('my-image')
- })
-
- it('Cypress.Screenshot.defaults() - change default config of screenshots', function () {
- Cypress.Screenshot.defaults({
- blackout: ['.foo'],
- capture: 'viewport',
- clip: { x: 0, y: 0, width: 200, height: 200 },
- scale: false,
- disableTimersAndAnimations: true,
- screenshotOnRunFailure: true,
- onBeforeScreenshot () { },
- onAfterScreenshot () { },
- })
- })
- })
-
- it('cy.wrap() - wrap an object', () => {
- // https://on.cypress.io/wrap
- cy.wrap({ foo: 'bar' })
- .should('have.property', 'foo')
- .and('include', 'bar')
- })
-})
diff --git a/cypress/integration/2-advanced-examples/navigation.spec.js b/cypress/integration/2-advanced-examples/navigation.spec.js
deleted file mode 100644
index b85a46890c8..00000000000
--- a/cypress/integration/2-advanced-examples/navigation.spec.js
+++ /dev/null
@@ -1,56 +0,0 @@
-///
-
-context('Navigation', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io')
- cy.get('.navbar-nav').contains('Commands').click()
- cy.get('.dropdown-menu').contains('Navigation').click()
- })
-
- it('cy.go() - go back or forward in the browser\'s history', () => {
- // https://on.cypress.io/go
-
- cy.location('pathname').should('include', 'navigation')
-
- cy.go('back')
- cy.location('pathname').should('not.include', 'navigation')
-
- cy.go('forward')
- cy.location('pathname').should('include', 'navigation')
-
- // clicking back
- cy.go(-1)
- cy.location('pathname').should('not.include', 'navigation')
-
- // clicking forward
- cy.go(1)
- cy.location('pathname').should('include', 'navigation')
- })
-
- it('cy.reload() - reload the page', () => {
- // https://on.cypress.io/reload
- cy.reload()
-
- // reload the page without using the cache
- cy.reload(true)
- })
-
- it('cy.visit() - visit a remote url', () => {
- // https://on.cypress.io/visit
-
- // Visit any sub-domain of your current domain
-
- // Pass options to the visit
- cy.visit('https://example.cypress.io/commands/navigation', {
- timeout: 50000, // increase total time for the visit to resolve
- onBeforeLoad (contentWindow) {
- // contentWindow is the remote page's window object
- expect(typeof contentWindow === 'object').to.be.true
- },
- onLoad (contentWindow) {
- // contentWindow is the remote page's window object
- expect(typeof contentWindow === 'object').to.be.true
- },
- })
- })
-})
diff --git a/cypress/integration/2-advanced-examples/network_requests.spec.js b/cypress/integration/2-advanced-examples/network_requests.spec.js
deleted file mode 100644
index 11213a0e852..00000000000
--- a/cypress/integration/2-advanced-examples/network_requests.spec.js
+++ /dev/null
@@ -1,163 +0,0 @@
-///
-
-context('Network Requests', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/network-requests')
- })
-
- // Manage HTTP requests in your app
-
- it('cy.request() - make an XHR request', () => {
- // https://on.cypress.io/request
- cy.request('https://jsonplaceholder.cypress.io/comments')
- .should((response) => {
- expect(response.status).to.eq(200)
- // the server sometimes gets an extra comment posted from another machine
- // which gets returned as 1 extra object
- expect(response.body).to.have.property('length').and.be.oneOf([500, 501])
- expect(response).to.have.property('headers')
- expect(response).to.have.property('duration')
- })
- })
-
- it('cy.request() - verify response using BDD syntax', () => {
- cy.request('https://jsonplaceholder.cypress.io/comments')
- .then((response) => {
- // https://on.cypress.io/assertions
- expect(response).property('status').to.equal(200)
- expect(response).property('body').to.have.property('length').and.be.oneOf([500, 501])
- expect(response).to.include.keys('headers', 'duration')
- })
- })
-
- it('cy.request() with query parameters', () => {
- // will execute request
- // https://jsonplaceholder.cypress.io/comments?postId=1&id=3
- cy.request({
- url: 'https://jsonplaceholder.cypress.io/comments',
- qs: {
- postId: 1,
- id: 3,
- },
- })
- .its('body')
- .should('be.an', 'array')
- .and('have.length', 1)
- .its('0') // yields first element of the array
- .should('contain', {
- postId: 1,
- id: 3,
- })
- })
-
- it('cy.request() - pass result to the second request', () => {
- // first, let's find out the userId of the first user we have
- cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
- .its('body') // yields the response object
- .its('0') // yields the first element of the returned list
- // the above two commands its('body').its('0')
- // can be written as its('body.0')
- // if you do not care about TypeScript checks
- .then((user) => {
- expect(user).property('id').to.be.a('number')
- // make a new post on behalf of the user
- cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
- userId: user.id,
- title: 'Cypress Test Runner',
- body: 'Fast, easy and reliable testing for anything that runs in a browser.',
- })
- })
- // note that the value here is the returned value of the 2nd request
- // which is the new post object
- .then((response) => {
- expect(response).property('status').to.equal(201) // new entity created
- expect(response).property('body').to.contain({
- title: 'Cypress Test Runner',
- })
-
- // we don't know the exact post id - only that it will be > 100
- // since JSONPlaceholder has built-in 100 posts
- expect(response.body).property('id').to.be.a('number')
- .and.to.be.gt(100)
-
- // we don't know the user id here - since it was in above closure
- // so in this test just confirm that the property is there
- expect(response.body).property('userId').to.be.a('number')
- })
- })
-
- it('cy.request() - save response in the shared test context', () => {
- // https://on.cypress.io/variables-and-aliases
- cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
- .its('body').its('0') // yields the first element of the returned list
- .as('user') // saves the object in the test context
- .then(function () {
- // NOTE ๐
- // By the time this callback runs the "as('user')" command
- // has saved the user object in the test context.
- // To access the test context we need to use
- // the "function () { ... }" callback form,
- // otherwise "this" points at a wrong or undefined object!
- cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
- userId: this.user.id,
- title: 'Cypress Test Runner',
- body: 'Fast, easy and reliable testing for anything that runs in a browser.',
- })
- .its('body').as('post') // save the new post from the response
- })
- .then(function () {
- // When this callback runs, both "cy.request" API commands have finished
- // and the test context has "user" and "post" objects set.
- // Let's verify them.
- expect(this.post, 'post has the right user id').property('userId').to.equal(this.user.id)
- })
- })
-
- it('cy.intercept() - route responses to matching requests', () => {
- // https://on.cypress.io/intercept
-
- let message = 'whoa, this comment does not exist'
-
- // Listen to GET to comments/1
- cy.intercept('GET', '**/comments/*').as('getComment')
-
- // we have code that gets a comment when
- // the button is clicked in scripts.js
- cy.get('.network-btn').click()
-
- // https://on.cypress.io/wait
- cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
-
- // Listen to POST to comments
- cy.intercept('POST', '**/comments').as('postComment')
-
- // we have code that posts a comment when
- // the button is clicked in scripts.js
- cy.get('.network-post').click()
- cy.wait('@postComment').should(({ request, response }) => {
- expect(request.body).to.include('email')
- expect(request.headers).to.have.property('content-type')
- expect(response && response.body).to.have.property('name', 'Using POST in cy.intercept()')
- })
-
- // Stub a response to PUT comments/ ****
- cy.intercept({
- method: 'PUT',
- url: '**/comments/*',
- }, {
- statusCode: 404,
- body: { error: message },
- headers: { 'access-control-allow-origin': '*' },
- delayMs: 500,
- }).as('putComment')
-
- // we have code that puts a comment when
- // the button is clicked in scripts.js
- cy.get('.network-put').click()
-
- cy.wait('@putComment')
-
- // our 404 statusCode logic in scripts.js executed
- cy.get('.network-put-comment').should('contain', message)
- })
-})
diff --git a/cypress/integration/2-advanced-examples/querying.spec.js b/cypress/integration/2-advanced-examples/querying.spec.js
deleted file mode 100644
index 00970480f6c..00000000000
--- a/cypress/integration/2-advanced-examples/querying.spec.js
+++ /dev/null
@@ -1,114 +0,0 @@
-///
-
-context('Querying', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/querying')
- })
-
- // The most commonly used query is 'cy.get()', you can
- // think of this like the '$' in jQuery
-
- it('cy.get() - query DOM elements', () => {
- // https://on.cypress.io/get
-
- cy.get('#query-btn').should('contain', 'Button')
-
- cy.get('.query-btn').should('contain', 'Button')
-
- cy.get('#querying .well>button:first').should('contain', 'Button')
- // โฒ
- // Use CSS selectors just like jQuery
-
- cy.get('[data-test-id="test-example"]').should('have.class', 'example')
-
- // 'cy.get()' yields jQuery object, you can get its attribute
- // by invoking `.attr()` method
- cy.get('[data-test-id="test-example"]')
- .invoke('attr', 'data-test-id')
- .should('equal', 'test-example')
-
- // or you can get element's CSS property
- cy.get('[data-test-id="test-example"]')
- .invoke('css', 'position')
- .should('equal', 'static')
-
- // or use assertions directly during 'cy.get()'
- // https://on.cypress.io/assertions
- cy.get('[data-test-id="test-example"]')
- .should('have.attr', 'data-test-id', 'test-example')
- .and('have.css', 'position', 'static')
- })
-
- it('cy.contains() - query DOM elements with matching content', () => {
- // https://on.cypress.io/contains
- cy.get('.query-list')
- .contains('bananas')
- .should('have.class', 'third')
-
- // we can pass a regexp to `.contains()`
- cy.get('.query-list')
- .contains(/^b\w+/)
- .should('have.class', 'third')
-
- cy.get('.query-list')
- .contains('apples')
- .should('have.class', 'first')
-
- // passing a selector to contains will
- // yield the selector containing the text
- cy.get('#querying')
- .contains('ul', 'oranges')
- .should('have.class', 'query-list')
-
- cy.get('.query-button')
- .contains('Save Form')
- .should('have.class', 'btn')
- })
-
- it('.within() - query DOM elements within a specific element', () => {
- // https://on.cypress.io/within
- cy.get('.query-form').within(() => {
- cy.get('input:first').should('have.attr', 'placeholder', 'Email')
- cy.get('input:last').should('have.attr', 'placeholder', 'Password')
- })
- })
-
- it('cy.root() - query the root DOM element', () => {
- // https://on.cypress.io/root
-
- // By default, root is the document
- cy.root().should('match', 'html')
-
- cy.get('.query-ul').within(() => {
- // In this within, the root is now the ul DOM element
- cy.root().should('have.class', 'query-ul')
- })
- })
-
- it('best practices - selecting elements', () => {
- // https://on.cypress.io/best-practices#Selecting-Elements
- cy.get('[data-cy=best-practices-selecting-elements]').within(() => {
- // Worst - too generic, no context
- cy.get('button').click()
-
- // Bad. Coupled to styling. Highly subject to change.
- cy.get('.btn.btn-large').click()
-
- // Average. Coupled to the `name` attribute which has HTML semantics.
- cy.get('[name=submission]').click()
-
- // Better. But still coupled to styling or JS event listeners.
- cy.get('#main').click()
-
- // Slightly better. Uses an ID but also ensures the element
- // has an ARIA role attribute
- cy.get('#main[role=button]').click()
-
- // Much better. But still coupled to text content that may change.
- cy.contains('Submit').click()
-
- // Best. Insulated from all changes.
- cy.get('[data-cy=submit]').click()
- })
- })
-})
diff --git a/cypress/integration/2-advanced-examples/spies_stubs_clocks.spec.js b/cypress/integration/2-advanced-examples/spies_stubs_clocks.spec.js
deleted file mode 100644
index 18b643ecd5a..00000000000
--- a/cypress/integration/2-advanced-examples/spies_stubs_clocks.spec.js
+++ /dev/null
@@ -1,205 +0,0 @@
-///
-// remove no check once Cypress.sinon is typed
-// https://github.com/cypress-io/cypress/issues/6720
-
-context('Spies, Stubs, and Clock', () => {
- it('cy.spy() - wrap a method in a spy', () => {
- // https://on.cypress.io/spy
- cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
-
- const obj = {
- foo () {},
- }
-
- const spy = cy.spy(obj, 'foo').as('anyArgs')
-
- obj.foo()
-
- expect(spy).to.be.called
- })
-
- it('cy.spy() retries until assertions pass', () => {
- cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
-
- const obj = {
- /**
- * Prints the argument passed
- * @param x {any}
- */
- foo (x) {
- console.log('obj.foo called with', x)
- },
- }
-
- cy.spy(obj, 'foo').as('foo')
-
- setTimeout(() => {
- obj.foo('first')
- }, 500)
-
- setTimeout(() => {
- obj.foo('second')
- }, 2500)
-
- cy.get('@foo').should('have.been.calledTwice')
- })
-
- it('cy.stub() - create a stub and/or replace a function with stub', () => {
- // https://on.cypress.io/stub
- cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
-
- const obj = {
- /**
- * prints both arguments to the console
- * @param a {string}
- * @param b {string}
- */
- foo (a, b) {
- console.log('a', a, 'b', b)
- },
- }
-
- const stub = cy.stub(obj, 'foo').as('foo')
-
- obj.foo('foo', 'bar')
-
- expect(stub).to.be.called
- })
-
- it('cy.clock() - control time in the browser', () => {
- // https://on.cypress.io/clock
-
- // create the date in UTC so its always the same
- // no matter what local timezone the browser is running in
- const now = new Date(Date.UTC(2017, 2, 14)).getTime()
-
- cy.clock(now)
- cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
- cy.get('#clock-div').click()
- .should('have.text', '1489449600')
- })
-
- it('cy.tick() - move time in the browser', () => {
- // https://on.cypress.io/tick
-
- // create the date in UTC so its always the same
- // no matter what local timezone the browser is running in
- const now = new Date(Date.UTC(2017, 2, 14)).getTime()
-
- cy.clock(now)
- cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
- cy.get('#tick-div').click()
- .should('have.text', '1489449600')
-
- cy.tick(10000) // 10 seconds passed
- cy.get('#tick-div').click()
- .should('have.text', '1489449610')
- })
-
- it('cy.stub() matches depending on arguments', () => {
- // see all possible matchers at
- // https://sinonjs.org/releases/latest/matchers/
- const greeter = {
- /**
- * Greets a person
- * @param {string} name
- */
- greet (name) {
- return `Hello, ${name}!`
- },
- }
-
- cy.stub(greeter, 'greet')
- .callThrough() // if you want non-matched calls to call the real method
- .withArgs(Cypress.sinon.match.string).returns('Hi')
- .withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name'))
-
- expect(greeter.greet('World')).to.equal('Hi')
- // @ts-ignore
- expect(() => greeter.greet(42)).to.throw('Invalid name')
- expect(greeter.greet).to.have.been.calledTwice
-
- // non-matched calls goes the actual method
- // @ts-ignore
- expect(greeter.greet()).to.equal('Hello, undefined!')
- })
-
- it('matches call arguments using Sinon matchers', () => {
- // see all possible matchers at
- // https://sinonjs.org/releases/latest/matchers/
- const calculator = {
- /**
- * returns the sum of two arguments
- * @param a {number}
- * @param b {number}
- */
- add (a, b) {
- return a + b
- },
- }
-
- const spy = cy.spy(calculator, 'add').as('add')
-
- expect(calculator.add(2, 3)).to.equal(5)
-
- // if we want to assert the exact values used during the call
- expect(spy).to.be.calledWith(2, 3)
-
- // let's confirm "add" method was called with two numbers
- expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number)
-
- // alternatively, provide the value to match
- expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3))
-
- // match any value
- expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3)
-
- // match any value from a list
- expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3)
-
- /**
- * Returns true if the given number is event
- * @param {number} x
- */
- const isEven = (x) => x % 2 === 0
-
- // expect the value to pass a custom predicate function
- // the second argument to "sinon.match(predicate, message)" is
- // shown if the predicate does not pass and assertion fails
- expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3)
-
- /**
- * Returns a function that checks if a given number is larger than the limit
- * @param {number} limit
- * @returns {(x: number) => boolean}
- */
- const isGreaterThan = (limit) => (x) => x > limit
-
- /**
- * Returns a function that checks if a given number is less than the limit
- * @param {number} limit
- * @returns {(x: number) => boolean}
- */
- const isLessThan = (limit) => (x) => x < limit
-
- // you can combine several matchers using "and", "or"
- expect(spy).to.be.calledWith(
- Cypress.sinon.match.number,
- Cypress.sinon.match(isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4')),
- )
-
- expect(spy).to.be.calledWith(
- Cypress.sinon.match.number,
- Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3)),
- )
-
- // matchers can be used from BDD assertions
- cy.get('@add').should('have.been.calledWith',
- Cypress.sinon.match.number, Cypress.sinon.match(3))
-
- // you can alias matchers for shorter test code
- const { match: M } = Cypress.sinon
-
- cy.get('@add').should('have.been.calledWith', M.number, M(3))
- })
-})
diff --git a/cypress/integration/2-advanced-examples/traversal.spec.js b/cypress/integration/2-advanced-examples/traversal.spec.js
deleted file mode 100644
index 0a3b9d33062..00000000000
--- a/cypress/integration/2-advanced-examples/traversal.spec.js
+++ /dev/null
@@ -1,121 +0,0 @@
-///
-
-context('Traversal', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/traversal')
- })
-
- it('.children() - get child DOM elements', () => {
- // https://on.cypress.io/children
- cy.get('.traversal-breadcrumb')
- .children('.active')
- .should('contain', 'Data')
- })
-
- it('.closest() - get closest ancestor DOM element', () => {
- // https://on.cypress.io/closest
- cy.get('.traversal-badge')
- .closest('ul')
- .should('have.class', 'list-group')
- })
-
- it('.eq() - get a DOM element at a specific index', () => {
- // https://on.cypress.io/eq
- cy.get('.traversal-list>li')
- .eq(1).should('contain', 'siamese')
- })
-
- it('.filter() - get DOM elements that match the selector', () => {
- // https://on.cypress.io/filter
- cy.get('.traversal-nav>li')
- .filter('.active').should('contain', 'About')
- })
-
- it('.find() - get descendant DOM elements of the selector', () => {
- // https://on.cypress.io/find
- cy.get('.traversal-pagination')
- .find('li').find('a')
- .should('have.length', 7)
- })
-
- it('.first() - get first DOM element', () => {
- // https://on.cypress.io/first
- cy.get('.traversal-table td')
- .first().should('contain', '1')
- })
-
- it('.last() - get last DOM element', () => {
- // https://on.cypress.io/last
- cy.get('.traversal-buttons .btn')
- .last().should('contain', 'Submit')
- })
-
- it('.next() - get next sibling DOM element', () => {
- // https://on.cypress.io/next
- cy.get('.traversal-ul')
- .contains('apples').next().should('contain', 'oranges')
- })
-
- it('.nextAll() - get all next sibling DOM elements', () => {
- // https://on.cypress.io/nextall
- cy.get('.traversal-next-all')
- .contains('oranges')
- .nextAll().should('have.length', 3)
- })
-
- it('.nextUntil() - get next sibling DOM elements until next el', () => {
- // https://on.cypress.io/nextuntil
- cy.get('#veggies')
- .nextUntil('#nuts').should('have.length', 3)
- })
-
- it('.not() - remove DOM elements from set of DOM elements', () => {
- // https://on.cypress.io/not
- cy.get('.traversal-disabled .btn')
- .not('[disabled]').should('not.contain', 'Disabled')
- })
-
- it('.parent() - get parent DOM element from DOM elements', () => {
- // https://on.cypress.io/parent
- cy.get('.traversal-mark')
- .parent().should('contain', 'Morbi leo risus')
- })
-
- it('.parents() - get parent DOM elements from DOM elements', () => {
- // https://on.cypress.io/parents
- cy.get('.traversal-cite')
- .parents().should('match', 'blockquote')
- })
-
- it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => {
- // https://on.cypress.io/parentsuntil
- cy.get('.clothes-nav')
- .find('.active')
- .parentsUntil('.clothes-nav')
- .should('have.length', 2)
- })
-
- it('.prev() - get previous sibling DOM element', () => {
- // https://on.cypress.io/prev
- cy.get('.birds').find('.active')
- .prev().should('contain', 'Lorikeets')
- })
-
- it('.prevAll() - get all previous sibling DOM elements', () => {
- // https://on.cypress.io/prevall
- cy.get('.fruits-list').find('.third')
- .prevAll().should('have.length', 2)
- })
-
- it('.prevUntil() - get all previous sibling DOM elements until el', () => {
- // https://on.cypress.io/prevuntil
- cy.get('.foods-list').find('#nuts')
- .prevUntil('#veggies').should('have.length', 3)
- })
-
- it('.siblings() - get all sibling DOM elements', () => {
- // https://on.cypress.io/siblings
- cy.get('.traversal-pills .active')
- .siblings().should('have.length', 2)
- })
-})
diff --git a/cypress/integration/2-advanced-examples/utilities.spec.js b/cypress/integration/2-advanced-examples/utilities.spec.js
deleted file mode 100644
index 24e61a6a7c4..00000000000
--- a/cypress/integration/2-advanced-examples/utilities.spec.js
+++ /dev/null
@@ -1,110 +0,0 @@
-///
-
-context('Utilities', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/utilities')
- })
-
- it('Cypress._ - call a lodash method', () => {
- // https://on.cypress.io/_
- cy.request('https://jsonplaceholder.cypress.io/users')
- .then((response) => {
- let ids = Cypress._.chain(response.body).map('id').take(3).value()
-
- expect(ids).to.deep.eq([1, 2, 3])
- })
- })
-
- it('Cypress.$ - call a jQuery method', () => {
- // https://on.cypress.io/$
- let $li = Cypress.$('.utility-jquery li:first')
-
- cy.wrap($li)
- .should('not.have.class', 'active')
- .click()
- .should('have.class', 'active')
- })
-
- it('Cypress.Blob - blob utilities and base64 string conversion', () => {
- // https://on.cypress.io/blob
- cy.get('.utility-blob').then(($div) => {
- // https://github.com/nolanlawson/blob-util#imgSrcToDataURL
- // get the dataUrl string for the javascript-logo
- return Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous')
- .then((dataUrl) => {
- // create an element and set its src to the dataUrl
- let img = Cypress.$(' ', { src: dataUrl })
-
- // need to explicitly return cy here since we are initially returning
- // the Cypress.Blob.imgSrcToDataURL promise to our test
- // append the image
- $div.append(img)
-
- cy.get('.utility-blob img').click()
- .should('have.attr', 'src', dataUrl)
- })
- })
- })
-
- it('Cypress.minimatch - test out glob patterns against strings', () => {
- // https://on.cypress.io/minimatch
- let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', {
- matchBase: true,
- })
-
- expect(matching, 'matching wildcard').to.be.true
-
- matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', {
- matchBase: true,
- })
-
- expect(matching, 'comments').to.be.false
-
- // ** matches against all downstream path segments
- matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', {
- matchBase: true,
- })
-
- expect(matching, 'comments').to.be.true
-
- // whereas * matches only the next path segment
-
- matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', {
- matchBase: false,
- })
-
- expect(matching, 'comments').to.be.false
- })
-
- it('Cypress.Promise - instantiate a bluebird promise', () => {
- // https://on.cypress.io/promise
- let waited = false
-
- /**
- * @return Bluebird
- */
- function waitOneSecond () {
- // return a promise that resolves after 1 second
- // @ts-ignore TS2351 (new Cypress.Promise)
- return new Cypress.Promise((resolve, reject) => {
- setTimeout(() => {
- // set waited to true
- waited = true
-
- // resolve with 'foo' string
- resolve('foo')
- }, 1000)
- })
- }
-
- cy.then(() => {
- // return a promise to cy.then() that
- // is awaited until it resolves
- // @ts-ignore TS7006
- return waitOneSecond().then((str) => {
- expect(str).to.eq('foo')
- expect(waited).to.be.true
- })
- })
- })
-})
diff --git a/cypress/integration/2-advanced-examples/viewport.spec.js b/cypress/integration/2-advanced-examples/viewport.spec.js
deleted file mode 100644
index dbcd7eeddd0..00000000000
--- a/cypress/integration/2-advanced-examples/viewport.spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-///
-
-context('Viewport', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/viewport')
- })
-
- it('cy.viewport() - set the viewport size and dimension', () => {
- // https://on.cypress.io/viewport
-
- cy.get('#navbar').should('be.visible')
- cy.viewport(320, 480)
-
- // the navbar should have collapse since our screen is smaller
- cy.get('#navbar').should('not.be.visible')
- cy.get('.navbar-toggle').should('be.visible').click()
- cy.get('.nav').find('a').should('be.visible')
-
- // lets see what our app looks like on a super large screen
- cy.viewport(2999, 2999)
-
- // cy.viewport() accepts a set of preset sizes
- // to easily set the screen to a device's width and height
-
- // We added a cy.wait() between each viewport change so you can see
- // the change otherwise it is a little too fast to see :)
-
- cy.viewport('macbook-15')
- cy.wait(200)
- cy.viewport('macbook-13')
- cy.wait(200)
- cy.viewport('macbook-11')
- cy.wait(200)
- cy.viewport('ipad-2')
- cy.wait(200)
- cy.viewport('ipad-mini')
- cy.wait(200)
- cy.viewport('iphone-6+')
- cy.wait(200)
- cy.viewport('iphone-6')
- cy.wait(200)
- cy.viewport('iphone-5')
- cy.wait(200)
- cy.viewport('iphone-4')
- cy.wait(200)
- cy.viewport('iphone-3')
- cy.wait(200)
-
- // cy.viewport() accepts an orientation for all presets
- // the default orientation is 'portrait'
- cy.viewport('ipad-2', 'portrait')
- cy.wait(200)
- cy.viewport('iphone-4', 'landscape')
- cy.wait(200)
-
- // The viewport will be reset back to the default dimensions
- // in between tests (the default can be set in cypress.json)
- })
-})
diff --git a/cypress/integration/2-advanced-examples/waiting.spec.js b/cypress/integration/2-advanced-examples/waiting.spec.js
deleted file mode 100644
index c8f0d7c6723..00000000000
--- a/cypress/integration/2-advanced-examples/waiting.spec.js
+++ /dev/null
@@ -1,31 +0,0 @@
-///
-
-context('Waiting', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/waiting')
- })
- // BE CAREFUL of adding unnecessary wait times.
- // https://on.cypress.io/best-practices#Unnecessary-Waiting
-
- // https://on.cypress.io/wait
- it('cy.wait() - wait for a specific amount of time', () => {
- cy.get('.wait-input1').type('Wait 1000ms after typing')
- cy.wait(1000)
- cy.get('.wait-input2').type('Wait 1000ms after typing')
- cy.wait(1000)
- cy.get('.wait-input3').type('Wait 1000ms after typing')
- cy.wait(1000)
- })
-
- it('cy.wait() - wait for a specific route', () => {
- // Listen to GET to comments/1
- cy.intercept('GET', '**/comments/*').as('getComment')
-
- // we have code that gets a comment when
- // the button is clicked in scripts.js
- cy.get('.network-btn').click()
-
- // wait for GET comments/1
- cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
- })
-})
diff --git a/cypress/integration/2-advanced-examples/window.spec.js b/cypress/integration/2-advanced-examples/window.spec.js
deleted file mode 100644
index f94b64971db..00000000000
--- a/cypress/integration/2-advanced-examples/window.spec.js
+++ /dev/null
@@ -1,22 +0,0 @@
-///
-
-context('Window', () => {
- beforeEach(() => {
- cy.visit('https://example.cypress.io/commands/window')
- })
-
- it('cy.window() - get the global window object', () => {
- // https://on.cypress.io/window
- cy.window().should('have.property', 'top')
- })
-
- it('cy.document() - get the document object', () => {
- // https://on.cypress.io/document
- cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
- })
-
- it('cy.title() - get the title', () => {
- // https://on.cypress.io/title
- cy.title().should('include', 'Kitchen Sink')
- })
-})
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
deleted file mode 100644
index 8229063adc1..00000000000
--- a/cypress/plugins/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-///
-// ***********************************************************
-// This example plugins/index.js can be used to load plugins
-//
-// You can change the location of this file or turn off loading
-// the plugins file with the 'pluginsFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/plugins-guide
-// ***********************************************************
-
-// This function is called when a project is opened or re-opened (e.g. due to
-// the project's config changing)
-
-/**
- * @type {Cypress.PluginConfig}
- */
-// eslint-disable-next-line no-unused-vars
-module.exports = (on, config) => {
- // `on` is used to hook into various events Cypress emits
- // `config` is the resolved Cypress config
-};
diff --git a/package-lock.json b/package-lock.json
index 2e5afff360d..44bb8aa0559 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -44,7 +44,7 @@
"raviger": "^4.1.2",
"react": "18.2.0",
"react-copy-to-clipboard": "^5.0.3",
- "react-csv": "^2.1.9",
+ "react-csv": "^2.2.2",
"react-csv-reader": "^3.5.2",
"react-dates": "^21.8.0",
"react-dnd": "^16.0.1",
@@ -102,8 +102,8 @@
"@typescript-eslint/parser": "^5.13.0",
"@vitejs/plugin-react-swc": "^3.2.0",
"autoprefixer": "^10.4.12",
- "cypress": "^10.11.0",
- "cypress-localstorage-commands": "^2.1.0",
+ "cypress": "^12.12.0",
+ "cypress-localstorage-commands": "^2.2.3",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-standard": "^16.0.3",
@@ -8016,9 +8016,9 @@
"license": "MIT"
},
"node_modules/cypress": {
- "version": "10.11.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz",
- "integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==",
+ "version": "12.12.0",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.12.0.tgz",
+ "integrity": "sha512-UU5wFQ7SMVCR/hyKok/KmzG6fpZgBHHfrXcHzDmPHWrT+UUetxFzQgt7cxCszlwfozckzwkd22dxMwl/vNkWRw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -8036,10 +8036,10 @@
"check-more-types": "^2.24.0",
"cli-cursor": "^3.1.0",
"cli-table3": "~0.6.1",
- "commander": "^5.1.0",
+ "commander": "^6.2.1",
"common-tags": "^1.8.0",
"dayjs": "^1.10.4",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"enquirer": "^2.3.6",
"eventemitter2": "6.4.7",
"execa": "4.1.0",
@@ -8054,7 +8054,7 @@
"listr2": "^3.8.3",
"lodash": "^4.17.21",
"log-symbols": "^4.0.0",
- "minimist": "^1.2.6",
+ "minimist": "^1.2.8",
"ospath": "^1.2.2",
"pretty-bytes": "^5.6.0",
"proxy-from-env": "1.0.0",
@@ -8069,15 +8069,14 @@
"cypress": "bin/cypress"
},
"engines": {
- "node": ">=12.0.0"
+ "node": "^14.0.0 || ^16.0.0 || >=18.0.0"
}
},
"node_modules/cypress-localstorage-commands": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cypress-localstorage-commands/-/cypress-localstorage-commands-2.2.0.tgz",
- "integrity": "sha512-kIPxqe2yATmas3UcqK8Famecg6jThV/DQ2XcKCzK9BbzljU5Mfl2RtuzrXOmLc7phm8CIGbzXH57IIoyPsndwg==",
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/cypress-localstorage-commands/-/cypress-localstorage-commands-2.2.3.tgz",
+ "integrity": "sha512-EUEaHzbstw9AsEheIqr+RyXuxIzUS64nBBwl+Q4/mSdzfXpfcaV1nrHF+6H9zbTuFVTc+oWu6eC1l8aSjiWW6w==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=14.0.0"
},
@@ -8093,11 +8092,10 @@
"license": "MIT"
},
"node_modules/cypress/node_modules/commander": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
- "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+ "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 6"
}
@@ -12964,10 +12962,12 @@
}
},
"node_modules/minimist": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
- "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
- "license": "MIT"
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
"node_modules/minipass": {
"version": "4.2.5",
@@ -14348,9 +14348,9 @@
}
},
"node_modules/react-csv": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.1.9.tgz",
- "integrity": "sha512-p/2TakszTfa1qDCkcyGKa+3L+K5sARyNnE4pQ01p206WsD1qugcWfnM22MoUgFzVK2CPGzJPtiWpfPiM2OlJWw=="
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
+ "integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw=="
},
"node_modules/react-csv-reader": {
"version": "3.5.2",
@@ -23453,9 +23453,9 @@
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
},
"cypress": {
- "version": "10.11.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz",
- "integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==",
+ "version": "12.12.0",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.12.0.tgz",
+ "integrity": "sha512-UU5wFQ7SMVCR/hyKok/KmzG6fpZgBHHfrXcHzDmPHWrT+UUetxFzQgt7cxCszlwfozckzwkd22dxMwl/vNkWRw==",
"dev": true,
"requires": {
"@cypress/request": "^2.88.10",
@@ -23472,10 +23472,10 @@
"check-more-types": "^2.24.0",
"cli-cursor": "^3.1.0",
"cli-table3": "~0.6.1",
- "commander": "^5.1.0",
+ "commander": "^6.2.1",
"common-tags": "^1.8.0",
"dayjs": "^1.10.4",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"enquirer": "^2.3.6",
"eventemitter2": "6.4.7",
"execa": "4.1.0",
@@ -23490,7 +23490,7 @@
"listr2": "^3.8.3",
"lodash": "^4.17.21",
"log-symbols": "^4.0.0",
- "minimist": "^1.2.6",
+ "minimist": "^1.2.8",
"ospath": "^1.2.2",
"pretty-bytes": "^5.6.0",
"proxy-from-env": "1.0.0",
@@ -23509,9 +23509,9 @@
"dev": true
},
"commander": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
- "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+ "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"dev": true
},
"execa": {
@@ -23567,9 +23567,9 @@
}
},
"cypress-localstorage-commands": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cypress-localstorage-commands/-/cypress-localstorage-commands-2.2.0.tgz",
- "integrity": "sha512-kIPxqe2yATmas3UcqK8Famecg6jThV/DQ2XcKCzK9BbzljU5Mfl2RtuzrXOmLc7phm8CIGbzXH57IIoyPsndwg==",
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/cypress-localstorage-commands/-/cypress-localstorage-commands-2.2.3.tgz",
+ "integrity": "sha512-EUEaHzbstw9AsEheIqr+RyXuxIzUS64nBBwl+Q4/mSdzfXpfcaV1nrHF+6H9zbTuFVTc+oWu6eC1l8aSjiWW6w==",
"dev": true
},
"damerau-levenshtein": {
@@ -26990,9 +26990,9 @@
}
},
"minimist": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
- "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
},
"minipass": {
"version": "4.2.5",
@@ -27949,9 +27949,9 @@
}
},
"react-csv": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.1.9.tgz",
- "integrity": "sha512-p/2TakszTfa1qDCkcyGKa+3L+K5sARyNnE4pQ01p206WsD1qugcWfnM22MoUgFzVK2CPGzJPtiWpfPiM2OlJWw=="
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
+ "integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw=="
},
"react-csv-reader": {
"version": "3.5.2",
diff --git a/package.json b/package.json
index 8b7a465a6c1..3c964232cc7 100644
--- a/package.json
+++ b/package.json
@@ -143,8 +143,8 @@
"@typescript-eslint/parser": "^5.13.0",
"@vitejs/plugin-react-swc": "^3.2.0",
"autoprefixer": "^10.4.12",
- "cypress": "^10.11.0",
- "cypress-localstorage-commands": "^2.1.0",
+ "cypress": "^12.12.0",
+ "cypress-localstorage-commands": "^2.2.3",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-standard": "^16.0.3",
diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx
index 42b43167ceb..258352a65fd 100644
--- a/src/Common/constants.tsx
+++ b/src/Common/constants.tsx
@@ -17,6 +17,7 @@ export const LocalStorageKeys = {
export interface OptionsType {
id: number;
text: string;
+ label?: string;
desc?: string;
disabled?: boolean;
}
@@ -135,8 +136,8 @@ export const FACILITY_TYPES: Array = [
// { id: 1600, text: "District War Room" },
];
-export const SHIFTING_CHOICES: Array = [
- { id: 10, text: "PENDING" },
+export const SHIFTING_CHOICES_WARTIME: Array = [
+ { id: 10, text: "PENDING", label: "SHIFTING APPROVAL PENDING" },
{ id: 15, text: "ON HOLD" },
{ id: 20, text: "APPROVED" },
{ id: 30, text: "REJECTED" },
@@ -147,6 +148,18 @@ export const SHIFTING_CHOICES: Array = [
{ id: 70, text: "TRANSFER IN PROGRESS" },
{ id: 80, text: "COMPLETED" },
{ id: 90, text: "PATIENT EXPIRED" },
+ { id: 100, text: "CANCELLED" },
+];
+
+export const SHIFTING_CHOICES_PEACETIME: Array = [
+ { id: 20, text: "APPROVED", label: "PATIENTS TO BE SHIFTED" },
+ { id: 40, text: "DESTINATION APPROVED" },
+ // { id: 50, text: "DESTINATION REJECTED" },
+ { id: 60, text: "PATIENT TO BE PICKED UP", label: "TRANSPORTATION ARRANGED" },
+ { id: 70, text: "TRANSFER IN PROGRESS" },
+ { id: 80, text: "COMPLETED" },
+ { id: 90, text: "PATIENT EXPIRED" },
+ { id: 100, text: "CANCELLED" },
];
export const SHIFTING_VEHICLE_CHOICES: Array = [
@@ -475,7 +488,8 @@ export const ICMR_CATEGORY = [
];
export const TELEMEDICINE_ACTIONS = [
- { id: 10, text: "PENDING", desc: "Pending" },
+ { id: 10, text: "NO_ACTION", desc: "No Action" },
+ { id: 20, text: "PENDING", desc: "Pending" },
{ id: 30, text: "SPECIALIST_REQUIRED", desc: "Specialist Required" },
{ id: 40, text: "PLAN_FOR_HOME_CARE", desc: "Plan for Home Care" },
{ id: 50, text: "FOLLOW_UP_NOT_REQUIRED", desc: "Follow Up Not Required" },
diff --git a/src/Components/Assets/AssetFilter.tsx b/src/Components/Assets/AssetFilter.tsx
index c7a891d4794..b6102bfae82 100644
--- a/src/Components/Assets/AssetFilter.tsx
+++ b/src/Components/Assets/AssetFilter.tsx
@@ -169,7 +169,7 @@ function AssetFilter(props: any) {
label="Asset Status"
errorClassName="hidden"
options={["ACTIVE", "TRANSFER_IN_PROGRESS"]}
- optionLabel={(o) => o}
+ optionLabel={(o) => o.replace(/_/g, " ")}
optionValue={(o) => o}
value={asset_status}
onChange={({ value }) => setAssetStatus(value)}
diff --git a/src/Components/Assets/AssetManage.tsx b/src/Components/Assets/AssetManage.tsx
index 5a48526c8f8..4d8de391c41 100644
--- a/src/Components/Assets/AssetManage.tsx
+++ b/src/Components/Assets/AssetManage.tsx
@@ -25,6 +25,7 @@ import { useTranslation } from "react-i18next";
const PageTitle = loadable(() => import("../Common/PageTitle"));
const Loading = loadable(() => import("../Common/Loading"));
import * as Notification from "../../Utils/Notifications.js";
+import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor";
interface AssetManageProps {
assetId: string;
@@ -324,9 +325,10 @@ const AssetManage = (props: AssetManageProps) => {
)
}
id="update-asset"
+ authorizeFor={NonReadOnlyUsers}
>
- Update
+ {t("update")}
{asset?.asset_class && (
{
)
}
id="configure-asset"
+ authorizeFor={NonReadOnlyUsers}
>
- Configure
+ {t("configure")}
)}
{checkAuthority(user_type, "DistrictAdmin") && (
setShowDeleteDialog(true)}
- variant={"danger"}
+ variant="danger"
className="inline-flex"
>
- Delete
+ {t("delete")}
)}
diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx
index 89d4ae5afc3..a9755ff79d5 100644
--- a/src/Components/Assets/AssetsList.tsx
+++ b/src/Components/Assets/AssetsList.tsx
@@ -10,7 +10,7 @@ import {
import { assetClassProps, AssetData } from "./AssetTypes";
import { getAsset } from "../../Redux/actions";
import { useState, useCallback, useEffect } from "react";
-import { navigate } from "raviger";
+import { Link, navigate } from "raviger";
import loadable from "@loadable/component";
import AssetFilter from "./AssetFilter";
import { parseQueryParams } from "../../Utils/primitives";
@@ -20,7 +20,7 @@ import useFilters from "../../Common/hooks/useFilters";
import { FacilityModel } from "../Facility/models";
import CareIcon from "../../CAREUI/icons/CareIcon";
import { useIsAuthorized } from "../../Common/hooks/useIsAuthorized";
-import AuthorizeFor from "../../Utils/AuthorizeFor";
+import AuthorizeFor, { NonReadOnlyUsers } from "../../Utils/AuthorizeFor";
import ButtonV2 from "../Common/components/ButtonV2";
import FacilitiesSelectDialogue from "../ExternalResult/FacilitiesSelectDialogue";
import ExportMenu from "../Common/Export";
@@ -28,10 +28,12 @@ import CountBlock from "../../CAREUI/display/Count";
import AssetImportModal from "./AssetImportModal";
import Page from "../Common/components/Page";
import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover";
+import { useTranslation } from "react-i18next";
const Loading = loadable(() => import("../Common/Loading"));
const AssetsList = () => {
+ const { t } = useTranslation();
const {
qParams,
updateQuery,
@@ -48,6 +50,7 @@ const AssetsList = () => {
const [totalCount, setTotalCount] = useState(0);
const [facility, setFacility] = useState();
const [asset_type, setAssetType] = useState();
+ const [status, setStatus] = useState();
const [facilityName, setFacilityName] = useState();
const [asset_class, setAssetClass] = useState();
const [locationName, setLocationName] = useState();
@@ -109,6 +112,10 @@ const AssetsList = () => {
setAssetType(qParams.asset_type);
}, [qParams.asset_type]);
+ useEffect(() => {
+ setStatus(qParams.status);
+ }, [qParams.status]);
+
useEffect(() => {
setAssetClass(qParams.asset_class);
}, [qParams.asset_class]);
@@ -233,50 +240,50 @@ const AssetsList = () => {
manageAssets = (
{assets.map((asset: AssetData) => (
-
- navigate(
- `facility/${asset?.location_object.facility.id}/assets/${asset.id}`
- )
- }
+
-
-
-
-
+
+
+
+
+
+
+
{asset.name}
+
+
+
+
+
+ {asset?.location_object?.name}
+
+
+
+ {asset?.location_object?.facility?.name}
-
{asset.name}
-
-
-
-
- {asset?.location_object?.name}
-
-
-
- {asset?.location_object?.facility?.name}
-
-
-
- {asset.is_working ? (
-
- ) : (
-
- )}
+
+ {asset.is_working ? (
+
+ ) : (
+
+ )}
+
-
+
))}
);
@@ -361,6 +368,7 @@ const AssetsList = () => {
{
if (qParams.facility) {
@@ -371,7 +379,7 @@ const AssetsList = () => {
}}
>
- Create Asset
+ {t("create_asset")}
@@ -387,7 +395,7 @@ const AssetsList = () => {
badge("Name/Serial No./QR ID", "search"),
value("Asset Type", "asset_type", asset_type || ""),
value("Asset Class", "asset_class", asset_class || ""),
- badge("Status", "status"),
+ value("Status", "status", status?.replace(/_/g, " ") || ""),
value("Location", "location", locationName || ""),
]}
/>
diff --git a/src/Components/Common/ConfirmDialogV2.tsx b/src/Components/Common/ConfirmDialogV2.tsx
index ba99423be21..47633201458 100644
--- a/src/Components/Common/ConfirmDialogV2.tsx
+++ b/src/Components/Common/ConfirmDialogV2.tsx
@@ -1,12 +1,13 @@
import DialogModal from "./Dialog";
-import ButtonV2, { ButtonVariant, Cancel } from "./components/ButtonV2";
+import { ButtonVariant, Cancel, Submit } from "./components/ButtonV2";
type ConfirmDialogV2Props = {
+ className?: string;
title: React.ReactNode;
description?: React.ReactNode;
disabled?: boolean;
show: boolean;
- action: string;
+ action: React.ReactNode;
variant?: ButtonVariant;
onClose: () => void;
onConfirm: () => void;
@@ -15,34 +16,22 @@ type ConfirmDialogV2Props = {
};
const ConfirmDialogV2 = ({
- title,
- description,
- show,
disabled,
variant,
action,
- onClose,
onConfirm,
cancelLabel,
children,
+ ...props
}: ConfirmDialogV2Props) => {
return (
-
- {description}
-
- }
- show={show}
- >
+
{children}
-
-
+
+
{action}
-
+
);
diff --git a/src/Components/Common/HelperInputFields.tsx b/src/Components/Common/HelperInputFields.tsx
index aaeff97fb41..b5cc2376df9 100644
--- a/src/Components/Common/HelperInputFields.tsx
+++ b/src/Components/Common/HelperInputFields.tsx
@@ -26,7 +26,6 @@ import {
MuiPickersUtilsProvider,
} from "@material-ui/pickers";
import { MaterialUiPickersDate } from "@material-ui/pickers/typings/date";
-import { debounce } from "lodash";
import React, { ChangeEvent, useEffect, useState } from "react";
import PhoneInput, { ICountryData } from "react-phone-input-2";
import "react-phone-input-2/lib/high-res.css";
@@ -577,19 +576,53 @@ export const LegacyPhoneNumberField = (props: any) => {
value,
turnOffAutoFormat,
disabled,
- enableTollFree,
countryCodeEditable = false,
className,
name,
requiredError = false,
} = props;
const [maxLength, setMaxLength] = useState(15);
-
+ const [enableTollFree, setEnableTollFree] = useState(
+ value.startsWith("1800")
+ );
const countryRestriction = onlyIndia ? { onlyCountries: ["in"] } : {};
- const onChangeHandler = debounce(onChange, 500);
+ const [randId, setRandId] = useState("");
useEffect(() => {
- setMaxLength(() => (value?.slice(4, 8) === "1800" ? 16 : 15));
+ if (value.startsWith("1800")) {
+ setEnableTollFree(true);
+ } else {
+ setEnableTollFree(false);
+ }
+ }, [value]);
+
+ useEffect(() => {
+ setRandId(
+ Math.random()
+ .toString(36)
+ .replace(/[^a-z]+/g, "a")
+ );
+ }, []);
+
+ const setFocus = () => {
+ setTimeout(() => {
+ const input = document.querySelector(`div#${randId} > div.visible input`);
+ if (input instanceof HTMLElement) {
+ input.focus();
+ }
+ }, 10);
+ };
+
+ useEffect(() => {
+ if (enableTollFree) {
+ if (value.startsWith("1800")) {
+ setMaxLength(11);
+ } else {
+ setMaxLength(15);
+ }
+ } else {
+ setMaxLength(15);
+ }
}, [value]);
const handleChange = (
@@ -598,44 +631,114 @@ export const LegacyPhoneNumberField = (props: any) => {
event: ChangeEvent,
formattedValue: string
) => {
- onChangeHandler(formattedValue);
+ let phone = value;
+ if (phone.startsWith("91")) {
+ phone = phone.replace("91", "");
+ }
+ if (phone.startsWith("1800")) {
+ setEnableTollFree(true);
+ setFocus();
+ onChange(phone);
+ } else {
+ if (!value.startsWith("91") && !value.startsWith("1800")) {
+ onChange(`91${formattedValue}`);
+ } else {
+ onChange(formattedValue);
+ }
+ if (!value.startsWith("1800")) {
+ setEnableTollFree(false);
+ setFocus();
+ }
+ }
+ setFocus();
};
return (
<>
{label && {label} }
-
-
onChange("+91")}
+
+
+
+
+
+
{
+ onChange("+91");
+ setEnableTollFree(false);
+ setFocus();
+ }}
+ >
+ {" "}
+
+
+
+
-
-
+
+
{
+ onChange("+91");
+ setEnableTollFree(false);
+ setFocus();
+ }}
+ >
+
+
+
{errors && }
>
diff --git a/src/Components/Common/PrescriptionBuilder.res b/src/Components/Common/PrescriptionBuilder.res
index 8d2adcb9826..913ac7d06da 100644
--- a/src/Components/Common/PrescriptionBuilder.res
+++ b/src/Components/Common/PrescriptionBuilder.res
@@ -1,5 +1,5 @@
let select = (setPrescription, prescription) => setPrescription(_ => prescription)
-@react.component
-export make = (~prescriptions, ~setPrescriptions) =>
+@genType @react.component
+let make = (~prescriptions, ~setPrescriptions) =>
diff --git a/src/Components/Common/components/ButtonV2.tsx b/src/Components/Common/components/ButtonV2.tsx
index a8aa548fe15..a236ec96135 100644
--- a/src/Components/Common/components/ButtonV2.tsx
+++ b/src/Components/Common/components/ButtonV2.tsx
@@ -1,9 +1,9 @@
-import { Link } from "raviger";
-import { useTranslation } from "react-i18next";
-import CareIcon from "../../../CAREUI/icons/CareIcon";
import AuthorizedChild from "../../../CAREUI/misc/AuthorizedChild";
import { AuthorizedElementProps } from "../../../Utils/AuthorizeFor";
+import CareIcon from "../../../CAREUI/icons/CareIcon";
+import { Link } from "raviger";
import { classNames } from "../../../Utils/utils";
+import { useTranslation } from "react-i18next";
export type ButtonSize = "small" | "default" | "large";
export type ButtonShape = "square" | "circle";
@@ -75,6 +75,14 @@ export type ButtonProps = RawButtonProps &
* Whether the button should be having a Id.
*/
id?: string | undefined;
+ /**
+ * Tooltip showed when hovered over.
+ */
+ tooltip?: string;
+ /**
+ * Class for tooltip
+ */
+ tooltipClassName?: string;
};
const ButtonV2 = ({
@@ -91,6 +99,8 @@ const ButtonV2 = ({
children,
href,
target,
+ tooltip,
+ tooltipClassName,
...props
}: ButtonProps) => {
const className = classNames(
@@ -101,36 +111,49 @@ const ButtonV2 = ({
`button-shape-${circle ? "circle" : "square"}`,
ghost ? `button-${variant}-ghost` : `button-${variant}-default`,
border && `button-${variant}-border`,
- shadow && "shadow enabled:hover:shadow-lg"
+ shadow && "shadow enabled:hover:shadow-lg",
+ tooltip && "tooltip"
);
- if (authorizeFor) {
-
- {({ isAuthorized }) => (
-
- {children}
-
- )}
- ;
+ if (tooltip) {
+ children = (
+ <>
+ {tooltip && (
+
+ {tooltip}
+
+ )}
+ {children}
+ >
+ );
}
if (href && !(disabled || loading)) {
return (
-
- {children}
+
+
+ {children}
+
);
}
+ if (authorizeFor) {
+ return (
+
+ {({ isAuthorized }) => (
+
+ {children}
+
+ )}
+
+ );
+ }
+
return (
{children}
diff --git a/src/Components/Common/components/ResponsiveMedicineTables.tsx b/src/Components/Common/components/ResponsiveMedicineTables.tsx
index a589f0ae426..f3dc2599ec9 100644
--- a/src/Components/Common/components/ResponsiveMedicineTables.tsx
+++ b/src/Components/Common/components/ResponsiveMedicineTables.tsx
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import AccordionV2 from "./AccordionV2";
+import { classNames } from "../../../Utils/utils";
function getWindowSize() {
const { innerWidth, innerHeight } = window;
@@ -11,6 +12,10 @@ export default function ResponsiveMedicineTable(props: {
list: Array;
objectKeys: Array;
fieldsToDisplay: Array;
+ actions?: (item: any) => JSX.Element;
+ actionLabel?: string;
+ maxWidthColumn?: number;
+ onClick?: (item: any) => void;
}) {
const [windowSize, setWindowSize] = useState(getWindowSize());
useEffect(() => {
@@ -32,30 +37,49 @@ export default function ResponsiveMedicineTable(props: {
{props.theads.map((item) => {
return (
-
+
{item}
);
})}
+ {props.actions && (
+
+ {props.actionLabel || ""}
+
+ )}
{props.list?.map?.((med: any, index: number) => (
-
+ props.onClick && props.onClick(med)}
+ >
{props.objectKeys.map((key, idx) => {
- if (idx === 0)
+ if (
+ props.maxWidthColumn !== undefined &&
+ idx === props.maxWidthColumn
+ ) {
return (
-
- {med[key]}
-
- );
- else
- return (
-
+
{med[key]}
);
+ }
+
+ return (
+
+ {med[key]}
+
+ );
})}
+ {props.actions && (
+ {props.actions(med)}
+ )}
))}
diff --git a/src/Components/Common/prescription-builder/PRNPrescriptionBuilder.tsx b/src/Components/Common/prescription-builder/PRNPrescriptionBuilder.tsx
deleted file mode 100644
index 57a14fef792..00000000000
--- a/src/Components/Common/prescription-builder/PRNPrescriptionBuilder.tsx
+++ /dev/null
@@ -1,322 +0,0 @@
-import { useState } from "react";
-import AutoCompleteAsync from "../../Form/AutoCompleteAsync";
-import SelectMenuV2 from "../../Form/SelectMenuV2";
-import { medicines, routes, units } from "./PrescriptionBuilder";
-import CareIcon from "../../../CAREUI/icons/CareIcon";
-
-export type PRNPrescriptionType = {
- medicine?: string;
- route?: string;
- dosage?: string;
- indicator?: string;
- max_dosage?: string;
- min_time?: number;
-};
-
-export const PRNEmptyValues = {
- medicine: "",
- route: "",
- dosage: "0 mg",
- max_dosage: "0 mg",
- indicator: "",
- min_time: 0,
-};
-
-const DOSAGE_HRS = [1, 2, 3, 6, 12, 24];
-
-export interface PrescriptionBuilderProps {
- prescriptions: T[];
- setPrescriptions: React.Dispatch>;
-}
-
-export default function PRNPrescriptionBuilder(
- props: PrescriptionBuilderProps
-) {
- const { prescriptions, setPrescriptions } = props;
-
- const setItem = (object: PRNPrescriptionType, i: number) => {
- setPrescriptions(
- prescriptions.map((prescription, index) =>
- index === i ? object : prescription
- )
- );
- };
-
- const [activeIdx, setActiveIdx] = useState(null);
-
- return (
-
- {prescriptions.map((prescription, i) => {
- const setMedicine = (medicine: string) => {
- setItem(
- {
- ...prescription,
- medicine,
- },
- i
- );
- };
-
- const setRoute = (route: string) => {
- setItem(
- {
- ...prescription,
- route,
- },
- i
- );
- };
-
- const setDosageUnit = (unit: string) => {
- setItem(
- {
- ...prescription,
- dosage: prescription.dosage
- ? prescription.dosage.split(" ")[0] + " " + unit
- : "0 mg",
- },
- i
- );
- };
-
- const setMaxDosageUnit = (unit: string) => {
- setItem(
- {
- ...prescription,
- max_dosage: prescription.max_dosage
- ? prescription.max_dosage.split(" ")[0] + " " + unit
- : "0 mg",
- },
- i
- );
- };
-
- const setMinTime = (min_time: number) => {
- setItem(
- {
- ...prescription,
- min_time,
- },
- i
- );
- };
-
- return (
-
-
-
- Prescription No. {i + 1}
-
- {
- setPrescriptions(
- prescriptions.filter((prescription, index) => i != index)
- );
- }}
- >
- Delete Prescription
-
-
-
-
-
-
- Medicine
- {" *"}
-
-
{
- return Promise.resolve(
- medicines.filter((medicine: string) =>
- medicine.toLowerCase().includes(search.toLowerCase())
- )
- );
- }}
- optionLabel={(option) => option}
- onChange={setMedicine}
- showNOptions={medicines.length}
- className="-mt-1"
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
-
Route
-
setRoute(route || "")}
- optionLabel={(option) => option}
- required={false}
- showChevronIcon={false}
- className="mt-[2px]"
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
-
-
Dosage
-
-
{
- let value = parseFloat(e.target.value);
- if (value < 0) {
- value = 0;
- }
- setItem(
- {
- ...prescription,
- dosage:
- value +
- " " +
- (prescription.dosage?.split(" ")[1] || "mg"),
- },
- i
- );
- }}
- required
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
- setDosageUnit(dosage || "")}
- optionLabel={(option) => option}
- required={false}
- showChevronIcon={false}
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
-
-
-
-
-
-
-
- Indicator
- {" *"}
-
-
{
- setItem(
- {
- ...prescription,
- indicator: e.target.value,
- },
- i
- );
- }}
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
-
Max Dosage in 24 hrs.
-
-
-
{
- let value = parseFloat(e.target.value);
- if (value < 0) {
- value = 0;
- }
- setItem(
- {
- ...prescription,
- max_dosage:
- value +
- " " +
- (prescription.max_dosage?.split(" ")[1] || "mg"),
- },
- i
- );
- }}
- required
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
- setMaxDosageUnit(dosage || "")}
- optionLabel={(option) => option}
- required={false}
- showChevronIcon={false}
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
-
-
-
Min. time btwn. 2 doses
-
-
- min_time && (min_time > 0 ? setMinTime(min_time) : 0)
- }
- optionLabel={(option) => option}
- required={false}
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
- Hrs.
-
-
-
-
- );
- })}
-
{
- setPrescriptions([...prescriptions, PRNEmptyValues]);
- }}
- className="shadow-sm mt-4 bg-gray-200 w-full font-bold block px-4 py-2 text-sm leading-5 text-left text-gray-700 hover:bg-gray-300 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
- >
- + Add Prescription
-
-
- );
-}
diff --git a/src/Components/Common/prescription-builder/PrescriptionBuilder.tsx b/src/Components/Common/prescription-builder/PrescriptionBuilder.tsx
deleted file mode 100644
index 68159cb444e..00000000000
--- a/src/Components/Common/prescription-builder/PrescriptionBuilder.tsx
+++ /dev/null
@@ -1,309 +0,0 @@
-import AutoCompleteAsync from "../../Form/AutoCompleteAsync";
-import SelectMenuV2 from "../../Form/SelectMenuV2";
-import { PrescriptionDropdown } from "./PrescriptionDropdown";
-import { PrescriptionBuilderProps } from "./PRNPrescriptionBuilder";
-import CareIcon from "../../../CAREUI/icons/CareIcon";
-import medicines_list from "./assets/medicines.json";
-import ToolTip from "../utils/Tooltip";
-import { useState } from "react";
-
-export const medicines = medicines_list;
-
-const frequency = ["Stat", "od", "hs", "bd", "tid", "qid", "q4h", "qod", "qwk"];
-const frequencyTips = {
- Stat: "Immediately",
- od: "once daily",
- hs: "Night only",
- bd: "Twice daily",
- tid: "8th hourly",
- qid: "6th hourly",
- q4h: "4th hourly",
- qod: "Alternate day",
- qwk: "Once a week",
-};
-export const routes = ["Oral", "IV", "IM", "S/C"];
-export const units = ["mg", "g", "ml", "drops", "ampule", "tsp"];
-
-export type PrescriptionType = {
- medicine?: string;
- route?: string;
- dosage?: string; // is now frequency
- dosage_new?: string;
- days?: number;
- notes?: string;
-};
-
-export const emptyValues = {
- medicine: "",
- route: "",
- dosage: "",
- dosage_new: "0 mg",
- days: 0,
- notes: "",
-};
-
-export default function PrescriptionBuilder(
- props: PrescriptionBuilderProps
-) {
- const { prescriptions, setPrescriptions } = props;
-
- const setItem = (object: PrescriptionType, i: number) => {
- setPrescriptions(
- prescriptions.map((prescription, index) =>
- index === i ? object : prescription
- )
- );
- };
-
- const [activeIdx, setActiveIdx] = useState(null);
-
- return (
-
- {prescriptions.map((prescription, i) => {
- const setMedicine = (medicine: string) => {
- setItem(
- {
- ...prescription,
- medicine,
- },
- i
- );
- };
-
- const setRoute = (route: string) => {
- setItem(
- {
- ...prescription,
- route,
- },
- i
- );
- };
-
- const setFrequency = (frequency: string) => {
- setItem(
- {
- ...prescription,
- dosage: frequency,
- },
- i
- );
- };
-
- const setDosageUnit = (unit: string) => {
- setItem(
- {
- ...prescription,
- dosage_new: prescription.dosage_new
- ? prescription.dosage_new.split(" ")[0] + " " + unit
- : "0 mg",
- },
- i
- );
- };
-
- return (
-
-
-
- Prescription No. {i + 1}
-
- {
- setPrescriptions(
- prescriptions.filter((prescription, index) => i != index)
- );
- }}
- >
- Delete Prescription
-
-
-
-
-
-
- Medicine *
-
-
setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- placeholder="Medicine"
- selected={prescription.medicine}
- fetchData={(search) => {
- return Promise.resolve(
- medicines.filter((medicine: string) =>
- medicine.toLowerCase().includes(search.toLowerCase())
- )
- );
- }}
- optionLabel={(option) => option}
- onChange={setMedicine}
- showNOptions={medicines.length}
- />
-
-
-
-
Route
-
setRoute(route || "")}
- optionLabel={(option) => option}
- required={false}
- className="mt-[6px]"
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
- Frequency *
-
-
setFrequency(freq || "")}
- optionLabel={(option) => option}
- optionIcon={(option) => (
-
- {
- frequencyTips[
- option as keyof typeof frequencyTips
- ]
- }
-
- }
- >
-
-
- )}
- showIconWhenSelected={false}
- required={false}
- className="mt-[6px] w-[150px]"
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
-
-
-
-
Dosage
-
-
{
- let value = parseFloat(e.target.value);
- if (value < 0) {
- value = 0;
- }
- setItem(
- {
- ...prescription,
- dosage_new:
- value +
- " " +
- (prescription.dosage_new?.split(" ")[1] || "mg"),
- },
- i
- );
- }}
- required
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
-
-
-
Days
-
{
- let value = parseInt(e.target.value);
- if (value < 0) {
- value = 0;
- }
- setItem(
- {
- ...prescription,
- days: value,
- },
- i
- );
- }}
- required
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
-
-
Notes
-
{
- setItem(
- {
- ...prescription,
- notes: e.target.value,
- },
- i
- );
- }}
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
-
-
- );
- })}
-
{
- setPrescriptions([...prescriptions, emptyValues]);
- }}
- className="shadow-sm mt-4 bg-gray-200 w-full font-bold block px-4 py-2 text-sm leading-5 text-left text-gray-700 hover:bg-gray-300 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
- >
- + Add Medicine
-
-
- );
-}
diff --git a/src/Components/Common/prescription-builder/PrescriptionBuilderTS.res b/src/Components/Common/prescription-builder/PrescriptionBuilderTS.res
deleted file mode 100644
index 6f52af48b94..00000000000
--- a/src/Components/Common/prescription-builder/PrescriptionBuilderTS.res
+++ /dev/null
@@ -1,12 +0,0 @@
-type reactClass
-module PrescriptionBuilder = {
- @module("./PrescriptionBuilder.tsx") @react.component
- external make: (
- ~prescriptions: array,
- ~setPrescriptions: array => unit,
- ) => React.element = "default"
-}
-
-@react.component
-let make = (~prescriptions, ~setPrescriptions) =>
-
diff --git a/src/Components/CriticalCareRecording/CriticalCare__MedicineEditor.res b/src/Components/CriticalCareRecording/CriticalCare__MedicineEditor.res
deleted file mode 100644
index 5f5d8a00f4f..00000000000
--- a/src/Components/CriticalCareRecording/CriticalCare__MedicineEditor.res
+++ /dev/null
@@ -1,149 +0,0 @@
-let str = React.string
-
-@module("./CriticalCare__API")
-external updateDailyRound: (string, string, Js.Json.t, _ => unit, _ => unit) => unit =
- "updateDailyRound"
-
-type prescriptionType = {
- medicine: string,
- route: string,
- dosage: string, // is now frequency
- dosage_new: string,
- days: int,
- notes: string,
-}
-
-type state = {
- medicines: array,
- saving: bool,
- dirty: bool,
-}
-
-type action =
- | SetMedicines(array)
- | SetSaving
- | ClearSaving
-
-let reducer = (state, action) => {
- switch action {
- | SetMedicines(medicines) => {
- ...state,
- medicines: medicines,
- dirty: true,
- }
- | SetSaving => {
- ...state,
- saving: true,
- }
- | ClearSaving => {
- ...state,
- saving: false,
- }
- }
-}
-
-let makeField = p => {
- let payload = Js.Dict.empty()
- Js.Dict.set(payload, "medicine", Js.Json.string(Prescription__Prescription.medicine(p)))
- Js.Dict.set(payload, "dosage", Js.Json.string(Prescription__Prescription.dosage(p)))
- Js.Dict.set(payload, "days", Js.Json.number(float_of_int(Prescription__Prescription.days(p))))
- Js.Dict.set(payload, "route", Js.Json.string(Prescription__Prescription.route(p)))
- Js.Dict.set(payload, "notes", Js.Json.string(Prescription__Prescription.notes(p)))
- Js.Dict.set(payload, "dosage_new", Js.Json.string(Prescription__Prescription.dosage_new(p)))
- payload
-}
-
-let makePayload = state => {
- Js.Dict.fromArray([
- ("medication_given", Js.Json.objectArray(Js.Array.map(makeField, state.medicines))),
- ])
-}
-
-let successCB = (send, updateCB, data) => {
- send(ClearSaving)
- updateCB(CriticalCare__DailyRound.makeFromJs(data))
-}
-
-let errorCB = (send, _error) => {
- send(ClearSaving)
-}
-
-let validateMedicines = (prescriptions: array) => {
- let error = prescriptions |> Js.Array.find(prescription => {
- let medicine = prescription |> Prescription__Prescription.medicine
- let dosage = prescription |> Prescription__Prescription.dosage
- let dosage_new = prescription |> Prescription__Prescription.dosage_new |> Js.String.split(" ")
- let dosage_invalid = switch dosage_new |> Js.Array.length == 2 {
- | true => {
- let dosage_value =
- dosage_new->Js.Array.unsafe_get(0) |> Js.String.replaceByRe(%re("/\D/g"), "")
- let dosage_unit = dosage_new->Js.Array.unsafe_get(1)
- dosage_value == "" || dosage_unit == ""
- }
- | false => true
- }
- let invalid =
- medicine == "" || medicine == " " || dosage == "" || dosage == " " || dosage_invalid
- switch invalid {
- | true => {
- Notifications.error({
- msg: "Medicine, Dosage and Frequency are mandatory for Prescriptions.",
- })
- true
- }
- | false => false
- }
- })
- switch error {
- | None => true
- | Some(_) => false
- }
-}
-
-let saveData = (id, consultationId, state, send, updateCB) => {
- switch state.medicines |> validateMedicines {
- | true => {
- send(SetSaving)
- updateDailyRound(
- consultationId,
- id,
- Js.Json.object_(makePayload(state)),
- successCB(send, updateCB),
- errorCB(send),
- )
- }
- | false => Js_console.log("Validation failed")
- }
-}
-let initialState = medicines => {
- {
- medicines: medicines,
- saving: false,
- dirty: false,
- }
-}
-
-let getFieldValue = event => {
- ReactEvent.Form.target(event)["value"]
-}
-
-@react.component
-let make = (~medicines, ~updateCB, ~id, ~consultationId) => {
- let (state, send) = React.useReducer(reducer, initialState(medicines))
-
-
-
-
-
send(SetMedicines(medicines))}
- />
-
-
saveData(id, consultationId, state, send, updateCB)}>
- {str("Done")}
-
-
-}
diff --git a/src/Components/CriticalCareRecording/Pain/CriticalCare__PainInputModal.res b/src/Components/CriticalCareRecording/Pain/CriticalCare__PainInputModal.res
index de70327038b..631390b51bd 100644
--- a/src/Components/CriticalCareRecording/Pain/CriticalCare__PainInputModal.res
+++ b/src/Components/CriticalCareRecording/Pain/CriticalCare__PainInputModal.res
@@ -93,6 +93,7 @@ let getStatus = (min, minText, max, maxText, val) => {
{
hasError={ValidationUtils.isInputInRangeInt(0, 5, Belt.Float.toString(painScale)->Belt.Int.fromString)}
/>
-
+
{str("Description")}
@@ -239,7 +230,6 @@ let make = (~id, ~facilityId, ~patientId, ~consultationId, ~dailyRound) => {
DialysisEditor,
PressureSoreEditor,
NursingCareEditor,
- MedicineEditor,
])->React.array}
{
invalidForm = true;
}
// eslint-disable-next-line no-case-declarations
+ const checkTollFree = support_phone.startsWith("1800");
const supportPhoneSimple = support_phone
.replace(/[^0-9]/g, "")
.slice(2);
- const checkTollFree = supportPhoneSimple.startsWith("1800");
- if (supportPhoneSimple.length > 10 && !checkTollFree) {
+ if (supportPhoneSimple.length != 10 && !checkTollFree) {
errors[field] = "Please enter valid phone number";
invalidForm = true;
- } else if (supportPhoneSimple.length > 11 && checkTollFree) {
- errors[field] = "Please enter valid phone number";
- invalidForm = true;
- } else if (supportPhoneSimple.length < 10) {
+ } else if (
+ (support_phone.length < 10 || support_phone.length > 11) &&
+ checkTollFree
+ ) {
errors[field] = "Please enter valid phone number";
invalidForm = true;
}
@@ -324,8 +324,9 @@ const AssetCreate = (props: AssetProps) => {
vendor_name: vendor_name,
support_name: support_name,
support_email: support_email,
- support_phone:
- parsePhoneNumberFromString(support_phone)?.format("E.164"),
+ support_phone: support_phone.startsWith("1800")
+ ? support_phone
+ : parsePhoneNumberFromString(support_phone)?.format("E.164"),
qr_code_id: qrCodeId !== "" ? qrCodeId : null,
manufacturer: manufacturer,
warranty_amc_end_of_validity: warranty_amc_end_of_validity
@@ -771,7 +772,6 @@ const AssetCreate = (props: AssetProps) => {
name="support_phone"
label="Customer support number"
required
- tollFree
value={support_phone}
onChange={(e) => setSupportPhone(e.value)}
error={state.errors.support_phone}
diff --git a/src/Components/Facility/ConsultationDetails.tsx b/src/Components/Facility/ConsultationDetails.tsx
index 3b6ce20d87c..e926a169f90 100644
--- a/src/Components/Facility/ConsultationDetails.tsx
+++ b/src/Components/Facility/ConsultationDetails.tsx
@@ -1,5 +1,3 @@
-import * as Notification from "../../Utils/Notifications";
-
import {
CONSULTATION_TABS,
DISCHARGE_REASONS,
@@ -11,28 +9,19 @@ import { ConsultationModel, ICD11DiagnosisModel } from "./models";
import { getConsultation, getPatient } from "../../Redux/actions";
import { statusType, useAbortableEffect } from "../../Common/utils";
import { useCallback, useState } from "react";
-import { useDispatch, useSelector } from "react-redux";
-
+import { useDispatch } from "react-redux";
import { ABGPlots } from "./Consultations/ABGPlots";
-import { Button } from "@material-ui/core";
import ButtonV2 from "../Common/components/ButtonV2";
import CareIcon from "../../CAREUI/icons/CareIcon";
import Chip from "../../CAREUI/display/Chip";
import { DailyRoundsList } from "./Consultations/DailyRoundsList";
-import Dialog from "@material-ui/core/Dialog";
-import DialogActions from "@material-ui/core/DialogActions";
-import DialogContent from "@material-ui/core/DialogContent";
-import DialogContentText from "@material-ui/core/DialogContentText";
-import DialogTitle from "@material-ui/core/DialogTitle";
import { DialysisPlots } from "./Consultations/DialysisPlots";
import DischargeModal from "./DischargeModal";
import DoctorVideoSlideover from "./DoctorVideoSlideover";
import { Feed } from "./Consultations/Feed";
import { FileUpload } from "../Patient/FileUpload";
import InvestigationTab from "./Investigations/investigationsTab";
-import { LegacyTextInputField } from "../Common/HelperInputFields";
import { make as Link } from "../Common/components/Link.gen";
-import { MedicineTables } from "./Consultations/MedicineTables";
import { NeurologicalTable } from "./Consultations/NeurologicalTables";
import { NursingPlot } from "./Consultations/NursingPlot";
import { NutritionPlots } from "./Consultations/NutritionPlots";
@@ -42,91 +31,39 @@ import PatientVitalsCard from "../Patient/PatientVitalsCard";
import { PressureSoreDiagrams } from "./Consultations/PressureSoreDiagrams";
import { PrimaryParametersPlot } from "./Consultations/PrimaryParametersPlot";
import ReadMore from "../Common/components/Readmore";
-import ResponsiveMedicineTable from "../Common/components/ResponsiveMedicineTables";
import { VentilatorPlot } from "./Consultations/VentilatorPlot";
-import { discharge } from "../../Redux/actions";
import { formatDate } from "../../Utils/utils";
import loadable from "@loadable/component";
import moment from "moment";
import { navigate } from "raviger";
-import { validateEmailAddress } from "../../Common/validation";
+import { useTranslation } from "react-i18next";
+import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor";
+import PrescriptionsTable from "../Medicine/PrescriptionsTable";
+import MedicineAdministrationsTable from "../Medicine/MedicineAdministrationsTable";
+import DischargeSummaryModal from "./DischargeSummaryModal";
const Loading = loadable(() => import("../Common/Loading"));
const PageTitle = loadable(() => import("../Common/PageTitle"));
const symptomChoices = [...SYMPTOM_CHOICES];
export const ConsultationDetails = (props: any) => {
+ const [medicinesKey, setMedicinesKey] = useState(0);
+ const { t } = useTranslation();
const { facilityId, patientId, consultationId } = props;
const tab = props.tab.toUpperCase();
const dispatch: any = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const [showDoctors, setShowDoctors] = useState(false);
- const state: any = useSelector((state) => state);
- const { currentUser } = state;
const [consultationData, setConsultationData] = useState(
{} as ConsultationModel
);
const [patientData, setPatientData] = useState({});
- const [open, setOpen] = useState(false);
+ const [openDischargeSummaryDialog, setOpenDischargeSummaryDialog] =
+ useState(false);
const [openDischargeDialog, setOpenDischargeDialog] = useState(false);
-
- const initDischargeSummaryForm: { email: string } = {
- email: "",
- };
- const [dischargeSummaryState, setDischargeSummaryForm] = useState(
- initDischargeSummaryForm
- );
- const [errors, setErrors] = useState({});
const [showAutomatedRounds, setShowAutomatedRounds] = useState(true);
- const handleClickOpen = () => {
- setOpen(true);
- };
-
- const handleDischageClickOpen = () => {
- setOpenDischargeDialog(true);
- };
-
- const handleClose = () => {
- setOpen(false);
- };
-
- const handleDischargeClose = () => {
- setOpenDischargeDialog(false);
- };
-
- const handleDischargeSummarySubmit = () => {
- if (!dischargeSummaryState.email) {
- const errorField = Object.assign({}, errors);
- errorField["dischargeSummaryForm"] = "email field can not be blank.";
- setErrors(errorField);
- } else if (!validateEmailAddress(dischargeSummaryState.email)) {
- const errorField = Object.assign({}, errors);
- errorField["dischargeSummaryForm"] = "Please Enter a Valid Email Address";
- setErrors(errorField);
- } else {
- dispatch(
- discharge(
- { email: dischargeSummaryState.email },
- { external_id: patientData.id }
- )
- ).then((response: any) => {
- if ((response || {}).status === 200) {
- Notification.Success({
- msg: "We will be sending an email shortly. Please check your inbox.",
- });
- }
- });
- setOpen(false);
- }
- };
-
- const handleDischargeSummary = (e: any) => {
- e.preventDefault();
- setOpen(false);
- };
-
const getPatientGender = (patientData: any) =>
GENDER_TYPES.find((i) => i.id === patientData.gender)?.text;
@@ -146,26 +83,6 @@ export const ConsultationDetails = (props: any) => {
}
};
- const handleDischargeSummaryFormChange = (
- e: React.ChangeEvent
- ) => {
- const { value } = e.target;
-
- const errorField = Object.assign({}, errors);
- errorField["dischargeSummaryForm"] = null;
- setErrors(errorField);
-
- setDischargeSummaryForm({ email: value });
- };
-
- const dischargeSummaryFormSetUserEmail = () => {
- if (!currentUser.data.email.trim())
- return Notification.Error({
- msg: "Email not provided! Please update profile",
- });
- setDischargeSummaryForm({ email: currentUser.data.email });
- };
-
const fetchData = useCallback(
async (status: statusType) => {
setIsLoading(true);
@@ -184,13 +101,6 @@ export const ConsultationDetails = (props: any) => {
return option ? option.text.toLowerCase() : symptom;
});
data.symptoms_text = symptoms.join(", ");
- data.discharge_advice =
- Object.keys(res.data.discharge_advice).length === 0
- ? []
- : res.data.discharge_advice;
- }
- if (!Array.isArray(res.data.prn_prescription)) {
- data.prn_prescription = [];
}
setConsultationData(data);
const id = res.data.patient;
@@ -278,60 +188,15 @@ export const ConsultationDetails = (props: any) => {
return (
-
-
- Download Discharge Summary
-
-
-
- Please enter your email id to receive the discharge summary.
- Disclaimer: This is an automatically Generated email using your info
- Captured in Care System.
-
-
- Please check your email id before continuing. We cannot deliver
- the email if the email id is invalid
-
-
-
-
-
-
-
-
- Cancel
-
-
- Submit
-
-
-
+
setOpenDischargeSummaryDialog(false)}
+ />
setOpenDischargeDialog(false)}
consultationData={consultationData}
/>
@@ -355,7 +220,7 @@ export const ConsultationDetails = (props: any) => {
backUrl="/patients"
/>
- {patientData.is_active && (
+ {!consultationData.discharge_date && (
@@ -485,18 +350,18 @@ export const ConsultationDetails = (props: any) => {
)}
-
+ setOpenDischargeSummaryDialog(true)}
+ >
Discharge Summary
setOpenDischargeDialog(true)}
+ disabled={!!consultationData.discharge_date}
>
Discharge from CARE
@@ -572,7 +437,7 @@ export const ConsultationDetails = (props: any) => {
)}
@@ -603,7 +468,10 @@ export const ConsultationDetails = (props: any) => {
Discharge Date {" - "}
{consultationData.discharge_date
- ? formatDate(consultationData.discharge_date)
+ ? formatDate(
+ consultationData.discharge_date,
+ "DD/MM/YYYY"
+ )
: "--:--"}
@@ -613,66 +481,22 @@ export const ConsultationDetails = (props: any) => {
{consultationData.discharge_notes || "--"}
-
-
- Prescription
-
-
{" "}
+
-
-
- PRN Prescription
-
-
-
-
+
)}
@@ -689,7 +513,7 @@ export const ConsultationDetails = (props: any) => {
Cause of death {" - "}
- {consultationData.discharge_reason || "--"}
+ {consultationData.discharge_notes || "--"}
@@ -709,7 +533,10 @@ export const ConsultationDetails = (props: any) => {
Discharge Date {" - "}
{consultationData.discharge_date
- ? formatDate(consultationData.discharge_date)
+ ? formatDate(
+ consultationData.discharge_date,
+ "DD/MM/YYYY"
+ )
: "--:--"}
@@ -892,6 +719,7 @@ export const ConsultationDetails = (props: any) => {
/>
)}
+
{consultationData.special_instruction && (
Special Instruction
@@ -906,6 +734,53 @@ export const ConsultationDetails = (props: any) => {
)}
+ {consultationData.procedure &&
+ consultationData.procedure.length > 0 && (
+
+
+
+
+
+
+ Procedure
+
+
+ Notes
+
+
+ Repetitive
+
+
+ Time / Frequency
+
+
+
+
+ {consultationData.procedure?.map(
+ (procedure, index) => (
+
+
+ {procedure.procedure}
+
+
+ {procedure.notes}
+
+
+ {procedure.repetitive ? "Yes" : "No"}
+
+
+ {procedure.repetitive
+ ? procedure.frequency
+ : formatDate(String(procedure.time))}
+
+
+ )
+ )}
+
+
+
+
+ )}
{consultationData.intubation_start_date && (
@@ -1088,131 +963,29 @@ export const ConsultationDetails = (props: any) => {
)}
{tab === "MEDICINES" && (
- {consultationData.discharge_advice && (
-
-
-
Prescription
-
-
- {consultationData.modified_date &&
- formatDate(consultationData.modified_date)}
-
-
-
-
-
-
- {consultationData.discharge_advice.length === 0 && (
-
- No data found
-
- )}
-
-
-
-
- )}
- {consultationData.prn_prescription && (
-
-
-
PRN Prescription
-
-
- {consultationData.modified_date &&
- formatDate(consultationData.modified_date)}
-
-
-
-
-
-
- {consultationData.prn_prescription.length === 0 && (
-
- No data found
-
- )}
-
-
-
-
- )}
- {consultationData.procedure && (
-
-
-
-
-
- {!consultationData.procedure?.length && (
-
- No data found
-
- )}
-
-
-
-
- )}
-
-
+
+
setMedicinesKey((k) => k + 1)}
+ readonly={!!consultationData.discharge_date}
+ />
+
+
+
setMedicinesKey((k) => k + 1)}
+ readonly={!!consultationData.discharge_date}
+ />
+
+
+
+
)}
{tab === "FILES" && (
@@ -1325,6 +1098,7 @@ export const ConsultationDetails = (props: any) => {
/>
navigate(
@@ -1333,7 +1107,7 @@ export const ConsultationDetails = (props: any) => {
}
>
- Log Lab Result
+ {t("log_lab_results")}
diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx
index 9bc52331372..8585340b974 100644
--- a/src/Components/Facility/ConsultationForm.tsx
+++ b/src/Components/Facility/ConsultationForm.tsx
@@ -33,12 +33,6 @@ import { UserModel } from "../Users/models";
import { BedSelect } from "../Common/BedSelect";
import { dischargePatient } from "../../Redux/actions";
import Beds from "./Consultations/Beds";
-import PrescriptionBuilder, {
- PrescriptionType,
-} from "../Common/prescription-builder/PrescriptionBuilder";
-import PRNPrescriptionBuilder, {
- PRNPrescriptionType,
-} from "../Common/prescription-builder/PRNPrescriptionBuilder";
import InvestigationBuilder, {
InvestigationType,
} from "../Common/prescription-builder/InvestigationBuilder";
@@ -94,8 +88,6 @@ type FormDetails = {
ip_no: string;
op_no: string;
procedure: ProcedureType[];
- discharge_advice: PrescriptionType[];
- prn_prescription: PRNPrescriptionType[];
investigation: InvestigationType[];
is_telemedicine: BooleanStrings;
action?: string;
@@ -144,11 +136,9 @@ const initForm: FormDetails = {
ip_no: "",
op_no: "",
procedure: [],
- discharge_advice: [],
- prn_prescription: [],
investigation: [],
is_telemedicine: "false",
- action: undefined,
+ action: "NO_ACTION",
assigned_to: "",
assigned_to_object: null,
special_instruction: "",
@@ -223,10 +213,6 @@ export const ConsultationForm = (props: any) => {
const { facilityId, patientId, id } = props;
const [state, dispatch] = useReducer(consultationFormReducer, initialState);
const [bed, setBed] = useState
(null);
- const [dischargeAdvice, setDischargeAdvice] = useState(
- []
- );
- const [PRNAdvice, setPRNAdvice] = useState([]);
const [InvestigationAdvice, setInvestigationAdvice] = useState<
InvestigationType[]
>([]);
@@ -305,12 +291,6 @@ export const ConsultationForm = (props: any) => {
async (status: statusType) => {
setIsLoading(true);
const res = await dispatchAction(getConsultation(id));
- setDischargeAdvice(res && res.data && res.data.discharge_advice);
- setPRNAdvice(
- !Array.isArray(res.data.prn_prescription)
- ? []
- : res.data.prn_prescription
- );
setInvestigationAdvice(
!Array.isArray(res.data.investigation) ? [] : res.data.investigation
);
@@ -368,7 +348,7 @@ export const ConsultationForm = (props: any) => {
fetchData(status);
}
},
- [dispatch, fetchData]
+ [fetchData, id]
);
if (isLoading) return ;
@@ -489,27 +469,6 @@ export const ConsultationForm = (props: any) => {
invalidForm = true;
}
return;
- case "discharge_advice": {
- let invalid = false;
- let errorMsg = "";
- for (const f of dischargeAdvice) {
- if (!f.medicine?.replace(/\s/g, "").length) {
- invalid = true;
- errorMsg = "Prescription Medicine field can not be empty";
- break;
- }
- if (!f.dosage?.replace(/\s/g, "").length) {
- invalid = true;
- errorMsg = "Prescription Frequency field can not be empty";
- break;
- }
- }
- if (invalid) {
- errors[field] = errorMsg;
- invalidForm = true;
- }
- return;
- }
case "procedure": {
for (const p of procedures) {
if (!p.procedure?.replace(/\s/g, "").length) {
@@ -530,21 +489,6 @@ export const ConsultationForm = (props: any) => {
}
return;
}
- case "prn_prescription": {
- for (const f of PRNAdvice) {
- if (!f.medicine?.replace(/\s/g, "").length) {
- errors[field] = "Medicine field can not be empty";
- invalidForm = true;
- break;
- }
- if (!f.indicator?.replace(/\s/g, "").length) {
- errors[field] = "Indicator field can not be empty";
- invalidForm = true;
- break;
- }
- }
- return;
- }
case "investigation": {
for (const i of InvestigationAdvice) {
@@ -617,7 +561,7 @@ export const ConsultationForm = (props: any) => {
death_datetime: death_datetime,
death_confirmed_doctor: death_confirmed_doctor,
},
- { id: patientId }
+ { id }
)
);
@@ -661,8 +605,6 @@ export const ConsultationForm = (props: any) => {
icd11_provisional_diagnoses:
state.form.icd11_provisional_diagnoses_object.map((o) => o.id),
verified_by: state.form.verified_by,
- discharge_advice: dischargeAdvice,
- prn_prescription: PRNAdvice,
investigation: InvestigationAdvice,
procedure: procedures,
patient: patientId,
@@ -693,6 +635,7 @@ export const ConsultationForm = (props: any) => {
setIsLoading(false);
if (res && res.data && res.status !== 400) {
dispatch({ type: "set_form", form: initForm });
+
if (data.suggestion === "DD") {
await declareThePatientDead(
state.form.cause_of_death,
@@ -700,19 +643,21 @@ export const ConsultationForm = (props: any) => {
state.form.death_confirmed_doctor
);
}
- if (id) {
- Notification.Success({
- msg: "Consultation updated successfully",
- });
- navigate(
- `/facility/${facilityId}/patient/${patientId}/consultation/${id}`
- );
- } else {
- Notification.Success({
- msg: "Consultation created successfully",
- });
+
+ Notification.Success({
+ msg: `Consultation ${id ? "updated" : "created"} successfully`,
+ });
+
+ navigate(
+ `/facility/${facilityId}/patient/${patientId}/consultation/${res.data.id}`
+ );
+
+ if (data.suggestion === "R") {
+ navigate(`/facility/${facilityId}/patient/${patientId}/shift/new`);
+ return;
+ } else if (!id) {
navigate(
- `/facility/${facilityId}/patient/${patientId}/consultation/${res.data.id}`
+ `/facility/${facilityId}/patient/${patientId}/consultation/${res.data.id}/prescriptions`
);
}
}
@@ -876,7 +821,7 @@ export const ConsultationForm = (props: any) => {
})}
-
+
-
-
-
Prescription Medication
-
-
-
-
-
-
{
- const { facilityId, patientId, consultationId } = props;
- const dispatch: any = useDispatch();
- const [isLoading, setIsLoading] = useState(false);
- const [results, setResults] = useState
({});
- const [currentPage, setCurrentPage] = useState(1);
- const [totalCount, setTotalCount] = useState(0);
-
- const fetchDailyRounds = useCallback(
- async (status: statusType) => {
- setIsLoading(true);
- const res = await dispatch(
- dailyRoundsAnalyse(
- {
- page: currentPage,
- fields: ["medication_given"],
- },
- { consultationId }
- )
- );
- if (!status.aborted) {
- if (res && res.data) {
- setResults(res.data.results);
- setTotalCount(res.data.count);
- }
- setIsLoading(false);
- }
- },
- [consultationId, dispatch, currentPage]
- );
-
- useAbortableEffect(
- (status: statusType) => {
- fetchDailyRounds(status);
- },
- [currentPage]
- );
-
- const handlePagination = (page: number) => {
- setCurrentPage(page);
- };
-
- const areFieldsEmpty = () => {
- let emptyFieldCount = 0;
- Object.keys(results).forEach((k: any) => {
- if (Object.keys(results[k].medication_given).length === 0) {
- emptyFieldCount++;
- }
- });
- if (emptyFieldCount === Object.keys(results).length) return true;
- else return false;
- };
-
- const noDataFound = Object.keys(results).length === 0 || areFieldsEmpty();
-
- return (
-
- {results && (
-
-
Consultation Updates
- {noDataFound && (
-
- No Consultation Updates Found
-
- )}
- {Object.keys(results).map((k: any, indx: number) => (
-
- {Object.keys(results[k].medication_given).length !== 0 && (
-
-
-
- {formatDate(k)}
-
-
-
-
- )}
-
- ))}
-
- )}
-
- {totalCount > PAGINATION_LIMIT && (
-
- )}
-
- );
-};
diff --git a/src/Components/Facility/DischargeModal.tsx b/src/Components/Facility/DischargeModal.tsx
index 133895656a0..6815f917c35 100644
--- a/src/Components/Facility/DischargeModal.tsx
+++ b/src/Components/Facility/DischargeModal.tsx
@@ -1,12 +1,6 @@
import * as Notification from "../../Utils/Notifications";
import { Cancel, Submit } from "../Common/components/ButtonV2";
-import PRNPrescriptionBuilder, {
- PRNPrescriptionType,
-} from "../Common/prescription-builder/PRNPrescriptionBuilder";
-import PrescriptionBuilder, {
- PrescriptionType,
-} from "../Common/prescription-builder/PrescriptionBuilder";
import { useCallback, useEffect, useState } from "react";
import CareIcon from "../../CAREUI/icons/CareIcon";
@@ -19,7 +13,7 @@ import DateFormField from "../Form/FormFields/DateFormField";
import DialogModal from "../Common/Dialog";
import { FieldChangeEvent } from "../Form/FormFields/Utils";
import { FieldLabel } from "../Form/FormFields/FormField";
-import { HCXActions } from "../../Redux/actions";
+import { HCXActions, PrescriptionActions } from "../../Redux/actions";
import { HCXClaimModel } from "../HCX/models";
import { SelectFormField } from "../Form/FormFields/SelectFormField";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
@@ -29,6 +23,7 @@ import moment from "moment";
import useConfig from "../../Common/hooks/useConfig";
import { useDispatch } from "react-redux";
import { useMessageListener } from "../../Common/hooks/useMessageListener";
+import PrescriptionBuilder from "../Medicine/PrescriptionBuilder";
interface PreDischargeFormInterface {
discharge_reason: string;
@@ -36,8 +31,6 @@ interface PreDischargeFormInterface {
discharge_date: string | null;
death_datetime: string | null;
death_confirmed_doctor: string | null;
- discharge_prescription: PrescriptionType[];
- discharge_prn_prescription: PRNPrescriptionType[];
}
interface IProps {
@@ -73,15 +66,7 @@ const DischargeModal = ({
discharge_date,
death_datetime,
death_confirmed_doctor: null,
- discharge_prescription: [],
- discharge_prn_prescription: [],
});
- const [dischargePrescription, setDischargePrescription] = useState<
- PrescriptionType[]
- >([]);
- const [dischargePRNPrescription, setDischargePRNPrescription] = useState<
- PRNPrescriptionType[]
- >([]);
const [latestClaim, setLatestClaim] = useState();
const [isCreateClaimLoading, setIsCreateClaimLoading] = useState(false);
const [isSendingDischargeApi, setIsSendingDischargeApi] = useState(false);
@@ -153,10 +138,8 @@ const DischargeModal = ({
discharge_date: moment(preDischargeForm.discharge_date).toISOString(
true
),
- discharge_prescription: dischargePrescription,
- discharge_prn_prescription: dischargePRNPrescription,
},
- { id: consultationData.patient }
+ { id: consultationData.id }
)
);
@@ -184,12 +167,14 @@ const DischargeModal = ({
});
};
+ const prescriptionActions = PrescriptionActions(consultationData.id);
+
return (
Discharge patient from CARE
-
+
Caution: this action is irreversible.
@@ -251,18 +236,20 @@ const DischargeModal = ({
required
onChange={handleDateChange}
/>
- Discharge Prescription
-
+
+
+
Discharge Prescription Medications
-
-
Discharge PRN Prescription
-
+ Discharge PRN Prescriptions
+
diff --git a/src/Components/Facility/DischargeSummaryModal.tsx b/src/Components/Facility/DischargeSummaryModal.tsx
new file mode 100644
index 00000000000..2925fafb6c5
--- /dev/null
+++ b/src/Components/Facility/DischargeSummaryModal.tsx
@@ -0,0 +1,159 @@
+import { useState } from "react";
+import DialogModal from "../Common/Dialog";
+import TextFormField from "../Form/FormFields/TextFormField";
+import { ConsultationModel } from "./models";
+import { Cancel, Submit } from "../Common/components/ButtonV2";
+import CareIcon from "../../CAREUI/icons/CareIcon";
+import {
+ EmailValidator,
+ MultiValidator,
+ RequiredFieldValidator,
+} from "../Form/FieldValidators";
+import { useDispatch } from "react-redux";
+import {
+ emailDischargeSummary,
+ generateDischargeSummary,
+} from "../../Redux/actions";
+import { Error, Success } from "../../Utils/Notifications";
+import { previewDischargeSummary } from "../../Redux/actions";
+import { useTranslation } from "react-i18next";
+
+interface Props {
+ show: boolean;
+ onClose: () => void;
+ consultation: ConsultationModel;
+}
+
+export default function DischargeSummaryModal(props: Props) {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+ const [email, setEmail] = useState("");
+ const [emailError, setEmailError] = useState("");
+ const [emailing, setEmailing] = useState(false);
+ const [downloading, setDownloading] = useState(false);
+ const [generating, setGenerating] = useState(false);
+
+ const handleDownload = async () => {
+ setDownloading(true);
+
+ if (props.consultation.discharge_date) {
+ const res = await dispatch(
+ previewDischargeSummary({ external_id: props.consultation.id })
+ );
+
+ if (res.status === 200) {
+ window.open(res.data.read_signed_url, "_blank");
+ setDownloading(false);
+ props.onClose();
+ return;
+ }
+ }
+
+ await dispatch(
+ generateDischargeSummary({ external_id: props.consultation.id })
+ );
+
+ setGenerating(true);
+ Success({ msg: t("generating_discharge_summary") + "..." });
+
+ setTimeout(async () => {
+ setGenerating(false);
+
+ const res = await dispatch(
+ previewDischargeSummary({ external_id: props.consultation.id })
+ );
+
+ if (res.status === 200) {
+ window.open(res.data.read_signed_url, "_blank");
+ setDownloading(false);
+ props.onClose();
+ return;
+ }
+
+ Error({
+ msg: t("discharge_summary_not_ready") + " " + t("try_again_later"),
+ });
+ setDownloading(false);
+ }, 5000);
+ };
+
+ const handleEmail = async () => {
+ setEmailing(true);
+
+ const emailError = MultiValidator([
+ RequiredFieldValidator(),
+ EmailValidator(),
+ ])(email);
+
+ if (emailError) {
+ setEmailError(emailError);
+ setEmailing(false);
+ return;
+ }
+
+ const res = await dispatch(
+ emailDischargeSummary({ email }, { external_id: props.consultation.id })
+ );
+
+ if (res.status === 200) {
+ Success({ msg: t("email_success") });
+ props.onClose();
+ }
+
+ setEmailing(false);
+ };
+
+ return (
+
+
+
+
+ {t("email_discharge_summary_description")}
+
+
+
+ {`${t("disclaimer")}: ${t("generated_summary_caution")}`}
+
+
+
setEmail(e.value)}
+ error={emailError}
+ />
+
+
+
+ {downloading ? (
+
+ ) : (
+
+ )}
+
+ {generating
+ ? t("generating") + "..."
+ : downloading
+ ? t("downloading") + "..."
+ : t("download")}
+
+
+
+ {emailing ? (
+
+ ) : (
+
+ )}
+ {t("send_email")}
+
+
+
+
+ );
+}
diff --git a/src/Components/Facility/DoctorCapacity.tsx b/src/Components/Facility/DoctorCapacity.tsx
index cb07d15b43b..fd9800da516 100644
--- a/src/Components/Facility/DoctorCapacity.tsx
+++ b/src/Components/Facility/DoctorCapacity.tsx
@@ -6,7 +6,7 @@ import { createDoctor, getDoctor, listDoctor } from "../../Redux/actions";
import * as Notification from "../../Utils/Notifications.js";
import { LegacyErrorHelperText } from "../Common/HelperInputFields";
import { DoctorModal, OptionsType } from "./models";
-import { Cancel, Submit } from "../Common/components/ButtonV2";
+import ButtonV2, { Cancel } from "../Common/components/ButtonV2";
import SelectMenuV2 from "../Form/SelectMenuV2";
import TextFormField from "../Form/FormFields/TextFormField";
import { FieldLabel } from "../Form/FormFields/FormField";
@@ -147,6 +147,7 @@ export const DoctorCapacity = (props: DoctorCapacityProps) => {
};
const handleSubmit = async (e: any) => {
+ const submitBtnID = e.currentTarget?.id;
e.preventDefault();
const valid = validateData();
if (valid) {
@@ -181,7 +182,7 @@ export const DoctorCapacity = (props: DoctorCapacityProps) => {
}
handleUpdate();
- if (e.nativeEvent.submitter.id === "save-and-exit") handleClose();
+ if (submitBtnID === "save-and-exit") handleClose();
}
};
@@ -211,55 +212,56 @@ export const DoctorCapacity = (props: DoctorCapacityProps) => {
) : (
@@ -183,18 +209,11 @@ export default function FacilityCNS({ facilityId }: { facilityId: string }) {
>
setShowSelectLocation(false)}
className="w-full max-w-md"
>
{!monitors && }
- {monitors.length === 0 && (
-
-
- No vitals monitors present
-
-
- )}
location}
disabled={!monitors}
/>
-
+
+ {
+ setDefaultShowAllLocation(true);
+ setShowSelectLocation(false);
+ }}
+ >
+ Show All Locations
+
setShowSelectLocation(false)}
+ onClick={() => {
+ setDefaultShowAllLocation(false);
+ setShowSelectLocation(false);
+ }}
+ className="mr-2 my-2"
label="Confirm"
/>
+ setShowSelectLocation(false)}
+ className="mr-2 my-2"
+ />
@@ -236,33 +273,36 @@ export default function FacilityCNS({ facilityId }: { facilityId: string }) {
)}
- {monitors
- ?.filter((m) => m.asset.location_object.id === location?.id)
- ?.slice(
- (currentPage - 1) * PER_PAGE_LIMIT,
- currentPage * PER_PAGE_LIMIT
- )
- .map(({ patient, socketUrl }) => (
-
-
-
- {patient.name}
-
-
- {patient.age}y |{" "}
- {GENDER_TYPES.find((g) => g.id === patient.gender)?.icon}
-
-
-
- {patient.last_consultation?.current_bed?.bed_object?.name}
-
-
-
-
- ))}
+ {defaultShowAllLocation
+ ? monitors
+ ?.slice(
+ (currentPage - 1) * PER_PAGE_LIMIT,
+ currentPage * PER_PAGE_LIMIT
+ )
+ .map(({ patient, socketUrl, asset }) => (
+
+ ))
+ : monitors
+ ?.filter((m) => m.asset.location_object.id === location?.id)
+ ?.slice(
+ (currentPage - 1) * PER_PAGE_LIMIT,
+ currentPage * PER_PAGE_LIMIT
+ )
+ .map(({ patient, socketUrl, asset }) => (
+
+ ))}
);
diff --git a/src/Components/Facility/FacilityCreate.tsx b/src/Components/Facility/FacilityCreate.tsx
index 510f980d756..d6c8a4bc6d4 100644
--- a/src/Components/Facility/FacilityCreate.tsx
+++ b/src/Components/Facility/FacilityCreate.tsx
@@ -595,7 +595,7 @@ export const FacilityCreate = (props: FacilityProps) => {
const res = capacityData.find((data) => {
return data.room_type === x.id;
});
- if (res && res.current_capacity && res.total_capacity) {
+ if (res) {
const removeCurrentBedType = (bedTypeId: number | undefined) => {
setCapacityData((state) =>
state.filter((i) => i.id !== bedTypeId)
@@ -609,8 +609,8 @@ export const FacilityCreate = (props: FacilityProps) => {
key={`bed_${res.id}`}
room_type={res.room_type}
label={x.text}
- used={res.current_capacity}
- total={res.total_capacity}
+ used={res.current_capacity || 0}
+ total={res.total_capacity || 0}
lastUpdated={res.modified_date}
removeBedType={removeCurrentBedType}
handleUpdate={async () => {
diff --git a/src/Components/Facility/Investigations/index.tsx b/src/Components/Facility/Investigations/index.tsx
index b74e0c37c87..b0180dc0942 100644
--- a/src/Components/Facility/Investigations/index.tsx
+++ b/src/Components/Facility/Investigations/index.tsx
@@ -12,6 +12,7 @@ import {
import * as Notification from "../../../Utils/Notifications.js";
import { navigate, useQueryParams } from "raviger";
import loadable from "@loadable/component";
+import { useTranslation } from "react-i18next";
const Loading = loadable(() => import("../../Common/Loading"));
const PageTitle = loadable(() => import("../../Common/PageTitle"));
@@ -74,6 +75,7 @@ const Investigation = (props: {
patientId: string;
facilityId: string;
}) => {
+ const { t } = useTranslation();
const { patientId, facilityId } = props;
const [{ investigations: queryInvestigationsRaw = undefined }] =
useQueryParams();
@@ -282,7 +284,7 @@ const Investigation = (props: {
return (
{
+ return (
+
+
+
+ {patient.name}
+
+
+ {patient.age}y |{" "}
+ {GENDER_TYPES.find((g) => g.id === patient.gender)?.icon}
+
+
+
+ {patient.last_consultation?.current_bed?.bed_object?.name}
+
+
+
+ {location.name}
+
+
+
+
+ );
+};
diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx
index 578fbc2f8dc..29f6993ded4 100644
--- a/src/Components/Facility/models.tsx
+++ b/src/Components/Facility/models.tsx
@@ -1,7 +1,7 @@
-import { PRNPrescriptionType } from "../Common/prescription-builder/PRNPrescriptionBuilder";
-import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder";
-import { AssignedToObjectModel } from "../Patient/models";
import { UserAssignedModel } from "../Users/models";
+import { AssignedToObjectModel } from "../Patient/models";
+import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder";
+import { NormalPrescription, PRNPrescription } from "../Medicine/models";
export interface LocalBodyModel {
name: string;
@@ -89,8 +89,8 @@ export interface ConsultationModel {
created_date?: string;
discharge_date?: string;
discharge_reason?: string;
- discharge_prescription: any;
- discharge_prn_prescription: any;
+ discharge_prescription: NormalPrescription;
+ discharge_prn_prescription: PRNPrescription;
discharge_notes?: string;
examination_details?: string;
history_of_present_illness?: string;
@@ -118,8 +118,6 @@ export interface ConsultationModel {
symptoms_onset_date?: string;
consultation_notes?: string;
is_telemedicine?: boolean;
- discharge_advice?: any;
- prn_prescription?: PRNPrescriptionType[];
procedure?: ProcedureType[];
assigned_to_object?: AssignedToObjectModel;
created_by?: any;
diff --git a/src/Components/Form/FieldValidators.ts b/src/Components/Form/FieldValidators.tsx
similarity index 64%
rename from src/Components/Form/FieldValidators.ts
rename to src/Components/Form/FieldValidators.tsx
index c144e421791..7a7bf72d067 100644
--- a/src/Components/Form/FieldValidators.ts
+++ b/src/Components/Form/FieldValidators.tsx
@@ -17,7 +17,7 @@ export type FieldValidator = (value: T, ...args: any) => FieldError;
* @param validators List of `FieldValidator`s.
* @returns `FieldError`
*/
-export const MultiValidator = (
+export const MultiValidator = (
validators: FieldValidator[]
): FieldValidator => {
const validator = (value: T) => {
@@ -30,8 +30,16 @@ export const MultiValidator = (
};
export const RequiredFieldValidator = (message = "Field is required") => {
- return (value: T): FieldError => {
+ return (value: T): FieldError => {
if (!value) return message;
if (Array.isArray(value) && value.length === 0) return message;
};
};
+
+export const EmailValidator = (message = "Invalid email address") => {
+ return (value: string): FieldError => {
+ const pattern =
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ return pattern.test(value) ? undefined : message;
+ };
+};
diff --git a/src/Components/Form/Form.tsx b/src/Components/Form/Form.tsx
index d3bb41500dd..ed5ac1a889b 100644
--- a/src/Components/Form/Form.tsx
+++ b/src/Components/Form/Form.tsx
@@ -43,6 +43,7 @@ const Form = ({
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
+ event.stopPropagation();
if (validate) {
const errors = omitBy(validate(state.form), isEmpty) as FormErrors;
@@ -97,7 +98,7 @@ const Form = ({
{props.children}
) : (
<>
-
+
{props.children}
diff --git a/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx
new file mode 100644
index 00000000000..3d728dfab54
--- /dev/null
+++ b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx
@@ -0,0 +1,57 @@
+import FormField from "./FormField";
+import { FormFieldBaseProps, useFormFieldPropsResolver } from "./Utils";
+import { classNames } from "../../../Utils/utils";
+
+type Props = FormFieldBaseProps
& {
+ placeholder?: string;
+ value?: string;
+ autoComplete?: string;
+ className?: string | undefined;
+ min?: string | number;
+ max?: string | number;
+ units: string[];
+};
+
+export default function NumericWithUnitsFormField(props: Props) {
+ const field = useFormFieldPropsResolver(props);
+
+ const [numValue, unitValue] = field.value?.split(" ") ?? ["", props.units[0]];
+
+ return (
+
+
+
field.handleChange(e.target.value + " " + unitValue)}
+ />
+
+
+ field.handleChange(numValue + " " + e.target.value)
+ }
+ >
+ {props.units.map((unit) => (
+ {unit}
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/Components/Form/FormFields/PhoneNumberFormField.tsx b/src/Components/Form/FormFields/PhoneNumberFormField.tsx
index 2d14ef1cee9..352a1efefbf 100644
--- a/src/Components/Form/FormFields/PhoneNumberFormField.tsx
+++ b/src/Components/Form/FormFields/PhoneNumberFormField.tsx
@@ -6,7 +6,6 @@ type Props = FormFieldBaseProps & {
placeholder?: string;
autoComplete?: string;
noAutoFormat?: boolean;
- tollFree?: boolean;
onlyIndia?: boolean;
countryCodeEditable?: boolean;
};
@@ -23,7 +22,6 @@ const PhoneNumberFormField = (props: Props) => {
placeholder={props.placeholder}
onlyIndia={props.onlyIndia}
turnOffAutoFormat={props.noAutoFormat}
- enableTollFree={props.tollFree}
countryCodeEditable={!!props.countryCodeEditable}
className="my-0"
requiredError={field.error ? props.required : false}
diff --git a/src/Components/Form/FormFields/Utils.ts b/src/Components/Form/FormFields/Utils.ts
index ccf714a97d3..117e2631eac 100644
--- a/src/Components/Form/FormFields/Utils.ts
+++ b/src/Components/Form/FormFields/Utils.ts
@@ -86,7 +86,7 @@ export const useFormFieldPropsResolver = <
return {
...props,
- id: props.id,
+ id: props.id ?? props.name,
name: props.name,
onChange: props.onChange,
value: props.value,
diff --git a/src/Components/Form/Utils.ts b/src/Components/Form/Utils.ts
index 8f9f2c0d3ec..c6bf46e6d68 100644
--- a/src/Components/Form/Utils.ts
+++ b/src/Components/Form/Utils.ts
@@ -1,6 +1,6 @@
import { FieldError } from "./FieldValidators";
-export type FormDetails = Record;
+export type FormDetails = { [key: string]: any };
export type FormErrors = Partial>;
export type FormState = { form: T; errors: FormErrors };
export type FormAction =
diff --git a/src/Components/Medicine/AdministerMedicine.tsx b/src/Components/Medicine/AdministerMedicine.tsx
new file mode 100644
index 00000000000..180e3b1c9fb
--- /dev/null
+++ b/src/Components/Medicine/AdministerMedicine.tsx
@@ -0,0 +1,76 @@
+import { useState } from "react";
+import { PrescriptionActions } from "../../Redux/actions";
+import ConfirmDialogV2 from "../Common/ConfirmDialogV2";
+import { Prescription } from "./models";
+import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
+import { Success } from "../../Utils/Notifications";
+import { useDispatch } from "react-redux";
+import PrescriptionDetailCard from "./PrescriptionDetailCard";
+import CareIcon from "../../CAREUI/icons/CareIcon";
+import { formatDate } from "../../Utils/utils";
+import { useTranslation } from "react-i18next";
+
+interface Props {
+ prescription: Prescription;
+ actions: ReturnType["prescription"]>;
+ onClose: (success: boolean) => void;
+}
+
+export default function AdministerMedicine({ prescription, ...props }: Props) {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+ const [isLoading, setIsLoading] = useState(false);
+ const [notes, setNotes] = useState("");
+
+ return (
+
+
+ {t("administer_medicine")}
+ >
+ }
+ title={t("administer_medicine")}
+ description={
+
+ Last administered
+
+ {prescription.last_administered_on
+ ? formatDate(prescription.last_administered_on)
+ : t("never")}
+
+
+ }
+ show
+ onClose={() => props.onClose(false)}
+ // variant="primary"
+ onConfirm={async () => {
+ setIsLoading(true);
+ const res = await dispatch(props.actions.administer({ notes }));
+ if (res.status === 201) {
+ Success({ msg: t("medicines_administered") });
+ }
+ setIsLoading(false);
+ props.onClose(true);
+ }}
+ className="max-w-4xl w-full"
+ >
+
+
+
setNotes(value)}
+ errorClassName="hidden"
+ disabled={isLoading}
+ />
+
+
+ );
+}
diff --git a/src/Components/Medicine/CreatePrescriptionForm.tsx b/src/Components/Medicine/CreatePrescriptionForm.tsx
new file mode 100644
index 00000000000..dc252bce701
--- /dev/null
+++ b/src/Components/Medicine/CreatePrescriptionForm.tsx
@@ -0,0 +1,216 @@
+import moment from "moment";
+import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators";
+import Form from "../Form/Form";
+import { createFormContext } from "../Form/FormContext";
+import { SelectFormField } from "../Form/FormFields/SelectFormField";
+import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
+import TextFormField from "../Form/FormFields/TextFormField";
+import { MedicineAdministrationRecord, Prescription } from "./models";
+import { PrescriptionActions } from "../../Redux/actions";
+import { useDispatch } from "react-redux";
+import { useState } from "react";
+import AutocompleteFormField from "../Form/FormFields/Autocomplete";
+import medicines_list from "../Common/prescription-builder/assets/medicines.json";
+import NumericWithUnitsFormField from "../Form/FormFields/NumericWithUnitsFormField";
+import { useTranslation } from "react-i18next";
+
+export const medicines = medicines_list;
+
+const prescriptionFormContext = createFormContext();
+
+export default function CreatePrescriptionForm(props: {
+ prescription: Prescription;
+ create: ReturnType["create"];
+ onDone: () => void;
+}) {
+ const dispatch = useDispatch();
+ const [isCreating, setIsCreating] = useState(false);
+ const { t } = useTranslation();
+
+ return (
+
+ );
+}
+
+export const PRESCRIPTION_ROUTES = ["ORAL", "IV", "IM", "SC"];
+export const PRESCRIPTION_FREQUENCIES = {
+ STAT: {
+ slots: 1,
+ completed: (administrations: MedicineAdministrationRecord[]) =>
+ administrations.filter((administration) => administration),
+ },
+ OD: {
+ slots: 1,
+ completed: (administrations: MedicineAdministrationRecord[]) =>
+ administrations.filter((administration) =>
+ moment(administration.administered_date).isSame(moment(), "day")
+ ),
+ },
+ HS: {
+ slots: 1,
+ completed: (administrations: MedicineAdministrationRecord[]) =>
+ administrations.filter((administration) =>
+ moment(administration.administered_date).isSame(moment(), "day")
+ ),
+ },
+ BD: {
+ slots: 2,
+ completed: (administrations: MedicineAdministrationRecord[]) =>
+ administrations.filter((administration) =>
+ moment(administration.administered_date).isSame(moment(), "day")
+ ),
+ },
+ TID: {
+ slots: 3,
+ completed: (administrations: MedicineAdministrationRecord[]) =>
+ administrations.filter((administration) =>
+ moment(administration.administered_date).isSame(moment(), "day")
+ ),
+ },
+ QID: {
+ slots: 4,
+ completed: (administrations: MedicineAdministrationRecord[]) =>
+ administrations.filter((administration) =>
+ moment(administration.administered_date).isSame(moment(), "day")
+ ),
+ },
+ Q4H: {
+ slots: 6,
+ completed: (administrations: MedicineAdministrationRecord[]) =>
+ administrations.filter((administration) =>
+ moment(administration.administered_date).isSame(moment(), "day")
+ ),
+ },
+ QOD: {
+ slots: 1,
+ completed: (administrations: MedicineAdministrationRecord[]) => {
+ const lastAdministration = administrations[0];
+ if (!lastAdministration) {
+ return [];
+ }
+ if (
+ moment(lastAdministration.administered_date).isSame(moment(), "day") ||
+ moment(lastAdministration.administered_date).isSame(
+ moment().subtract(1, "day"),
+ "day"
+ )
+ ) {
+ return [lastAdministration];
+ } else {
+ return [] as MedicineAdministrationRecord[];
+ }
+ },
+ },
+ QWK: {
+ slots: 1,
+ completed: (administrations: MedicineAdministrationRecord[]) =>
+ administrations.filter((administration) =>
+ moment(administration.administered_date).isSame(moment(), "week")
+ ),
+ },
+};
diff --git a/src/Components/Medicine/DiscontinuePrescription.tsx b/src/Components/Medicine/DiscontinuePrescription.tsx
new file mode 100644
index 00000000000..e02dd41787c
--- /dev/null
+++ b/src/Components/Medicine/DiscontinuePrescription.tsx
@@ -0,0 +1,60 @@
+import { useState } from "react";
+import { PrescriptionActions } from "../../Redux/actions";
+import ConfirmDialogV2 from "../Common/ConfirmDialogV2";
+import { Prescription } from "./models";
+import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
+import { Success } from "../../Utils/Notifications";
+import { useDispatch } from "react-redux";
+import PrescriptionDetailCard from "./PrescriptionDetailCard";
+import { useTranslation } from "react-i18next";
+
+interface Props {
+ prescription: Prescription;
+ actions: ReturnType["prescription"]>;
+ onClose: (discontinued: boolean) => void;
+}
+
+export default function DiscontinuePrescription(props: Props) {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+ const [isDiscontinuing, setIsDiscontinuing] = useState(false);
+ const [discontinuedReason, setDiscontinuedReason] = useState("");
+
+ return (
+ props.onClose(false)}
+ variant="danger"
+ onConfirm={async () => {
+ setIsDiscontinuing(true);
+ const res = await dispatch(
+ props.actions.discontinue(discontinuedReason)
+ );
+ if (res.status === 201) {
+ Success({ msg: t("prescription_discontinued") });
+ }
+ setIsDiscontinuing(false);
+ props.onClose(true);
+ }}
+ className="max-w-4xl w-full"
+ >
+
+
+
setDiscontinuedReason(value)}
+ disabled={isDiscontinuing}
+ />
+
+
+ );
+}
diff --git a/src/Components/Medicine/ManagePrescriptions.tsx b/src/Components/Medicine/ManagePrescriptions.tsx
new file mode 100644
index 00000000000..8b9bc6bd990
--- /dev/null
+++ b/src/Components/Medicine/ManagePrescriptions.tsx
@@ -0,0 +1,48 @@
+import { useTranslation } from "react-i18next";
+import CareIcon from "../../CAREUI/icons/CareIcon";
+import useAppHistory from "../../Common/hooks/useAppHistory";
+import { PrescriptionActions } from "../../Redux/actions";
+import ButtonV2 from "../Common/components/ButtonV2";
+import Page from "../Common/components/Page";
+import PrescriptionBuilder from "./PrescriptionBuilder";
+
+interface Props {
+ consultationId: string;
+}
+
+export default function ManagePrescriptions({ consultationId }: Props) {
+ const actions = PrescriptionActions(consultationId);
+ const { t } = useTranslation();
+ const { goBack } = useAppHistory();
+
+ return (
+
+
+
+
+
+ {t("prescription_medications")}
+
+
+
+
+
+ {t("prn_prescriptions")}
+
+
+
+
+
+ goBack()}>
+
+ {t("return_to_patient_dashboard")}
+
+
+
+ {t("all_changes_have_been_saved")}
+
+
+
+
+ );
+}
diff --git a/src/Components/Medicine/MedicineAdministration.tsx b/src/Components/Medicine/MedicineAdministration.tsx
new file mode 100644
index 00000000000..978d3231d8d
--- /dev/null
+++ b/src/Components/Medicine/MedicineAdministration.tsx
@@ -0,0 +1,126 @@
+import { useEffect, useMemo, useState } from "react";
+import { PrescriptionActions } from "../../Redux/actions";
+import PrescriptionDetailCard from "./PrescriptionDetailCard";
+import { MedicineAdministrationRecord, Prescription } from "./models";
+import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
+import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField";
+import ButtonV2 from "../Common/components/ButtonV2";
+import CareIcon from "../../CAREUI/icons/CareIcon";
+import { useDispatch } from "react-redux";
+import { Error, Success } from "../../Utils/Notifications";
+import { formatDate } from "../../Utils/utils";
+import { useTranslation } from "react-i18next";
+
+interface Props {
+ prescriptions: Prescription[];
+ action: ReturnType["prescription"];
+ onDone: () => void;
+}
+
+export default function MedicineAdministration(props: Props) {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+ const [shouldAdminister, setShouldAdminister] = useState([]);
+ const [notes, setNotes] = useState(
+ []
+ );
+
+ const prescriptions = useMemo(
+ () =>
+ props.prescriptions.filter(
+ (obj) => !obj.discontinued && obj.prescription_type !== "DISCHARGE"
+ ),
+ [props.prescriptions]
+ );
+
+ useEffect(() => {
+ setShouldAdminister(Array(prescriptions.length).fill(false));
+ setNotes(Array(prescriptions.length).fill(""));
+ }, [props.prescriptions]);
+
+ const handleSubmit = () => {
+ const records: MedicineAdministrationRecord[] = [];
+ prescriptions.forEach((prescription, i) => {
+ if (shouldAdminister[i]) {
+ records.push({ prescription, notes: notes[i] });
+ }
+ });
+
+ Promise.all(
+ records.map(async ({ prescription, ...record }) => {
+ const res = await dispatch(
+ props.action(prescription!.id!).administer(record)
+ );
+ if (res.status !== 201) {
+ Error({ msg: t("medicines_administered_error") });
+ }
+ })
+ ).then(() => {
+ Success({ msg: t("medicines_administered") });
+ props.onDone();
+ });
+ };
+
+ const selectedCount = shouldAdminister.filter(Boolean).length;
+
+ return (
+
+ {prescriptions.map((obj, index) => (
+
+
+
+ setShouldAdminister((shouldAdminister) => {
+ const newShouldAdminister = [...shouldAdminister];
+ newShouldAdminister[index] = event.value;
+ return newShouldAdminister;
+ })
+ }
+ errorClassName="hidden"
+ />
+
+ {" "}
+ {t("last_administered")}
+
+ {obj.last_administered_on
+ ? formatDate(obj.last_administered_on)
+ : t("never")}
+
+
+
+ setNotes((notes) => {
+ const newNotes = [...notes];
+ newNotes[index] = event.value;
+ return newNotes;
+ })
+ }
+ errorClassName="hidden"
+ />
+
+
+ ))}
+
+
+
+ {t("administer_selected_medicines")}{" "}
+ {selectedCount > 0 && `(${selectedCount})`}
+
+
+
+ );
+}
diff --git a/src/Components/Medicine/MedicineAdministrationsTable.tsx b/src/Components/Medicine/MedicineAdministrationsTable.tsx
new file mode 100644
index 00000000000..718f9d037dd
--- /dev/null
+++ b/src/Components/Medicine/MedicineAdministrationsTable.tsx
@@ -0,0 +1,86 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+import ResponsiveMedicineTable from "../Common/components/ResponsiveMedicineTables";
+import { formatDate } from "../../Utils/utils";
+import { PrescriptionActions } from "../../Redux/actions";
+import { useDispatch } from "react-redux";
+import { MedicineAdministrationRecord } from "./models";
+import CareIcon from "../../CAREUI/icons/CareIcon";
+import RecordMeta from "../../CAREUI/display/RecordMeta";
+import { useTranslation } from "react-i18next";
+
+interface Props {
+ consultation_id: string;
+}
+
+export default function MedicineAdministrationsTable({
+ consultation_id,
+}: Props) {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+ const [items, setItems] = useState();
+
+ const { listAdministrations } = useMemo(
+ () => PrescriptionActions(consultation_id),
+ [consultation_id]
+ );
+
+ const fetchItems = useCallback(() => {
+ dispatch(listAdministrations()).then((res: any) =>
+ setItems(res.data.results)
+ );
+ }, [consultation_id]);
+
+ useEffect(() => {
+ fetchItems();
+ }, [consultation_id]);
+
+ const lastModified = items?.[0]?.modified_date;
+
+ return (
+
+
+
+
+ {t("medicine_administration_history")}
+
+
+
+
+ {lastModified && formatDate(lastModified)}
+
+
+
+
+
+
+
+
t(_))}
+ list={
+ items?.map((obj) => ({
+ ...obj,
+ medicine: obj.prescription?.medicine,
+ created_date__pretty: (
+
+ by{" "}
+ {obj.administered_by?.first_name}{" "}
+ {obj.administered_by?.last_name}
+
+ ),
+ ...obj,
+ })) || []
+ }
+ objectKeys={["medicine", "notes", "created_date__pretty"]}
+ fieldsToDisplay={[2, 3]}
+ />
+ {items?.length === 0 && (
+
+ {t("no_data_found")}
+
+ )}
+
+
+
+
+ );
+}
diff --git a/src/Components/Medicine/PrescriptionBuilder.tsx b/src/Components/Medicine/PrescriptionBuilder.tsx
new file mode 100644
index 00000000000..44c3b47d9a3
--- /dev/null
+++ b/src/Components/Medicine/PrescriptionBuilder.tsx
@@ -0,0 +1,130 @@
+import { useCallback, useEffect, useState } from "react";
+import CareIcon from "../../CAREUI/icons/CareIcon";
+import ButtonV2 from "../Common/components/ButtonV2";
+import { NormalPrescription, Prescription } from "./models";
+import DialogModal from "../Common/Dialog";
+import { PRNPrescription } from "./models";
+import CreatePrescriptionForm from "./CreatePrescriptionForm";
+import PrescriptionDetailCard from "./PrescriptionDetailCard";
+import { PrescriptionActions } from "../../Redux/actions";
+import { useDispatch } from "react-redux";
+import DiscontinuePrescription from "./DiscontinuePrescription";
+import AdministerMedicine from "./AdministerMedicine";
+import { useTranslation } from "react-i18next";
+
+interface Props {
+ prescription_type?: Prescription["prescription_type"];
+ actions: ReturnType;
+ is_prn?: boolean;
+ disabled?: boolean;
+}
+
+export default function PrescriptionBuilder({
+ prescription_type,
+ actions,
+ is_prn = false,
+ disabled,
+}: Props) {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ const [prescriptions, setPrescriptions] = useState();
+ const [showCreate, setShowCreate] = useState(false);
+ const [showDiscontinueFor, setShowDiscontinueFor] = useState();
+ const [showAdministerFor, setShowAdministerFor] = useState();
+
+ const fetchPrescriptions = useCallback(() => {
+ dispatch(actions.list({ is_prn, prescription_type })).then((res: any) =>
+ setPrescriptions(res.data.results)
+ );
+ }, [dispatch, is_prn]);
+
+ useEffect(() => {
+ fetchPrescriptions();
+ }, []);
+
+ return (
+
+ {showDiscontinueFor && (
+
{
+ setShowDiscontinueFor(undefined);
+ if (success) fetchPrescriptions();
+ }}
+ key={showDiscontinueFor.id}
+ />
+ )}
+ {showAdministerFor && (
+ {
+ setShowAdministerFor(undefined);
+ if (success) fetchPrescriptions();
+ }}
+ key={showAdministerFor.id}
+ />
+ )}
+
+ {prescriptions?.map((obj, index) => (
+
setShowDiscontinueFor(obj)}
+ onAdministerClick={() => setShowAdministerFor(obj)}
+ readonly={disabled}
+ />
+ ))}
+
+ setShowCreate(true)}
+ variant="secondary"
+ className="mt-4 bg-gray-200 text-gray-700 hover:bg-gray-300 hover:text-gray-900 w-full focus:bg-gray-100 focus:text-gray-900"
+ align="start"
+ disabled={disabled}
+ >
+
+
+ {t(is_prn ? "add_prn_prescription" : "add_prescription_medication")}
+
+
+ {showCreate && (
+ setShowCreate(false)}
+ show={showCreate}
+ title={t(
+ is_prn ? "add_prn_prescription" : "add_prescription_medication"
+ )}
+ description={
+
+
+ {t("modification_caution_note")}
+
+ }
+ className="max-w-3xl w-full"
+ >
+ {
+ setShowCreate(false);
+ fetchPrescriptions();
+ }}
+ />
+
+ )}
+
+ );
+}
+
+const DefaultPrescription: Partial = { is_prn: false };
+const DefaultPRNPrescription: Partial = { is_prn: true };
diff --git a/src/Components/Medicine/PrescriptionDetailCard.tsx b/src/Components/Medicine/PrescriptionDetailCard.tsx
new file mode 100644
index 00000000000..d6be22ad1c1
--- /dev/null
+++ b/src/Components/Medicine/PrescriptionDetailCard.tsx
@@ -0,0 +1,174 @@
+import { Prescription } from "./models";
+import CareIcon from "../../CAREUI/icons/CareIcon";
+import { classNames } from "../../Utils/utils";
+import ReadMore from "../Common/components/Readmore";
+import ButtonV2 from "../Common/components/ButtonV2";
+import { PrescriptionActions } from "../../Redux/actions";
+import { useTranslation } from "react-i18next";
+
+export default function PrescriptionDetailCard({
+ prescription,
+ ...props
+}: {
+ prescription: Prescription;
+ readonly?: boolean;
+ children?: React.ReactNode;
+ actions: ReturnType["prescription"]>;
+ onDiscontinueClick?: () => void;
+ onAdministerClick?: () => void;
+ selected?: boolean;
+}) {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+
+ {prescription.prescription_type === "DISCHARGE" &&
+ `${t("discharge")} `}
+ {t(prescription.is_prn ? "prn_prescription" : "prescription")}
+ {` #${prescription.id?.slice(-5)}`}
+
+ {prescription.discontinued && (
+
+ {t("discontinued")}
+
+ )}
+
+
+ {!props.readonly &&
+ prescription.prescription_type !== "DISCHARGE" && (
+
+
+
+ {t("administer")}
+
+
+
+ {t("discontinue")}
+
+
+ )}
+
+
+
+
+
+ {prescription.medicine}
+
+
+ {prescription.route &&
+ t("PRESCRIPTION_ROUTE_" + prescription.route)}
+
+
+ {prescription.dosage}
+
+
+ {prescription.is_prn ? (
+ <>
+
+ {prescription.indicator}
+
+
+ {prescription.max_dosage}
+
+
+ {prescription.max_dosage}
+
+ >
+ ) : (
+ <>
+
+ {prescription.frequency &&
+ t(
+ "PRESCRIPTION_FREQUENCY_" +
+ prescription.frequency.toUpperCase()
+ )}
+
+
+ {prescription.days}
+
+ >
+ )}
+
+ {prescription.notes && (
+
+
+
+ )}
+
+ {prescription.discontinued && (
+
+ {prescription.discontinued_reason}
+
+ )}
+
+
+
+ {props.children}
+
+ );
+}
+
+const Detail = (props: {
+ className?: string;
+ label: string;
+ children?: React.ReactNode;
+}) => {
+ const { t } = useTranslation();
+ return (
+
+
{props.label}
+
+ {props.children ? (
+ {props.children}
+ ) : (
+ {t("not_specified")}
+ )}
+
+
+ );
+};
diff --git a/src/Components/Medicine/PrescriptionsTable.tsx b/src/Components/Medicine/PrescriptionsTable.tsx
new file mode 100644
index 00000000000..7b6272675b9
--- /dev/null
+++ b/src/Components/Medicine/PrescriptionsTable.tsx
@@ -0,0 +1,327 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+import ResponsiveMedicineTable from "../Common/components/ResponsiveMedicineTables";
+import { formatDate } from "../../Utils/utils";
+import { PrescriptionActions } from "../../Redux/actions";
+import { useDispatch } from "react-redux";
+import { Prescription } from "./models";
+import CareIcon from "../../CAREUI/icons/CareIcon";
+import ButtonV2, { Cancel, Submit } from "../Common/components/ButtonV2";
+import SlideOver from "../../CAREUI/interactive/SlideOver";
+import MedicineAdministration from "./MedicineAdministration";
+import DiscontinuePrescription from "./DiscontinuePrescription";
+import RecordMeta from "../../CAREUI/display/RecordMeta";
+import AdministerMedicine from "./AdministerMedicine";
+import DialogModal from "../Common/Dialog";
+import PrescriptionDetailCard from "./PrescriptionDetailCard";
+import { useTranslation } from "react-i18next";
+
+interface Props {
+ is_prn?: boolean;
+ prescription_type?: Prescription["prescription_type"];
+ consultation_id: string;
+ onChange?: () => void;
+ readonly?: boolean;
+}
+
+export default function PrescriptionsTable({
+ is_prn = false,
+ prescription_type = "REGULAR",
+ consultation_id,
+ onChange,
+ readonly,
+}: Props) {
+ const dispatch = useDispatch();
+ const { t } = useTranslation();
+
+ const [prescriptions, setPrescriptions] = useState();
+ const [showBulkAdminister, setShowBulkAdminister] = useState(false);
+ const [showDiscontinueFor, setShowDiscontinueFor] = useState();
+ const [showAdministerFor, setShowAdministerFor] = useState();
+ const [detailedViewFor, setDetailedViewFor] = useState();
+
+ const { list, prescription } = useMemo(
+ () => PrescriptionActions(consultation_id),
+ [consultation_id]
+ );
+
+ const fetchPrescriptions = useCallback(() => {
+ dispatch(list({ is_prn, prescription_type })).then((res: any) =>
+ setPrescriptions(res.data.results)
+ );
+ }, [consultation_id]);
+
+ useEffect(() => {
+ fetchPrescriptions();
+ }, [consultation_id]);
+
+ const lastModified = prescriptions?.[0]?.modified_date;
+ const tkeys =
+ prescription_type === "REGULAR"
+ ? is_prn
+ ? REGULAR_PRN_TKEYS
+ : REGULAR_NORMAL_TKEYS
+ : is_prn
+ ? DISCHARGE_PRN_TKEYS
+ : DISCHARGE_NORMAL_TKEYS;
+
+ return (
+
+ {prescriptions && (
+
+ {
+ setShowBulkAdminister(false);
+ onChange?.();
+ }}
+ />
+
+ )}
+ {showDiscontinueFor && (
+
{
+ setShowDiscontinueFor(undefined);
+ if (success) onChange?.();
+ }}
+ key={showDiscontinueFor.id}
+ />
+ )}
+ {showAdministerFor && (
+ {
+ setShowAdministerFor(undefined);
+ if (success) onChange?.();
+ }}
+ key={showAdministerFor.id}
+ />
+ )}
+ {detailedViewFor && (
+ setDetailedViewFor(undefined)}
+ title={t("prescription_details")}
+ className="md:max-w-4xl w-full"
+ show
+ >
+
+
+
+ setDetailedViewFor(undefined)}
+ label={t("close")}
+ />
+ setShowDiscontinueFor(detailedViewFor)}
+ >
+
+ {t("discontinue")}
+
+ setShowAdministerFor(detailedViewFor)}
+ >
+
+ {t("administer")}
+
+
+
+
+ )}
+
+
+
+ {is_prn ? "PRN Prescriptions" : "Prescriptions"}
+
+
+
+
+ {lastModified && formatDate(lastModified)}
+
+
+
+ {prescription_type === "REGULAR" && (
+
+
+
+ {t("edit_prescriptions")}
+ {t("edit")}
+
+ setShowBulkAdminister(true)}
+ className="w-full lg:w-auto"
+ >
+
+
+ {t("administer_medicines")}
+
+ {t("administer")}
+
+
+ )}
+
+
+
+
+
t(_))}
+ list={
+ prescriptions?.map((obj) => ({
+ ...obj,
+ route__pretty:
+ obj.route && t("PRESCRIPTION_ROUTE_" + obj.route),
+ frequency__pretty:
+ obj.frequency &&
+ t("PRESCRIPTION_FREQUENCY_" + obj.frequency.toUpperCase()),
+ days__pretty: obj.days && obj.days + " day(s)",
+ min_hours_between_doses__pretty:
+ obj.min_hours_between_doses &&
+ obj.min_hours_between_doses + " hour(s)",
+ last_administered__pretty: obj.last_administered_on ? (
+
+ ) : (
+ "never"
+ ),
+ })) || []
+ }
+ objectKeys={Object.values(tkeys)}
+ fieldsToDisplay={[2, 3]}
+ actions={
+ !readonly
+ ? (med: Prescription) => {
+ if (med.prescription_type === "DISCHARGE") {
+ return (
+
+
+ {t("discharge_prescription")}
+
+
+ );
+ }
+
+ if (med.discontinued) {
+ return (
+
+
+ {t("discontinued")}
+
+ );
+ }
+
+ return (
+
+ {
+ e.stopPropagation();
+ setShowAdministerFor(med);
+ }}
+ >
+
+ {t("administer")}
+
+ {
+ e.stopPropagation();
+ setShowDiscontinueFor(med);
+ }}
+ >
+
+ {t("discontinue")}
+
+
+ );
+ }
+ : undefined
+ }
+ />
+ {prescriptions?.length === 0 && (
+
+ {t("no_data_found")}
+
+ )}
+
+
+
+
+ );
+}
+
+const COMMON_TKEYS = {
+ medicine: "medicine",
+ route: "route__pretty",
+ dosage: "dosage",
+};
+
+const REGULAR_NORMAL_TKEYS = {
+ ...COMMON_TKEYS,
+ frequency: "frequency__pretty",
+ days: "days__pretty",
+ notes: "notes",
+ last_administered: "last_administered__pretty",
+};
+
+const REGULAR_PRN_TKEYS = {
+ ...COMMON_TKEYS,
+ indicator: "indicator",
+ max_dosage_24_hrs: "max_dosage",
+ min_time_bw_doses: "min_hours_between_doses__pretty",
+ notes: "notes",
+ last_administered: "last_administered__pretty",
+};
+
+const DISCHARGE_NORMAL_TKEYS = {
+ ...COMMON_TKEYS,
+ frequency: "frequency__pretty",
+ days: "days__pretty",
+ notes: "notes",
+};
+
+const DISCHARGE_PRN_TKEYS = {
+ ...COMMON_TKEYS,
+ indicator: "indicator",
+ max_dosage_24_hrs: "max_dosage",
+ min_time_bw_doses: "min_hours_between_doses__pretty",
+ notes: "notes",
+};
diff --git a/src/Components/Medicine/models.ts b/src/Components/Medicine/models.ts
new file mode 100644
index 00000000000..2f02e6a84b2
--- /dev/null
+++ b/src/Components/Medicine/models.ts
@@ -0,0 +1,58 @@
+import { PerformedByModel } from "../HCX/misc";
+
+interface BasePrescription {
+ readonly id?: string;
+ medicine: string;
+ route?: "ORAL" | "IV" | "IM" | "SC";
+ dosage: string;
+ notes?: string;
+ meta?: object;
+ readonly prescription_type?: "DISCHARGE" | "REGULAR";
+ readonly discontinued?: boolean;
+ discontinued_reason?: string;
+ readonly prescribed_by?: PerformedByModel;
+ readonly discontinued_date: string;
+ readonly last_administered_on?: string;
+ readonly is_migrated?: boolean;
+ readonly created_date?: string;
+ readonly modified_date?: string;
+}
+
+export interface NormalPrescription extends BasePrescription {
+ frequency:
+ | "STAT"
+ | "OD"
+ | "HS"
+ | "BD"
+ | "TID"
+ | "QID"
+ | "Q4H"
+ | "QOD"
+ | "QWK";
+ days?: number;
+ is_prn: false;
+ indicator?: undefined;
+ max_dosage?: undefined;
+ min_hours_between_doses?: undefined;
+}
+
+export interface PRNPrescription extends BasePrescription {
+ indicator: string;
+ max_dosage?: string;
+ min_hours_between_doses?: number;
+ is_prn: true;
+ frequency?: undefined;
+ days?: undefined;
+}
+
+export type Prescription = NormalPrescription | PRNPrescription;
+
+export type MedicineAdministrationRecord = {
+ readonly id?: string;
+ readonly prescription?: Prescription;
+ notes: string;
+ readonly administered_by?: PerformedByModel;
+ readonly administered_date?: string;
+ readonly created_date?: string;
+ readonly modified_date?: string;
+};
diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx
index ad786186b25..0ba60f4cf5c 100644
--- a/src/Components/Patient/DailyRounds.tsx
+++ b/src/Components/Patient/DailyRounds.tsx
@@ -52,7 +52,7 @@ const initForm: any = {
patient_category: "Comfort",
current_health: 0,
recommend_discharge: false,
- actions: null,
+ action: null,
review_interval: 0,
admitted_to: "",
taken_at: null,
@@ -110,10 +110,33 @@ export const DailyRounds = (props: any) => {
const [facilityName, setFacilityName] = useState("");
const [patientName, setPatientName] = useState("");
const [prevReviewInterval, setPreviousReviewInterval] = useState(-1);
+ const [prevAction, setPreviousAction] = useState("NO_ACTION");
const [hasPreviousLog, setHasPreviousLog] = useState(false);
const headerText = !id ? "Add Consultation Update" : "Info";
const buttonText = !id ? "Save" : "Continue";
+ useEffect(() => {
+ (async () => {
+ if (patientId) {
+ const res = await dispatchAction(getPatient({ id: patientId }));
+ if (res.data) {
+ setPatientName(res.data.name);
+ setFacilityName(res.data.facility_object.name);
+ setPreviousReviewInterval(
+ Number(res.data.last_consultation.review_interval)
+ );
+ setPreviousAction(
+ TELEMEDICINE_ACTIONS.find((action) => action.id === res.data.action)
+ ?.text || "NO_ACTION"
+ );
+ }
+ } else {
+ setPatientName("");
+ setFacilityName("");
+ }
+ })();
+ }, [dispatchAction, patientId]);
+
const fetchRoundDetails = useCallback(
async (status: statusType) => {
setIsLoading(true);
@@ -149,26 +172,7 @@ export const DailyRounds = (props: any) => {
);
useEffect(() => {
- async function fetchPatientName() {
- if (patientId) {
- const res = await dispatchAction(getPatient({ id: patientId }));
- if (res.data) {
- setPatientName(res.data.name);
- setFacilityName(res.data.facility_object.name);
- setPreviousReviewInterval(
- Number(res.data.last_consultation.review_interval)
- );
- }
- } else {
- setPatientName("");
- setFacilityName("");
- }
- }
- fetchPatientName();
- }, [dispatchAction, patientId]);
-
- useEffect(() => {
- async function fetchHasPreviousLog() {
+ (async () => {
if (consultationId && !id) {
const res = await dispatchAction(
getDailyReport({ limit: 1, offset: 0 }, { consultationId })
@@ -187,8 +191,7 @@ export const DailyRounds = (props: any) => {
},
});
}
- }
- fetchHasPreviousLog();
+ })();
}, [dispatchAction, consultationId, id]);
const validateForm = () => {
@@ -275,10 +278,8 @@ export const DailyRounds = (props: any) => {
other_details: state.form.other_details,
consultation: consultationId,
recommend_discharge: JSON.parse(state.form.recommend_discharge),
- action: state.form.action,
- review_interval: Number(
- state.form.review_interval || prevReviewInterval
- ),
+ action: prevAction,
+ review_interval: Number(prevReviewInterval),
};
if (state.form.rounds_type === "NORMAL") {
data = {
@@ -648,11 +649,11 @@ export const DailyRounds = (props: any) => {
setPreviousAction(e.target.value)}
/>
@@ -664,12 +665,14 @@ export const DailyRounds = (props: any) => {
+ setPreviousReviewInterval(Number(e.target.value))
+ }
errors={state.errors.review_interval}
className="mt-1"
/>
diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx
index b44f4ade30e..24550090819 100644
--- a/src/Components/Patient/FileUpload.tsx
+++ b/src/Components/Patient/FileUpload.tsx
@@ -1,7 +1,7 @@
import axios from "axios";
import { CircularProgress, InputLabel } from "@material-ui/core";
import loadable from "@loadable/component";
-import React, { useCallback, useState, useEffect } from "react";
+import React, { useCallback, useState, useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { statusType, useAbortableEffect } from "../../Common/utils";
import {
@@ -31,6 +31,10 @@ import CareIcon from "../../CAREUI/icons/CareIcon";
import TextFormField from "../Form/FormFields/TextFormField";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
import RecordMeta from "../../CAREUI/display/RecordMeta";
+import Webcam from "react-webcam";
+import useWindowDimensions from "../../Common/hooks/useWindowDimensions";
+import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor";
+import AuthorizedChild from "../../CAREUI/misc/AuthorizedChild";
const Loading = loadable(() => import("../Common/Loading"));
const PageTitle = loadable(() => import("../Common/PageTitle"));
@@ -152,6 +156,26 @@ export const FileUpload = (props: FileUploadProps) => {
const [audioFileError, setAudioFileError] = useState("");
const [contentType, setcontentType] = useState("");
const [downloadURL, setDownloadURL] = useState();
+ const FACING_MODE_USER = "user";
+ const FACING_MODE_ENVIRONMENT = { exact: "environment" };
+ const webRef = useRef(null);
+ const [previewImage, setPreviewImage] = useState(null);
+ const [facingMode, setFacingMode] = useState(FACING_MODE_USER);
+ const videoConstraints = {
+ width: 1280,
+ height: 720,
+ facingMode: "user",
+ };
+ const { width } = useWindowDimensions();
+ const LaptopScreenBreakpoint = 640;
+ const isLaptopScreen = width >= LaptopScreenBreakpoint ? true : false;
+ const handleSwitchCamera = useCallback(() => {
+ setFacingMode((prevState: any) =>
+ prevState === FACING_MODE_USER
+ ? FACING_MODE_ENVIRONMENT
+ : FACING_MODE_USER
+ );
+ }, []);
const initialState = {
open: false,
isImage: false,
@@ -170,6 +194,7 @@ export const FileUpload = (props: FileUploadProps) => {
const [facilityName, setFacilityName] = useState("");
const [patientName, setPatientName] = useState("");
const [modalOpenForEdit, setModalOpenForEdit] = useState(false);
+ const [modalOpenForCamera, setModalOpenForCamera] = useState(false);
const [modalOpenForArchive, setModalOpenForArchive] = useState(false);
const [modalOpenForMoreDetails, setModalOpenForMoreDetails] = useState(false);
const [archiveReason, setArchiveReason] = useState("");
@@ -206,6 +231,18 @@ export const FileUpload = (props: FileUploadProps) => {
fetchPatientName();
}, [dispatch, patientId]);
+ const captureImage = () => {
+ setPreviewImage(webRef.current.getScreenshot());
+ fetch(webRef.current.getScreenshot())
+ .then((res) => res.blob())
+ .then((blob) => {
+ const myFile = new File([blob], "image.png", {
+ type: blob.type,
+ });
+ setFile(myFile);
+ });
+ };
+
const handlePagination = (page: number, limit: number) => {
const offset = (page - 1) * limit;
setCurrentPage(page);
@@ -1112,7 +1149,7 @@ export const FileUpload = (props: FileUploadProps) => {
{downloadURL && downloadURL.length > 0 && (
@@ -1155,6 +1192,159 @@ export const FileUpload = (props: FileUploadProps) => {
)}
+
+
+
+
+
+
Camera
+
+
+ }
+ className="max-w-2xl"
+ onClose={() => setModalOpenForCamera(false)}
+ >
+
+ {!previewImage ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ {/* buttons for mobile screens */}
+
+
+ {!previewImage ? (
+
+ {t("switch")}
+
+ ) : (
+ <>>
+ )}
+
+
+ {!previewImage ? (
+ <>
+
+ {
+ captureImage();
+ }}
+ className="m-2"
+ >
+ {t("capture")}
+
+
+ >
+ ) : (
+ <>
+
+ {
+ setPreviewImage(null);
+ }}
+ className="m-2"
+ >
+ {t("retake")}
+
+ {
+ setModalOpenForCamera(false);
+ }}
+ className="m-2"
+ >
+ {t("submit")}
+
+
+ >
+ )}
+
+
+ {
+ setPreviewImage(null);
+ setModalOpenForCamera(false);
+ }}
+ className="m-2"
+ >
+ {t("close")}
+
+
+
+ {/* buttons for laptop screens */}
+
+
+
+
+ {`${t("switch")} ${t("camera")}`}
+
+
+
+
+
+ {!previewImage ? (
+ <>
+
+ {
+ captureImage();
+ }}
+ >
+
+ {t("capture")}
+
+
+ >
+ ) : (
+ <>
+
+ {
+ setPreviewImage(null);
+ }}
+ >
+ {t("retake")}
+
+ {
+ setModalOpenForCamera(false);
+ }}
+ >
+ {t("submit")}
+
+
+ >
+ )}
+
+
+
{
+ setPreviewImage(null);
+ setModalOpenForCamera(false);
+ }}
+ >
+ {`${t("close")} ${t("camera")}`}
+
+
+
+
{
{uploadStarted ? (
) : (
-
)}
{file && (
diff --git a/src/Components/Patient/PatientCategorySelect.tsx b/src/Components/Patient/PatientCategorySelect.tsx
index 5a5ea4fece5..24add33cf95 100644
--- a/src/Components/Patient/PatientCategorySelect.tsx
+++ b/src/Components/Patient/PatientCategorySelect.tsx
@@ -3,7 +3,7 @@ import { SelectFormField } from "../Form/FormFields/SelectFormField";
import { FormFieldBaseProps } from "../Form/FormFields/Utils";
/**
- * A `FormField` component to select patient category and is always a mandatory
+ * A `FormField` component to select patient category and is by default a mandatory
* field.
*/
export default function PatientCategorySelect(
@@ -12,7 +12,7 @@ export default function PatientCategorySelect(
return (
option.id}
optionLabel={(option) => option.text}
diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx
index 733c77e61ec..f48a1a95b7f 100644
--- a/src/Components/Patient/PatientHome.tsx
+++ b/src/Components/Patient/PatientHome.tsx
@@ -2,7 +2,7 @@ import { CircularProgress } from "@material-ui/core";
import { navigate } from "raviger";
import moment from "moment";
import React, { useCallback, useEffect, useState } from "react";
-import { useDispatch } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
import { GENDER_TYPES, SAMPLE_TEST_STATUS } from "../../Common/constants";
import loadable from "@loadable/component";
import { statusType, useAbortableEffect } from "../../Common/utils";
@@ -34,6 +34,7 @@ import ButtonV2 from "../Common/components/ButtonV2";
import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor";
import RelativeDateUserMention from "../Common/RelativeDateUserMention";
import CareIcon from "../../CAREUI/icons/CareIcon";
+import { useTranslation } from "react-i18next";
const Loading = loadable(() => import("../Common/Loading"));
const PageTitle = loadable(() => import("../Common/PageTitle"));
@@ -64,6 +65,11 @@ export const PatientHome = (props: any) => {
const [isConsultationLoading, setIsConsultationLoading] = useState(false);
const [isSampleLoading, setIsSampleLoading] = useState(false);
const [sampleFlag, callSampleList] = useState(false);
+ const rootState: any = useSelector((rootState) => rootState);
+ const { currentUser } = rootState;
+ const userHomeFacilityId = currentUser.data.home_facility;
+ const userType = currentUser.data.user_type;
+ const { t } = useTranslation();
const [selectedStatus, setSelectedStatus] = useState<{
status: number;
sample: any;
@@ -651,16 +657,17 @@ export const PatientHome = (props: any) => {
)}
- {patientData.is_vaccinated && patientData.last_vaccinated_date && (
-
-
- Last Vaccinated on
-
-
- {formatDate(patientData.last_vaccinated_date)}
+ {patientData.is_vaccinated &&
+ patientData.last_vaccinated_date && (
+
+
+ Last Vaccinated on
+
+
+ {formatDate(patientData.last_vaccinated_date)}
+
-
- )}
+ )}
{patientData.countries_travelled &&
!!patientData.countries_travelled.length && (
@@ -933,15 +940,24 @@ export const PatientHome = (props: any) => {
All Details
- {shift.status === "TRANSFER IN PROGRESS" &&
+ {shift.status === "COMPLETED" &&
shift.assigned_facility && (
setModalFor(shift.external_id)}
>
- TRANSFER TO RECEIVING FACILITY
+ {t("transfer_to_receiving_facility")}
c.text === category)?.twClass
: "patient-unknown";
- const bedDialogTitle = !patient.is_active
+ const bedDialogTitle = consultation?.discharge_date
? "Bed History"
: !consultation?.current_bed
? "Assign Bed"
@@ -54,7 +56,7 @@ export default function PatientInfoCard(props: {
- {patient.facility_object?.name}
+ {consultation?.facility_name}
{(consultation?.suggestion === "A" || op_no) && (
@@ -154,7 +156,7 @@ export default function PatientInfoCard(props: {
)}
- {!patient.is_active && (
+ {!!consultation?.discharge_date && (
Discharged from CARE
@@ -199,7 +201,7 @@ export default function PatientInfoCard(props: {
);
})}
- {patient.is_active === false && (
+ {!!consultation?.discharge_date && (
@@ -241,7 +243,7 @@ export default function PatientInfoCard(props: {
- {patient.is_active === false && (
+ {!!consultation?.discharge_date && (
Discharge Reason
@@ -264,13 +266,17 @@ export default function PatientInfoCard(props: {
`/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/update`,
"Edit Consultation Details",
"pen",
- patient.is_active && consultation?.id,
+ patient.is_active &&
+ consultation?.id &&
+ !consultation?.discharge_date,
],
[
`/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/daily-rounds`,
"Log Update",
"plus",
- patient.is_active && consultation?.id,
+ patient.is_active &&
+ consultation?.id &&
+ !consultation?.discharge_date,
[
!(consultation?.facility !== patient.facility) &&
!(consultation?.discharge_date || !patient.is_active) &&
diff --git a/src/Components/Patient/ShiftCreate.tsx b/src/Components/Patient/ShiftCreate.tsx
index 97bc13ac91b..e863ae3fd84 100644
--- a/src/Components/Patient/ShiftCreate.tsx
+++ b/src/Components/Patient/ShiftCreate.tsx
@@ -1,37 +1,43 @@
-import { useReducer, useState, useEffect } from "react";
-import loadable from "@loadable/component";
-import { FacilitySelect } from "../Common/FacilitySelect";
-import {
- LegacyErrorHelperText,
- LegacySelectField,
-} from "../Common/HelperInputFields";
import * as Notification from "../../Utils/Notifications.js";
-import { useDispatch } from "react-redux";
-import { navigate } from "raviger";
+
import {
+ BREATHLESSNESS_LEVEL,
FACILITY_TYPES,
+ PATIENT_CATEGORIES,
SHIFTING_VEHICLE_CHOICES,
- BREATHLESSNESS_LEVEL,
} from "../../Common/constants";
-import { parsePhoneNumberFromString } from "libphonenumber-js";
import {
+ Box,
Card,
CardContent,
+ FormControlLabel,
Radio,
RadioGroup,
- Box,
- FormControlLabel,
} from "@material-ui/core";
-import { phonePreg } from "../../Common/validation";
-
-import { createShift, getPatient } from "../../Redux/actions";
import { Cancel, Submit } from "../Common/components/ButtonV2";
-import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField";
+import {
+ LegacyErrorHelperText,
+ LegacySelectField,
+} from "../Common/HelperInputFields";
+import { createShift, getPatient } from "../../Redux/actions";
+import { useEffect, useReducer, useState } from "react";
+
+import { FacilitySelect } from "../Common/FacilitySelect";
import { FieldChangeEvent } from "../Form/FormFields/Utils";
-import TextFormField from "../Form/FormFields/TextFormField";
import { FieldLabel } from "../Form/FormFields/FormField";
+import PatientCategorySelect from "./PatientCategorySelect";
+import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
+import TextFormField from "../Form/FormFields/TextFormField";
+import loadable from "@loadable/component";
+import { navigate } from "raviger";
+import { parsePhoneNumberFromString } from "libphonenumber-js";
+import { phonePreg } from "../../Common/validation";
import useAppHistory from "../../Common/hooks/useAppHistory";
+import useConfig from "../../Common/hooks/useConfig";
+import { useDispatch } from "react-redux";
+import { useTranslation } from "react-i18next";
+
const PageTitle = loadable(() => import("../Common/PageTitle"));
const Loading = loadable(() => import("../Common/Loading"));
@@ -40,57 +46,6 @@ interface patientShiftProps {
patientId: number;
}
-const initForm: any = {
- shifting_approving_facility: null,
- assigned_facility: null,
- emergency: "false",
- is_up_shift: "true",
- reason: "",
- vehicle_preference: "",
- comments: "",
- refering_facility_contact_name: "",
- refering_facility_contact_number: "",
- assigned_facility_type: "",
- preferred_vehicle_choice: "",
- breathlessness_level: "",
-};
-
-const requiredFields: any = {
- shifting_approving_facility: {
- errorText: "Name of the referring facility",
- },
- refering_facility_contact_name: {
- errorText: "Name of contact of the referring facility",
- },
- refering_facility_contact_number: {
- errorText: "Phone number of contact of the referring facility",
- invalidText: "Please enter valid phone number",
- },
- reason: {
- errorText: "Reason for shifting in mandatory",
- invalidText: "Please enter reason for shifting",
- },
- assigned_facility_type: {
- errorText: "Please Select Facility Type",
- },
- preferred_vehicle_choice: {
- errorText: "Please Preferred Vehicle Type",
- },
- breathlessness_level: {
- errorText: "Severity of Breathlessness is required",
- },
-};
-
-const initError = Object.assign(
- {},
- ...Object.keys(initForm).map((k) => ({ [k]: "" }))
-);
-
-const initialState = {
- form: { ...initForm },
- errors: { ...initError },
-};
-
export const ShiftCreate = (props: patientShiftProps) => {
const { goBack } = useAppHistory();
const { facilityId, patientId } = props;
@@ -98,12 +53,82 @@ export const ShiftCreate = (props: patientShiftProps) => {
const [isLoading, setIsLoading] = useState(false);
const [facilityName, setFacilityName] = useState("");
const [patientName, setPatientName] = useState("");
+ const [patientCategory, setPatientCategory] = useState
();
+ const { t } = useTranslation();
+ const { wartime_shifting } = useConfig();
+
+ const initForm: any = {
+ shifting_approving_facility: null,
+ assigned_facility: null,
+ emergency: "false",
+ is_up_shift: "true",
+ reason: "",
+ vehicle_preference: "",
+ comments: "",
+ refering_facility_contact_name: "",
+ refering_facility_contact_number: "",
+ assigned_facility_type: null,
+ preferred_vehicle_choice: null,
+ breathlessness_level: null,
+ patient_category: "",
+ ambulance_driver_name: "",
+ ambulance_phone_number: "",
+ ambulance_number: "",
+ };
+
+ let requiredFields: any = {
+ refering_facility_contact_name: {
+ errorText: "Name of contact of the current facility",
+ },
+ refering_facility_contact_number: {
+ errorText: "Phone number of contact of the current facility",
+ invalidText: "Please enter valid phone number",
+ },
+ reason: {
+ errorText: "Reason for shifting in mandatory",
+ invalidText: "Please enter reason for shifting",
+ },
+ };
+
+ if (wartime_shifting) {
+ requiredFields = {
+ ...requiredFields,
+ shifting_approving_facility: {
+ errorText: "Name of the referring facility",
+ },
+ assigned_facility_type: {
+ errorText: "Please Select Facility Type",
+ },
+ preferred_vehicle_choice: {
+ errorText: "Please Preferred Vehicle Type",
+ },
+ breathlessness_level: {
+ errorText: "Severity of Breathlessness is required",
+ },
+ };
+ }
+
+ const initError = Object.assign(
+ {},
+ ...Object.keys(initForm).map((k) => ({ [k]: "" }))
+ );
+
+ const initialState = {
+ form: { ...initForm },
+ errors: { ...initError },
+ };
useEffect(() => {
async function fetchPatientName() {
if (patientId) {
const res = await dispatchAction(getPatient({ id: patientId }));
if (res.data) {
+ const patient_category =
+ res.data.last_consultation?.last_daily_round?.patient_category ??
+ res.data.last_consultation?.category;
+ setPatientCategory(
+ PATIENT_CATEGORIES.find((c) => c.text === patient_category)?.id
+ );
setPatientName(res.data.name);
setFacilityName(res.data.facility_object.name);
}
@@ -204,12 +229,19 @@ export const ShiftCreate = (props: patientShiftProps) => {
setIsLoading(true);
const data = {
- status: "PENDING",
+ status: wartime_shifting ? "PENDING" : "APPROVED",
orgin_facility: props.facilityId,
shifting_approving_facility: (
state.form.shifting_approving_facility || {}
).id,
- assigned_facility: (state.form.assigned_facility || {}).id,
+ assigned_facility:
+ state.form?.assigned_facility?.id != -1
+ ? state.form?.assigned_facility?.id
+ : null,
+ assigned_facility_external:
+ state.form?.assigned_facility?.id === -1
+ ? state.form?.assigned_facility?.name
+ : null,
patient: props.patientId,
emergency: state.form.emergency === "true",
is_up_shift: state.form.is_up_shift === "true",
@@ -224,6 +256,12 @@ export const ShiftCreate = (props: patientShiftProps) => {
state.form.refering_facility_contact_number
)?.format("E.164"),
breathlessness_level: state.form.breathlessness_level,
+ patient_category: patientCategory,
+ ambulance_driver_name: state.form.ambulance_driver_name,
+ ambulance_phone_number: parsePhoneNumberFromString(
+ state.form.ambulance_phone_number
+ )?.format("E.164"),
+ ambulance_number: state.form.ambulance_number,
};
const res = await dispatchAction(createShift(data));
@@ -262,7 +300,7 @@ export const ShiftCreate = (props: patientShiftProps) => {
{
/>
-
-
- Name of shifting approving facility{" "}
- *
-
-
- handleValueChange(value, "shifting_approving_facility")
- }
- errors={state.errors.shifting_approving_facility}
- />
-
-
-
+ {wartime_shifting && (
+
+
+ Name of shifting approving facility{" "}
+ *
+
+
+ handleValueChange(value, "shifting_approving_facility")
+ }
+ errors={state.errors.shifting_approving_facility}
+ />
+
+ )}
+
+
- What facility would you like to assign the patient to
+ {t("what_facility_assign_the_patient_to")}
{
setSelected={(value: any) =>
handleValueChange(value, "assigned_facility")
}
+ freeText={true}
errors={state.errors.assigned_facility}
/>
@@ -365,69 +406,73 @@ export const ShiftCreate = (props: patientShiftProps) => {
- {/*
- Vehicle preference
-
-
*/}
-
-
- Preferred Vehicle *
-
-
-
-
-
- Preferred Facility Type{" "}
- *
-
-
-
-
-
- Severity of Breathlessness{" "}
- *
-
-
+ {
+ setPatientCategory(e.value);
+ }}
+ label="Patient Category"
/>
+ {wartime_shifting && (
+ <>
+
+
+ Preferred Vehicle *
+
+
+
+
+
+ Preferred Facility Type{" "}
+ *
+
+
+
+
+
+ Severity of Breathlessness{" "}
+ *
+
+
+
+ >
+ )}
+
{
/>
+
+
+
+
+
+
{
+ handleFormFieldChange(event);
+ }}
+ error={state.errors.ambulance_phone_number}
+ />
+
+
+
+
+
import("../Common/PageTitle"));
const Loading = loadable(() => import("../Common/Loading"));
@@ -91,6 +85,7 @@ const initialState = {
export default function ResourceCreate(props: resourceProps) {
const { goBack } = useAppHistory();
const { facilityId } = props;
+ const { t } = useTranslation();
const dispatchAction: any = useDispatch();
const [isLoading, setIsLoading] = useState(false);
@@ -134,9 +129,9 @@ export default function ResourceCreate(props: resourceProps) {
const errors = { ...initError };
let isInvalidForm = false;
Object.keys(requiredFields).forEach((field) => {
- const phoneNumber = parsePhoneNumberFromString(state.form[field]);
switch (field) {
- case "refering_facility_contact_number":
+ case "refering_facility_contact_number": {
+ const phoneNumber = parsePhoneNumberFromString(state.form[field]);
if (!state.form[field]) {
errors[field] = requiredFields[field].errorText;
isInvalidForm = true;
@@ -148,6 +143,7 @@ export default function ResourceCreate(props: resourceProps) {
isInvalidForm = true;
}
return;
+ }
default:
if (!state.form[field]) {
errors[field] = requiredFields[field].errorText;
@@ -160,9 +156,9 @@ export default function ResourceCreate(props: resourceProps) {
return !isInvalidForm;
};
- const handleChange = (e: any) => {
+ const handleChange = (e: FieldChangeEvent) => {
const form = { ...state.form };
- const { name, value } = e.target;
+ const { name, value } = e;
form[name] = value;
dispatch({ type: "set_form", form });
};
@@ -225,7 +221,7 @@ export default function ResourceCreate(props: resourceProps) {
return (
-
- Name of Contact Person at Facility*
-
-
-
-
+
+
- Name of approving facility*
+ {t("approving_facility")}
-
- Category
-
-
-
-
- Subcategory
-
-
-
-
- Request Title*
-
-
-
-
-
- Required Quantity
-
-
-
+
option}
+ optionValue={(option: string) => option}
+ onChange={({ value }) => handleValueChange(value, "category")}
+ />
+ option.text}
+ optionValue={(option: OptionsType) => option.id}
+ onChange={({ value }) =>
+ handleValueChange(value, "sub_category")
+ }
+ />
+
+
+
+
- Description of request*
-
-
- Is this an emergency?
-
-
- }
- label="Yes"
- />
- }
- label="No"
- />
-
-
-
-
+ (o ? t("yes") : t("no"))}
+ optionValue={(o) => String(o)}
+ value={state.form.emergency}
+ onChange={handleChange}
+ />
goBack()} />
diff --git a/src/Components/Resource/ResourceDetails.tsx b/src/Components/Resource/ResourceDetails.tsx
index f48efed7559..6b69bdf0eca 100644
--- a/src/Components/Resource/ResourceDetails.tsx
+++ b/src/Components/Resource/ResourceDetails.tsx
@@ -331,7 +331,7 @@ export default function ResourceDetails(props: { id: string }) {
- Contact person at the facility:{" "}
+ Contact person at the current facility:{" "}
{data.refering_facility_contact_name || "--"}
diff --git a/src/Components/Shifting/BadgesList.tsx b/src/Components/Shifting/BadgesList.tsx
index bf378aa8e80..ff6144776f2 100644
--- a/src/Components/Shifting/BadgesList.tsx
+++ b/src/Components/Shifting/BadgesList.tsx
@@ -1,8 +1,9 @@
-import { useState, useEffect } from "react";
-import { getUserList, getAnyFacility } from "../../Redux/actions";
+import { getAnyFacility, getUserList } from "../../Redux/actions";
+import { useEffect, useState } from "react";
+
+import { SHIFTING_FILTER_ORDER } from "../../Common/constants";
import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
-import { SHIFTING_FILTER_ORDER } from "../../Common/constants";
export default function BadgesList(props: any) {
const { qParams, FilterBadges } = props;
diff --git a/src/Components/Shifting/BoardView.tsx b/src/Components/Shifting/BoardView.tsx
index db2db299140..79c85fb7572 100644
--- a/src/Components/Shifting/BoardView.tsx
+++ b/src/Components/Shifting/BoardView.tsx
@@ -1,9 +1,11 @@
-import React, { useState } from "react";
+import {
+ SHIFTING_CHOICES_PEACETIME,
+ SHIFTING_CHOICES_WARTIME,
+} from "../../Common/constants";
import BadgesList from "./BadgesList";
import { ExportButton } from "../Common/Export";
import ListFilter from "./ListFilter";
-import { SHIFTING_CHOICES } from "../../Common/constants";
import SearchInput from "../Form/SearchInput";
import ShiftingBoard from "./ShiftingBoard";
import { downloadShiftRequests } from "../../Redux/actions";
@@ -12,6 +14,7 @@ import loadable from "@loadable/component";
import { navigate } from "raviger";
import useConfig from "../../Common/hooks/useConfig";
import useFilters from "../../Common/hooks/useFilters";
+import { useState } from "react";
import { useTranslation } from "react-i18next";
import withScrolling from "react-dnd-scrolling";
@@ -25,16 +28,28 @@ export default function BoardView() {
});
const { wartime_shifting } = useConfig();
- const shiftStatusOptions = SHIFTING_CHOICES.map((obj) => obj.text).filter(
- (choice) => wartime_shifting || choice !== "PENDING"
- );
+ const shiftStatusOptions = wartime_shifting
+ ? SHIFTING_CHOICES_WARTIME
+ : SHIFTING_CHOICES_PEACETIME;
+
+ const COMPLETED = wartime_shifting
+ ? [
+ "COMPLETED",
+ "REJECTED",
+ "CANCELLED",
+ "DESTINATION REJECTED",
+ "PATIENT EXPIRED",
+ ]
+ : ["CANCELLED", "PATIENT EXPIRED"];
- const COMPLETED = ["COMPLETED", "REJECTED", "DESTINATION REJECTED"];
- const ACTIVE = shiftStatusOptions.filter(
- (option) => !COMPLETED.includes(option)
+ const completedBoards = shiftStatusOptions.filter((option) =>
+ COMPLETED.includes(option.text)
+ );
+ const activeBoards = shiftStatusOptions.filter(
+ (option) => !COMPLETED.includes(option.text)
);
- const [boardFilter, setBoardFilter] = useState(ACTIVE);
+ const [boardFilter, setBoardFilter] = useState(activeBoards);
const [isLoading] = useState(false);
const { t } = useTranslation();
@@ -68,22 +83,22 @@ export default function BoardView() {
setBoardFilter(ACTIVE)}
+ onClick={() => setBoardFilter(activeBoards)}
>
{t("active")}
setBoardFilter(COMPLETED)}
+ onClick={() => setBoardFilter(completedBoards)}
>
{t("completed")}
@@ -116,9 +131,10 @@ export default function BoardView() {
) : (
boardFilter.map((board) => (
))
diff --git a/src/Components/Shifting/ListFilter.tsx b/src/Components/Shifting/ListFilter.tsx
index f8dce323208..099926f78e4 100644
--- a/src/Components/Shifting/ListFilter.tsx
+++ b/src/Components/Shifting/ListFilter.tsx
@@ -5,6 +5,10 @@ import {
} from "../../Common/constants";
import { DateRangePicker, getDate } from "../Common/DateRangePicker";
import React, { useEffect, useState } from "react";
+import {
+ SHIFTING_CHOICES_PEACETIME,
+ SHIFTING_CHOICES_WARTIME,
+} from "../../Common/constants";
import { getAnyFacility, getUserList } from "../../Redux/actions";
import { CircularProgress } from "@material-ui/core";
@@ -14,7 +18,6 @@ import { FieldLabel } from "../Form/FormFields/FormField";
import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover";
import { LegacySelectField } from "../Common/HelperInputFields";
import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField";
-import { SHIFTING_CHOICES } from "../../Common/constants";
import { UserSelect } from "../Common/UserSelect2";
import moment from "moment";
import { navigate } from "raviger";
@@ -57,9 +60,9 @@ export default function ListFilter(props: any) {
const [isAssignedUserLoading, setAssignedUserLoading] = useState(false);
const { t } = useTranslation();
- const shiftStatusOptions = SHIFTING_CHOICES.map((obj) => obj.text).filter(
- (choice) => wartime_shifting || choice !== "PENDING"
- );
+ const shiftStatusOptions = (
+ wartime_shifting ? SHIFTING_CHOICES_WARTIME : SHIFTING_CHOICES_PEACETIME
+ ).map((option) => option.text);
const [filterState, setFilterState] = useMergeState({
orgin_facility: filter.orgin_facility || "",
@@ -302,25 +305,27 @@ export default function ListFilter(props: any) {
-
-
{t("shifting_approving_facility")}
-
- {isShiftingLoading ? (
-
- ) : (
-
- setFacility(obj, "shifting_approving_facility")
- }
- className="shifting-page-filter-dropdown"
- errors={""}
- />
- )}
+ {wartime_shifting && (
+
+
{t("shifting_approving_facility")}
+
+ {isShiftingLoading ? (
+
+ ) : (
+
+ setFacility(obj, "shifting_approving_facility")
+ }
+ className="shifting-page-filter-dropdown"
+ errors={""}
+ />
+ )}
+
-
+ )}
{t("assigned_facility")}
diff --git a/src/Components/Shifting/ListView.tsx b/src/Components/Shifting/ListView.tsx
index fefff000758..6a8739df848 100644
--- a/src/Components/Shifting/ListView.tsx
+++ b/src/Components/Shifting/ListView.tsx
@@ -1,29 +1,32 @@
-import { useState, useEffect } from "react";
-import loadable from "@loadable/component";
-import { navigate } from "raviger";
-import { useDispatch } from "react-redux";
-import moment from "moment";
import {
- listShiftRequests,
completeTransfer,
downloadShiftRequests,
+ listShiftRequests,
} from "../../Redux/actions";
-import ListFilter from "./ListFilter";
-import { formatFilter } from "./Commons";
-import { formatDate } from "../../Utils/utils";
-import SearchInput from "../Form/SearchInput";
-import useFilters from "../../Common/hooks/useFilters";
+import { useEffect, useState } from "react";
+
import BadgesList from "./BadgesList";
-import { ExportButton } from "../Common/Export";
-import { useTranslation } from "react-i18next";
import ButtonV2 from "../Common/components/ButtonV2";
import ConfirmDialogV2 from "../Common/ConfirmDialogV2";
+import { ExportButton } from "../Common/Export";
+import ListFilter from "./ListFilter";
import Page from "../Common/components/Page";
+import SearchInput from "../Form/SearchInput";
+import { formatDate } from "../../Utils/utils";
+import { formatFilter } from "./Commons";
+import loadable from "@loadable/component";
+import moment from "moment";
+import { navigate } from "raviger";
+import useConfig from "../../Common/hooks/useConfig";
+import { useDispatch, useSelector } from "react-redux";
+import useFilters from "../../Common/hooks/useFilters";
+import { useTranslation } from "react-i18next";
const Loading = loadable(() => import("../Common/Loading"));
export default function ListView() {
const dispatch: any = useDispatch();
+ const { wartime_shifting } = useConfig();
const {
qParams,
updateQuery,
@@ -39,6 +42,10 @@ export default function ListView() {
externalId: undefined,
loading: false,
});
+ const rootState: any = useSelector((rootState) => rootState);
+ const { currentUser } = rootState;
+ const userHomeFacilityId = currentUser.data.home_facility;
+ const userType = currentUser.data.user_type;
const { t } = useTranslation();
const handleTransferComplete = (shift: any) => {
@@ -165,17 +172,19 @@ export default function ListView() {
-
-
-
-
- {(shift.shifting_approving_facility_object || {}).name}
-
-
-
+ {wartime_shifting && (
+
+
+
+
+ {(shift.shifting_approving_facility_object || {}).name}
+
+
+
+ )}
- {(shift.assigned_facility_object || {}).name ||
+ {shift.assigned_facility_external ||
+ shift.assigned_facility_object?.name ||
t("yet_to_be_decided")}
@@ -233,27 +243,33 @@ export default function ListView() {
{t("all_details")}
- {shift.status === "TRANSFER IN PROGRESS" &&
- shift.assigned_facility && (
-
- setModalFor(shift.external_id)}
- >
- {t("transfer_to_receiving_facility")}
-
-
- setModalFor({ externalId: undefined, loading: false })
- }
- onConfirm={() => handleTransferComplete(shift)}
- />
-
- )}
+ {shift.status === "COMPLETED" && shift.assigned_facility && (
+
+ setModalFor(shift.external_id)}
+ >
+ {t("transfer_to_receiving_facility")}
+
+
+ setModalFor({ externalId: undefined, loading: false })
+ }
+ onConfirm={() => handleTransferComplete(shift)}
+ />
+
+ )}
diff --git a/src/Components/Shifting/ShiftDetails.tsx b/src/Components/Shifting/ShiftDetails.tsx
index 4ea8abe7d59..8305f06b9a9 100644
--- a/src/Components/Shifting/ShiftDetails.tsx
+++ b/src/Components/Shifting/ShiftDetails.tsx
@@ -1,26 +1,38 @@
-import React, { useState, useCallback } from "react";
-import loadable from "@loadable/component";
-import { useDispatch } from "react-redux";
-import { statusType, useAbortableEffect } from "../../Common/utils";
-import { getShiftDetails, deleteShiftRecord } from "../../Redux/actions";
-import { navigate, Link } from "raviger";
-import QRCode from "qrcode.react";
-import { GENDER_TYPES, TEST_TYPE_CHOICES } from "../../Common/constants";
import * as Notification from "../../Utils/Notifications.js";
-import { CopyToClipboard } from "react-copy-to-clipboard";
+
+import {
+ GENDER_TYPES,
+ SHIFTING_CHOICES_PEACETIME,
+ SHIFTING_CHOICES_WARTIME,
+ TEST_TYPE_CHOICES,
+} from "../../Common/constants";
+import { Link, navigate } from "raviger";
+import React, { useCallback, useState } from "react";
+import { deleteShiftRecord, getShiftDetails } from "../../Redux/actions";
+import { statusType, useAbortableEffect } from "../../Common/utils";
+
+import ButtonV2 from "../Common/components/ButtonV2";
import CommentSection from "./CommentsSection";
+import ConfirmDialogV2 from "../Common/ConfirmDialogV2";
+import { CopyToClipboard } from "react-copy-to-clipboard";
+import Page from "../Common/components/Page";
+import QRCode from "qrcode.react";
+import RecordMeta from "../../CAREUI/display/RecordMeta";
import { formatDate } from "../../Utils/utils";
+import loadable from "@loadable/component";
import useConfig from "../../Common/hooks/useConfig";
+import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
-import RecordMeta from "../../CAREUI/display/RecordMeta";
-import ButtonV2 from "../Common/components/ButtonV2";
-import ConfirmDialogV2 from "../Common/ConfirmDialogV2";
-import Page from "../Common/components/Page";
const Loading = loadable(() => import("../Common/Loading"));
export default function ShiftDetails(props: { id: string }) {
- const { static_header_logo, kasp_full_string } = useConfig();
+ const {
+ static_header_logo,
+ kasp_full_string,
+ wartime_shifting,
+ kasp_enabled,
+ } = useConfig();
const dispatch: any = useDispatch();
const initialData: any = {};
const [data, setData] = useState(initialData);
@@ -31,6 +43,10 @@ export default function ShiftDetails(props: { id: string }) {
React.useState(false);
const { t } = useTranslation();
+ const shiftStatusOptions = wartime_shifting
+ ? SHIFTING_CHOICES_WARTIME
+ : SHIFTING_CHOICES_PEACETIME;
+
const fetchData = useCallback(
async (status: statusType) => {
setIsLoading(true);
@@ -89,7 +105,7 @@ export default function ShiftDetails(props: { id: string }) {
};
const copyContent = (data: any) => {
- const formattedText =
+ let formattedText =
t("disease_status") +
": *" +
data?.patient_object?.disease_status +
@@ -114,13 +130,13 @@ export default function ShiftDetails(props: { id: string }) {
":" +
data?.patient_object?.address +
"\n" +
- t("facility_preference") +
- ":" +
- data?.assigned_facility_type +
- "\n" +
t("reason") +
":" +
data?.reason;
+ if (wartime_shifting) {
+ formattedText +=
+ t("facility_preference") + ": " + data?.assigned_facility_type + "\n";
+ }
return formattedText;
};
@@ -161,28 +177,12 @@ export default function ShiftDetails(props: { id: string }) {
{patientData?.disease_status}
-
-
-
- {t("srf_id")}:{" "}
-
- {(patientData?.srf_id && patientData?.srf_id) || "-"}
-
{t("test_type")}:{" "}
{(patientData?.test_type && testType) || "-"}
-
-
- {t("date_of_test")}:{" "}
-
- {(patientData?.date_of_test &&
- formatDate(patientData?.date_of_test)) ||
- "-"}
-
-
{t("facility")}:{" "}
@@ -274,71 +274,6 @@ export default function ShiftDetails(props: { id: string }) {
{patientData?.address || "-"}
-
-
- {t("contact_with_confirmed_carrier")}:{" "}
-
- {patientData?.contact_with_confirmed_carrier ? (
- {t("yes")}
- ) : (
-
- {t("no")}
-
- )}
-
-
-
- {t("contact_with_suspected_carrier")}:{" "}
-
- {patientData?.contact_with_suspected_carrier ? (
- {t("yes")}
- ) : (
-
- {t("no")}
-
- )}
-
- {patientData?.estimated_contact_date && (
-
-
- {t("estimated_contact_date")}:{" "}
-
- {formatDate(patientData?.estimated_contact_date)}
-
- )}
-
-
- {t("has_sari_severe_acute_respiratory_illness")}{" "}
-
- {patientData?.has_SARI ? (
- {t("yes")}
- ) : (
-
- {t("no")}
-
- )}
-
-
-
- {t("travel_within_last_28_days")}{" "}
-
- {patientData?.past_travel ? (
- {t("yes")}
- ) : (
-
- {t("no")}
-
- )}
-
- {patientData?.countries_travelled &&
- !!patientData?.countries_travelled.length && (
-
-
- {t("countries_travelled")}:{" "}
-
- {patientData?.countries_travelled.join(", ")}
-
- )}
{patientData?.ongoing_medication && (
@@ -355,22 +290,6 @@ export default function ShiftDetails(props: { id: string }) {
{patientData?.allergies}
)}
- {!!patientData?.number_of_aged_dependents && (
-
-
- {t("number_of_aged_dependents_above_60")}:{" "}
-
- {patientData?.number_of_aged_dependents}
-
- )}
- {!!patientData?.number_of_chronic_diseased_dependents && (
-
-
- {t("number_of_chronic_diseased_dependents")}:{" "}
-
- {patientData?.number_of_chronic_diseased_dependents}
-
- )}
);
@@ -565,7 +484,9 @@ export default function ShiftDetails(props: { id: string }) {
{t("referred_to")}:{" "}
- {data.assigned_facility_object?.name || "--"}
+ {data.assigned_facility_external ||
+ data.assigned_facility_object?.name ||
+ "--"}
@@ -640,6 +561,15 @@ export default function ShiftDetails(props: { id: string }) {
options={
navigate(`/shifting/${data.external_id}/update`)}
>
{t("update_status_details")}
@@ -679,7 +609,9 @@ export default function ShiftDetails(props: { id: string }) {
Status:
- {data.status}
+ {shiftStatusOptions.find(
+ (option) => data.status === option.text
+ )?.label || data.status}
@@ -688,17 +620,21 @@ export default function ShiftDetails(props: { id: string }) {
{data.orgin_facility_object?.name || "--"}
-
-
- {t("shifting_approving_facility")}:{" "}
-
- {data.shifting_approving_facility_object?.name || "--"}
-
+ {wartime_shifting && (
+
+
+ {t("shifting_approving_facility")}:{" "}
+
+ {data.shifting_approving_facility_object?.name || "--"}
+
+ )}
{t("assigned_facility")}:{" "}
- {data.assigned_facility_object?.name || "--"}
+ {data.assigned_facility_external ||
+ data.assigned_facility_object?.name ||
+ "--"}
@@ -708,7 +644,7 @@ export default function ShiftDetails(props: { id: string }) {
- {kasp_full_string}:{" "}
+ {t("patient_category")}:{" "}
{" "}
- {data.is_kasp ? t("yes") : t("no")}
+ {data.patient_object.last_consultation?.last_daily_round
+ ?.patient_category ??
+ data.patient_object.last_consultation?.category}
-
+ {kasp_enabled && (
+
+
+ {kasp_full_string}:{" "}
+
+
+ {" "}
+ {data.is_kasp ? t("yes") : t("no")}
+
+
+ )}
+ {wartime_shifting && (
+ <>
+
+
+ {kasp_full_string}:{" "}
+
+
+ {" "}
+ {data.is_kasp ? t("yes") : t("no")}
+
+
+
+
+ {t("vehicle_preference")}:{" "}
+
+ {data.vehicle_preference || data.preferred_vehicle_choice}
+
+
+
+ {t("facility_preference")}:{" "}
+
+ {data.assigned_facility_type || "--"}
+
+
+
+ {t("severity_of_breathlessness")}:{" "}
+
+ {data.breathlessness_level || "--"}
+
{" "}
+ >
+ )}
+
+
- {t("vehicle_preference")}:{" "}
+ {t("reason")}:{" "}
- {data.vehicle_preference || data.preferred_vehicle_choice}
+ {data.reason || "--"}
-
+
- {t("facility_preference")}:{" "}
+ {t("ambulance_driver_name")}:{" "}
+
+
+ {data.ambulance_driver_name || "--"}
- {data.assigned_facility_type || "--"}
-
+
- {t("severity_of_breathlessness")}:{" "}
+ {t("ambulance_phone_number")}:{" "}
+
+
+ {data.ambulance_phone_number ? (
+
+ {data.ambulance_phone_number}
+
+ ) : (
+ "--"
+ )}
- {data.breathlessness_level || "--"}
-
- {t("reason")}:{" "}
+ {t("ambulance_number")}:{" "}
- {data.reason || "--"}
+ {data.ambulance_number || "--"}
-
{t("comments")}:{" "}
@@ -869,17 +858,20 @@ export default function ShiftDetails(props: { id: string }) {
{showFacilityCard(data.orgin_facility_object)}
-
-
{t("details_of_assigned_facility")}
- {showFacilityCard(data.assigned_facility_object)}
-
-
-
-
- {t("details_of_shifting_approving_facility")}
-
- {showFacilityCard(data.shifting_approving_facility_object)}
-
+ {!data.assigned_facility_external && (
+
+
{t("details_of_assigned_facility")}
+ {showFacilityCard(data.assigned_facility_object)}
+
+ )}
+ {wartime_shifting && (
+
+
+ {t("details_of_shifting_approving_facility")}
+
+ {showFacilityCard(data.shifting_approving_facility_object)}
+
+ )}
diff --git a/src/Components/Shifting/ShiftDetailsUpdate.tsx b/src/Components/Shifting/ShiftDetailsUpdate.tsx
index 8abcd842048..36c64efbd45 100644
--- a/src/Components/Shifting/ShiftDetailsUpdate.tsx
+++ b/src/Components/Shifting/ShiftDetailsUpdate.tsx
@@ -3,73 +3,47 @@ import * as Notification from "../../Utils/Notifications.js";
import {
BREATHLESSNESS_LEVEL,
FACILITY_TYPES,
- SHIFTING_CHOICES,
+ PATIENT_CATEGORIES,
+ SHIFTING_CHOICES_PEACETIME,
+ SHIFTING_CHOICES_WARTIME,
SHIFTING_VEHICLE_CHOICES,
} from "../../Common/constants";
-import {
- Box,
- Card,
- CardContent,
- FormControlLabel,
- Radio,
- RadioGroup,
-} from "@material-ui/core";
import { Cancel, Submit } from "../Common/components/ButtonV2";
import { getShiftDetails, getUserList, updateShift } from "../../Redux/actions";
import { navigate, useQueryParams } from "raviger";
import { statusType, useAbortableEffect } from "../../Common/utils";
import { useCallback, useEffect, useReducer, useState } from "react";
-
-import { CircularProgress } from "@material-ui/core";
import { ConsultationModel } from "../Facility/models.js";
import DischargeModal from "../Facility/DischargeModal.js";
import { FacilitySelect } from "../Common/FacilitySelect";
+import { FieldChangeEvent } from "../Form/FormFields/Utils.js";
import { FieldLabel } from "../Form/FormFields/FormField";
-import { LegacyErrorHelperText } from "../Common/HelperInputFields";
-import { LegacySelectField } from "../Common/HelperInputFields";
+import PatientCategorySelect from "../Patient/PatientCategorySelect";
+import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField";
+import { SelectFormField } from "../Form/FormFields/SelectFormField.js";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
+import TextFormField from "../Form/FormFields/TextFormField";
import { UserSelect } from "../Common/UserSelect";
import loadable from "@loadable/component";
+import { parsePhoneNumberFromString } from "libphonenumber-js";
import useAppHistory from "../../Common/hooks/useAppHistory";
import useConfig from "../../Common/hooks/useConfig";
import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
+import CircularProgress from "../Common/components/CircularProgress.js";
+import Card from "../../CAREUI/display/Card";
+import RadioFormField from "../Form/FormFields/RadioFormField.js";
+import Page from "../Common/components/Page.js";
const Loading = loadable(() => import("../Common/Loading"));
-const PageTitle = loadable(() => import("../Common/PageTitle"));
interface patientShiftProps {
id: string;
}
-const initForm: any = {
- shifting_approving_facility_object: null,
- assigned_facility_object: null,
- emergency: "false",
- is_kasp: "false",
- is_up_shift: "true",
- reason: "",
- vehicle_preference: "",
- comments: "",
- assigned_facility_type: "",
- preferred_vehicle_choice: "",
- assigned_to: "",
- initial_status: "",
-};
-
-const initError = Object.assign(
- {},
- ...Object.keys(initForm).map((k) => ({ [k]: "" }))
-);
-
-const initialState = {
- form: { ...initForm },
- errors: { ...initError },
-};
-
export const ShiftDetailsUpdate = (props: patientShiftProps) => {
const { goBack } = useAppHistory();
- const { kasp_full_string, wartime_shifting } = useConfig();
+ const { kasp_full_string, kasp_enabled, wartime_shifting } = useConfig();
const dispatchAction: any = useDispatch();
const [qParams, _] = useQueryParams();
const [isLoading, setIsLoading] = useState(true);
@@ -81,25 +55,56 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => {
const [showDischargeModal, setShowDischargeModal] = useState(false);
const { t } = useTranslation();
- const shiftStatusOptions = SHIFTING_CHOICES.map((obj) => obj.text).filter(
- (choice) => wartime_shifting || choice !== "PENDING"
+ const initForm: any = {
+ shifting_approving_facility_object: null,
+ assigned_facility_object: null,
+ emergency: "false",
+ is_kasp: "false",
+ is_up_shift: "true",
+ reason: "",
+ vehicle_preference: "",
+ comments: "",
+ assigned_facility_type: null,
+ preferred_vehicle_choice: null,
+ assigned_to: "",
+ initial_status: "",
+ patient_category: "",
+ ambulance_driver_name: "",
+ ambulance_phone_number: "",
+ ambulance_number: "",
+ };
+
+ const initError = Object.assign(
+ {},
+ ...Object.keys(initForm).map((k) => ({ [k]: "" }))
);
- const requiredFields: any = {
- shifting_approving_facility_object: {
- errorText: t("shifting_approving_facility_can_not_be_empty"),
- },
- assigned_facility_type: {
- errorText: t("please_select_facility_type"),
- },
- preferred_vehicle_choice: {
- errorText: t("please_select_preferred_vehicle_type"),
- },
+ const initialState = {
+ form: { ...initForm },
+ errors: { ...initError },
+ };
+
+ let requiredFields: any = {
reason: {
errorText: t("please_enter_a_reason_for_the_shift"),
},
};
+ if (wartime_shifting) {
+ requiredFields = {
+ ...requiredFields,
+ shifting_approving_facility_object: {
+ errorText: t("shifting_approving_facility_can_not_be_empty"),
+ },
+ assigned_facility_type: {
+ errorText: t("please_select_facility_type"),
+ },
+ preferred_vehicle_choice: {
+ errorText: t("please_select_preferred_vehicle_type"),
+ },
+ };
+ }
+
const shiftFormReducer = (state = initialState, action: any) => {
switch (action.type) {
case "set_form": {
@@ -153,23 +158,24 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => {
return !isInvalidForm;
};
- const handleChange = (e: any) => {
+ const handleOnSelect = (user: any) => {
const form = { ...state.form };
- const { name, value } = e.target;
- form[name] = value;
+ form["assigned_to"] = user?.id;
+ SetAssignedUser(user);
dispatch({ type: "set_form", form });
};
- const handleTextAreaChange = (e: any) => {
- const form = { ...state.form };
- const { name, value } = e;
- form[name] = value;
- dispatch({ type: "set_form", form });
+
+ const handleFormFieldChange = (event: FieldChangeEvent
) => {
+ dispatch({
+ type: "set_form",
+ form: { ...state.form, [event.name]: event.value },
+ });
};
- const handleOnSelect = (user: any) => {
+ const handleTextFormFieldChange = (e: any) => {
const form = { ...state.form };
- form["assigned_to"] = user?.id;
- SetAssignedUser(user);
+ const { name, value } = e;
+ form[name] = value;
dispatch({ type: "set_form", form });
};
@@ -179,17 +185,29 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => {
dispatch({ type: "set_form", form });
};
- const handleSubmit = async () => {
+ const handleSubmit = async (discharged = false) => {
const validForm = validateForm();
if (validForm) {
+ if (!discharged && state.form.status === "PATIENT EXPIRED") {
+ setShowDischargeModal(true);
+ return;
+ }
+
setIsLoading(true);
const data: any = {
orgin_facility: state.form.orgin_facility_object?.id,
shifting_approving_facility:
state.form?.shifting_approving_facility_object?.id,
- assigned_facility: state.form?.assigned_facility_object?.id,
+ assigned_facility:
+ state.form?.assigned_facility_object?.id != -1
+ ? state.form?.assigned_facility_object?.id
+ : null,
+ assigned_facility_external:
+ state.form?.assigned_facility_object?.id === -1
+ ? state.form?.assigned_facility_object?.name
+ : null,
patient: state.form.patient_object?.id,
emergency: [true, "true"].includes(state.form.emergency),
is_kasp: [true, "true"].includes(state.form.is_kasp),
@@ -201,6 +219,12 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => {
preferred_vehicle_choice: state.form.preferred_vehicle_choice,
assigned_to: state.form.assigned_to,
breathlessness_level: state.form.breathlessness_level,
+ patient_category: state.form.patient_category,
+ ambulance_driver_name: state.form.ambulance_driver_name,
+ ambulance_phone_number: parsePhoneNumberFromString(
+ state.form.ambulance_phone_number
+ )?.format("E.164"),
+ ambulance_number: state.form.ambulance_number,
};
if (state.form.status !== state.form.initial_status) {
@@ -216,11 +240,7 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => {
msg: t("shift_request_updated_successfully"),
});
- if (data.status === "PATIENT EXPIRED") {
- setShowDischargeModal(true);
- } else {
- navigate(`/shifting/${props.id}`);
- }
+ navigate(`/shifting/${props.id}`);
} else {
setIsLoading(false);
}
@@ -235,8 +255,19 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => {
if (res && res.data) {
const d = res.data;
setConsultationData(d.patient.last_consultation);
+ if (d.assigned_facility_external)
+ d["assigned_facility_object"] = {
+ id: -1,
+ name: res.data.assigned_facility_external,
+ };
d["initial_status"] = res.data.status;
d["status"] = qParams.status || res.data.status;
+ const patient_category =
+ d.patient.last_consultation?.last_daily_round?.patient_category ??
+ d.patient.last_consultation?.category;
+ d["patient_category"] = PATIENT_CATEGORIES.find(
+ (c) => c.text === patient_category
+ )?.id;
dispatch({ type: "set_form", form: d });
}
setIsLoading(false);
@@ -260,238 +291,235 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => {
}
return (
-
+
setShowDischargeModal(false)}
consultationData={consultationData}
discharge_reason="EXP"
afterSubmit={() => {
- navigate(
- `/facility/${consultationData.facility}/patient/${consultationData.patient}/consultation/${consultationData.id}`
- );
+ handleSubmit(true);
}}
/>
-
-
-
-
-
-
- {t("status")}
-
-
-
-
{t("assigned_to")}
-
- {assignedUserLoading ? (
-
- ) : (
-
- )}
-
-
-
-
- {t("name_of_shifting_approving_facility")}
-
-
+
+
option.text}
+ optionValue={(option) => option.text}
+ optionSelectedLabel={(option) => option.text}
+ onChange={handleFormFieldChange}
+ className="bg-white w-full md:leading-5 mt-2 md:col-span-1"
+ />
+
+ {wartime_shifting && (
+
+ {t("assigned_to")}
+ {assignedUserLoading ? (
+
+ ) : (
+
- setFacility(obj, "shifting_approving_facility_object")
+ selected={assignedUser}
+ setSelected={handleOnSelect}
+ errors={""}
+ facilityId={
+ state.form?.shifting_approving_facility_object?.id
}
- errors={state.errors.shifting_approving_facility_object}
/>
-
-
-
-
- {t("what_facility_assign_the_patient_to")}
-
-
- setFacility(obj, "assigned_facility_object")
- }
- errors={state.errors.assigned_facility}
- />
-
-
-
- {t("is_this_an_emergency")}
-
-
- }
- label={t("yes")}
- />
- }
- label={t("no")}
- />
-
-
-
-
-
-
-
- {t("is")} {kasp_full_string}?
-
-
-
- }
- label={t("yes")}
- />
- }
- label={t("no")}
- />
-
-
-
-
-
-
- {t("is_this_an_upshift")}
-
-
- }
- label={t("yes")}
- />
- }
- label={t("no")}
- />
-
-
-
-
-
- {t("preferred_vehicle")}
-
-
-
- {t("preferred_facility_type")}*
-
-
-
- {t("severity_of_breathlessness")}*
-
-
-
-
-
-
-
-
-
-
-
- goBack()} />
-
-
+ )}
+
+ )}
+
+ {wartime_shifting && (
+
+
+ {t("name_of_shifting_approving_facility")}
+
+
+ setFacility(obj, "shifting_approving_facility_object")
+ }
+ errors={state.errors.shifting_approving_facility_object}
+ />
-
-
-
-
+ )}
+
+
+ {t("what_facility_assign_the_patient_to")}
+
+ setFacility(obj, "assigned_facility_object")
+ }
+ errors={state.errors.assigned_facility}
+ />
+
+
+ option.label}
+ optionValue={(option) => option.value}
+ />
+
+ {kasp_enabled && (
+ option.value}
+ optionDisplay={(option) => option.label}
+ onChange={handleFormFieldChange}
+ />
+ )}
+
+ option.value}
+ optionDisplay={(option) => option.label}
+ onChange={handleFormFieldChange}
+ />
+
+
+
+ {wartime_shifting && (
+ <>
+ option}
+ optionValue={(option) => option}
+ onChange={handleFormFieldChange}
+ className="bg-white h-11 w-full mt-2 shadow-sm md:leading-5"
+ error={state.errors.preferred_vehicle_choice}
+ />
+ option}
+ optionValue={(option) => option}
+ onChange={handleFormFieldChange}
+ className="bg-white h-11 w-full mt-2 shadow-sm md:leading-5 md:col-span-1"
+ error={state.errors.assigned_facility_type}
+ />
+ option}
+ optionValue={(option) => option}
+ onChange={handleFormFieldChange}
+ className="bg-white h-11 w-full mt-2 shadow-sm md:leading-5 md:col-span-1"
+ />
+ >
+ )}
+
+
+
+
+
+ {
+ handleFormFieldChange(event);
+ }}
+ error={state.errors.ambulance_phone_number}
+ />
+
+
+
+
+
+
+ goBack()} />
+ handleSubmit()} />
+
+
+
+
);
};
diff --git a/src/Components/Shifting/ShiftingBoard.tsx b/src/Components/Shifting/ShiftingBoard.tsx
index 98bf39cea6e..c7806c0eabb 100644
--- a/src/Components/Shifting/ShiftingBoard.tsx
+++ b/src/Components/Shifting/ShiftingBoard.tsx
@@ -1,25 +1,28 @@
-import React, { useState, useEffect } from "react";
-import { useDispatch } from "react-redux";
+import React, { useEffect, useState } from "react";
+import { classNames, formatDate } from "../../Utils/utils";
import {
- listShiftRequests,
completeTransfer,
downloadShiftRequests,
+ listShiftRequests,
} from "../../Redux/actions";
-import { navigate } from "raviger";
-import moment from "moment";
-import ConfirmDialogV2 from "../Common/ConfirmDialogV2";
-import { CSVLink } from "react-csv";
-import CircularProgress from "../Common/components/CircularProgress";
import { useDrag, useDrop } from "react-dnd";
-import { classNames, formatDate } from "../../Utils/utils";
+
import ButtonV2 from "../Common/components/ButtonV2";
+import { CSVLink } from "react-csv";
import CareIcon from "../../CAREUI/icons/CareIcon";
+import CircularProgress from "../Common/components/CircularProgress";
+import ConfirmDialogV2 from "../Common/ConfirmDialogV2";
+import moment from "moment";
+import { navigate } from "raviger";
+import useConfig from "../../Common/hooks/useConfig";
+import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
const limit = 14;
interface boardProps {
board: string;
+ title?: string;
filterProp: any;
formatFilter: any;
}
@@ -39,6 +42,7 @@ const reduceLoading = (action: string, current: any) => {
const ShiftCard = ({ shift, filter }: any) => {
const dispatch: any = useDispatch();
+ const { wartime_shifting } = useConfig();
const [modalFor, setModalFor] = useState({
externalId: undefined,
loading: false,
@@ -48,6 +52,10 @@ const ShiftCard = ({ shift, filter }: any) => {
item: shift,
collect: (monitor) => ({ isDragging: !!monitor.isDragging() }),
}));
+ const rootState: any = useSelector((rootState) => rootState);
+ const { currentUser } = rootState;
+ const userHomeFacilityId = currentUser.data.home_facility;
+ const userType = currentUser.data.user_type;
const { t } = useTranslation();
const handleTransferComplete = (shift: any) => {
@@ -109,17 +117,19 @@ const ShiftCard = ({ shift, filter }: any) => {
-
-
-
-
- {(shift.shifting_approving_facility_object || {}).name}
-
-
-
+ {wartime_shifting && (
+
+
+
+
+ {(shift.shifting_approving_facility_object || {}).name}
+
+
+
+ )}
{
- {(shift.assigned_facility_object || {}).name ||
+ {shift.assigned_facility_external ||
+ shift.assigned_facility_object?.name ||
t("yet_to_be_decided")}
@@ -201,12 +212,18 @@ const ShiftCard = ({ shift, filter }: any) => {
{t("all_details")}
- {filter === "TRANSFER IN PROGRESS" && shift.assigned_facility && (
+ {filter === "COMPLETED" && shift.assigned_facility && (
setModalFor(shift.external_id)}
>
{t("transfer_to_receiving_facility")}
@@ -236,6 +253,7 @@ const ShiftCard = ({ shift, filter }: any) => {
export default function ShiftingBoard({
board,
+ title,
filterProp,
formatFilter,
}: boardProps) {
@@ -337,9 +355,6 @@ export default function ShiftingBoard({
));
};
- const renderBoardTitle = (board: string) =>
- board === "APPROVED" ? t("awaiting_destination_approval") : board;
-
return (
- {renderBoardTitle(board)}{" "}
+ {title || board}{" "}
{downloadLoading ? (
) : (
diff --git a/src/Components/Users/SkillsSlideOver.tsx b/src/Components/Users/SkillsSlideOver.tsx
index 973f7ed559f..785a2513a18 100644
--- a/src/Components/Users/SkillsSlideOver.tsx
+++ b/src/Components/Users/SkillsSlideOver.tsx
@@ -55,6 +55,10 @@ export default ({ show, setShow, username }: IProps) => {
Notification.Error({
msg: "Error while adding skill",
});
+ } else {
+ Notification.Success({
+ msg: "Skill added successfully",
+ });
}
setSelectedSkill(null);
setIsLoading(false);
diff --git a/src/Locale/en/Asset.json b/src/Locale/en/Asset.json
new file mode 100644
index 00000000000..24a44ff187c
--- /dev/null
+++ b/src/Locale/en/Asset.json
@@ -0,0 +1,3 @@
+{
+ "create_asset": "Create Asset"
+}
\ No newline at end of file
diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json
index ffc0508f60d..d8d2f20abba 100644
--- a/src/Locale/en/Common.json
+++ b/src/Locale/en/Common.json
@@ -2,10 +2,20 @@
"coronasafe_network": "CoronaSafe Network",
"goal": "Our goal is to continuously improve the quality and accessibility of public healthcare services using digital tools.",
"something_wrong": "Something went wrong! Try again later!",
+ "try_again_later": "Try again later!",
"contribute_github": "Contribute on Github",
"footer_body": "CoronaSafe Network is an open-source public utility designed by a multi-disciplinary team of innovators and volunteers. CoronaSafe CARE is a Digital Public Good recognised by the United Nations.",
"reset": "Reset",
+ "download": "Download",
"downloads": "Downloads",
+ "downloading": "Downloading",
+ "generating": "Generating",
+ "send_email": "Send Email",
+ "email_address": "Email Address",
+ "email_success": "We will be sending an email shortly. Please check your inbox.",
+ "disclaimer": "Disclaimer",
+ "category": "Category",
+ "sub_category": "Sub Category",
"download_type": "Download Type",
"state": "State",
"district": "District",
@@ -20,12 +30,16 @@
"care": "CARE",
"something_went_wrong": "Something went wrong..!",
"stop": "Stop",
+ "record": "Record",
+ "recording": "Recording",
"yes": "Yes",
"no": "No",
"status": "Status",
"created": "Created",
"modified": "Modified",
"updated": "Updated",
+ "update": "Update",
+ "configure": "Configure",
"assigned_to": "Assigned to",
"cancel": "Cancel",
"clear": "Clear",
@@ -77,13 +91,12 @@
"type_your_comment": "Type your comment",
"any_other_comments": "Any other comments",
"loading": "Loading",
- "download": "Download",
"facility": "Facility",
"local_body": "Local body",
"filters": "Filters",
"unknown": "Unknown",
"active": "Active",
- "completed": "Completed",
+ "completed": "Archived",
"on": "On",
"open": "Open",
"features": "Features",
@@ -104,6 +117,7 @@
"recommended_aspect_ratio_for": "Recommended aspect ratio for",
"drag_drop_image_to_upload": "Drag & drop image to upload",
"upload_an_image": "Upload an image",
+ "upload": "Upload",
"uploading": "Uploading",
"switch": "Switch",
"capture": "Capture",
@@ -121,5 +135,18 @@
"RESPIRATORY_SUPPORT_UNKNOWN": "None",
"RESPIRATORY_SUPPORT_OXYGEN_SUPPORT": "O2 Support",
"RESPIRATORY_SUPPORT_NON_INVASIVE": "NIV",
- "RESPIRATORY_SUPPORT_INVASIVE": "IV"
+ "RESPIRATORY_SUPPORT_INVASIVE": "IV",
+ "choose_file": "Choose File",
+ "frequency": "Frequency",
+ "days": "Days",
+ "never": "never",
+ "notes": "Notes",
+ "add_notes": "Add notes",
+ "optional": "Optional",
+ "discontinue": "Discontinue",
+ "discontinued": "Discontinued",
+ "not_specified": "Not Specified",
+ "all_changes_have_been_saved": "All changes have been saved",
+ "no_data_found": "No data found",
+ "edit": "Edit"
}
\ No newline at end of file
diff --git a/src/Locale/en/Consultation.json b/src/Locale/en/Consultation.json
index a8c1adf685f..9c6799cac16 100644
--- a/src/Locale/en/Consultation.json
+++ b/src/Locale/en/Consultation.json
@@ -2,6 +2,13 @@
"no_consultation_updates": "No consultation updates",
"consultation_updates": "Consultation updates",
"update_log": "Update Log",
+ "log_lab_results": "Log Lab Results",
"no_log_update_delta": "No changes since previous log update",
- "virtual_nursing_assistant": "Virtual Nursing Assistant"
+ "virtual_nursing_assistant": "Virtual Nursing Assistant",
+ "discharge": "Discharge",
+ "generating_discharge_summary": "Generating discharge summary",
+ "discharge_summary_not_ready": "Discharge summary is not ready yet.",
+ "download_discharge_summary": "Download discharge summary",
+ "email_discharge_summary_description": "Enter your valid email address to receive the discharge summary",
+ "generated_summary_caution": "This is a computer generated summary using the information captured in the CARE system."
}
\ No newline at end of file
diff --git a/src/Locale/en/Medicine.json b/src/Locale/en/Medicine.json
new file mode 100644
index 00000000000..779e93373d6
--- /dev/null
+++ b/src/Locale/en/Medicine.json
@@ -0,0 +1,50 @@
+{
+ "medicine": "Medicine",
+ "route": "Route",
+ "dosage": "Dosage",
+ "indicator": "Indicator",
+ "inidcator_event": "Indicator Event",
+ "max_dosage_24_hrs": "Max. dosage in 24 hrs.",
+ "min_time_bw_doses": "Min. time b/w doses",
+ "manage_prescriptions": "Manage Prescriptions",
+ "prescription_details": "Prescription Details",
+ "prescription_medications": "Prescription Medications",
+ "prn_prescriptions": "PRN Prescriptions",
+ "prescription": "Prescription",
+ "discharge_prescription": "Discharge Prescription",
+ "edit_prescriptions": "Edit Prescriptions",
+ "prescription_medication": "Prescription Medication",
+ "add_prescription_medication": "Add Prescription Medication",
+ "prn_prescription": "PRN Prescription",
+ "add_prn_prescription": "Add PRN Prescription",
+ "add_prescription_to_consultation_note": "Add a new prescription to this consultation.",
+ "medicine_administration_history": "Medicine Administration History",
+ "return_to_patient_dashboard": "Return to Patient Dashboard",
+ "administered_on": "Administered on",
+ "administer": "Administer",
+ "administer_medicine": "Administer Medicine",
+ "administer_medicines": "Administer Medicines",
+ "administer_selected_medicines": "Administer Selected Medicines",
+ "select_for_administration": "Select for Administration",
+ "medicines_administered": "Medicine(s) administered",
+ "medicines_administered_error": "Error administering medicine(s)",
+ "prescription_discontinued": "Prescription discontinued",
+ "administration_notes": "Administration Notes",
+ "last_administered": "Last administered",
+ "modification_caution_note": "No modifications possible once added",
+ "discontinue_caution_note": "Are you sure you want to discontinue this prescription?",
+ "reason_for_discontinuation": "Reason for discontinuation",
+ "PRESCRIPTION_ROUTE_ORAL": "Oral",
+ "PRESCRIPTION_ROUTE_IV": "IV",
+ "PRESCRIPTION_ROUTE_IM": "IM",
+ "PRESCRIPTION_ROUTE_SC": "S/C",
+ "PRESCRIPTION_FREQUENCY_STAT": "Imediately",
+ "PRESCRIPTION_FREQUENCY_OD": "Once daily",
+ "PRESCRIPTION_FREQUENCY_HS": "Night only",
+ "PRESCRIPTION_FREQUENCY_BD": "Twice daily",
+ "PRESCRIPTION_FREQUENCY_TID": "8th hourly",
+ "PRESCRIPTION_FREQUENCY_QID": "6th hourly",
+ "PRESCRIPTION_FREQUENCY_Q4H": "4th hourly",
+ "PRESCRIPTION_FREQUENCY_QOD": "Alternate day",
+ "PRESCRIPTION_FREQUENCY_QWK": "Once a week"
+}
\ No newline at end of file
diff --git a/src/Locale/en/Resource.json b/src/Locale/en/Resource.json
new file mode 100644
index 00000000000..af69b32fa29
--- /dev/null
+++ b/src/Locale/en/Resource.json
@@ -0,0 +1,11 @@
+{
+ "create_resource_request": "Create Resource Request",
+ "contact_person": "Name of Contact Person at Facility",
+ "approving_facility": "Name of Approving Facility",
+ "contact_phone": "Contact Person Number",
+ "request_title": "Request Title",
+ "request_title_placeholder": "Type your title here",
+ "required_quantity": "Required Quantity",
+ "request_description": "Description of Request",
+ "request_description_placeholder": "Type your description here"
+}
\ No newline at end of file
diff --git a/src/Locale/en/Shifting.json b/src/Locale/en/Shifting.json
index d33a8b3f9c0..2d31d1397a2 100644
--- a/src/Locale/en/Shifting.json
+++ b/src/Locale/en/Shifting.json
@@ -7,7 +7,7 @@
"disease_status": "Disease status",
"breathlessness_level": "Breathlessness level",
"assigned_facility": "Facility assigned",
- "origin_facility": "Origin facility",
+ "origin_facility": "Current facility",
"shifting_approval_facility": "Shifting approval facility",
"shifting": "Shifting",
"search_patient": "Search Patient",
@@ -34,14 +34,19 @@
"details_of_origin_facility": "Details of origin facility",
"details_of_patient": "Details of patient",
"record_delete_confirm": "Are you sure you want to delete this record?",
+ "phone_number_at_current_facility": "Phone Number of Contact person at current Facility",
"authorize_shift_delete": "Authorize shift delete",
"delete_record": "Delete Record",
"severity_of_breathlessness": "Severity of Breathlessness",
"facility_preference": "Facility preference",
"vehicle_preference": "Vehicle preference",
"is_up_shift": "Is up shift",
+ "patient_category": "Patient Category",
+ "ambulance_driver_name": "Name of ambulance driver",
+ "ambulance_phone_number": "Phone number of Ambulance",
+ "ambulance_number": "Ambulance No.",
"is_emergency": "Is emergency",
- "contact_person_at_the_facility": "Contact person at the facility",
+ "contact_person_at_the_facility": "Contact person at the current facility",
"update_status_details": "Update Status/Details",
"shifting_details": "Shifting details",
"auto_generated_for_care": "Auto Generated for Care",
@@ -52,9 +57,6 @@
"covid_19_cat_gov": "Covid_19 Clinical Category as per Govt. of Kerala guideline (A/B/C)",
"district_program_management_supporting_unit": "District Program Management Supporting Unit",
"name_of_hospital": "Name of Hospital",
- "has_sari_severe_acute_respiratory_illness": "Has SARI (Severe Acute Respiratory illness)?",
- "contact_with_suspected_carrier": "Contact with suspected carrier",
- "contact_with_confirmed_carrier": "Contact with confirmed carrier",
"passport_number": "Passport Number",
"test_type": "Test Type",
"medical_worker": "Medical Worker",
diff --git a/src/Locale/en/index.js b/src/Locale/en/index.js
index b77b0a9f041..8bd6fca2285 100644
--- a/src/Locale/en/index.js
+++ b/src/Locale/en/index.js
@@ -1,4 +1,5 @@
import Auth from "./Auth.json";
+import Asset from "./Asset.json";
import Common from "./Common.json";
import Consultation from "./Consultation.json";
import Entities from "./Entities.json";
@@ -9,10 +10,13 @@ import Shifting from "./Shifting.json";
import Notifications from "./Notifications.json";
import ExternalResult from "./ExternalResult.json";
import CoverImageEdit from "./CoverImageEdit.json";
+import Resource from "./Resource.json";
import SortOptions from "./SortOptions.json";
+import Medicine from "./Medicine.json";
export default {
...Auth,
+ ...Asset,
...Common,
...Consultation,
...CoverImageEdit,
@@ -21,7 +25,9 @@ export default {
...ExternalResult,
...Facility,
...Hub,
+ ...Medicine,
...Notifications,
+ ...Resource,
...Shifting,
SortOptions,
};
diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx
index ffe68356d8e..3fe8d7c0149 100644
--- a/src/Redux/actions.tsx
+++ b/src/Redux/actions.tsx
@@ -1,4 +1,8 @@
import { HCXClaimModel, HCXPolicyModel } from "../Components/HCX/models";
+import {
+ MedicineAdministrationRecord,
+ Prescription,
+} from "../Components/Medicine/models";
import { fireRequest, fireRequestForFiles } from "./fireRequest";
export const getConfig = () => {
@@ -580,8 +584,21 @@ export const deleteLastInventoryLog = (params: object) => {
return fireRequest("deleteLastInventoryLog", [], {}, params);
};
-export const discharge = (params: object, pathParams: object) => {
- return fireRequest("discharge", [], params, pathParams);
+export const generateDischargeSummary = (pathParams: object) => {
+ return fireRequest("dischargeSummaryGenerate", [], {}, pathParams);
+};
+export const previewDischargeSummary = (pathParams: object) => {
+ return fireRequest(
+ "dischargeSummaryPreview",
+ [],
+ {},
+ pathParams,
+ undefined,
+ true
+ );
+};
+export const emailDischargeSummary = (params: object, pathParams: object) => {
+ return fireRequest("dischargeSummaryEmail", [], params, pathParams);
};
export const dischargePatient = (params: object, pathParams: object) => {
return fireRequest("dischargePatient", [], params, pathParams);
@@ -832,6 +849,62 @@ export const getAssetTransaction = (id: string) =>
export const listPMJYPackages = (query?: string) =>
fireRequest("listPMJYPackages", [], { query });
+/** Prescription related actions */
+export const PrescriptionActions = (consultation_external_id: string) => {
+ const pathParams = { consultation_external_id };
+
+ return {
+ list: (query?: Partial) => {
+ let altKey;
+ if (query?.is_prn !== undefined) {
+ altKey = query?.is_prn
+ ? "listPRNPrescriptions"
+ : "listNormalPrescriptions";
+ }
+ return fireRequest("listPrescriptions", [], query, pathParams, altKey);
+ },
+
+ create: (obj: Prescription) =>
+ fireRequest("createPrescription", [], obj, pathParams),
+
+ listAdministrations: (query?: object) =>
+ fireRequest("listAdministrations", [], query, pathParams),
+
+ getAdministration: (external_id: string) =>
+ fireRequest("getAdministration", [], {}, { ...pathParams, external_id }),
+
+ /** Returns actions specific to a prescription */
+ prescription(external_id: string) {
+ const pathParams = { consultation_external_id, external_id };
+
+ return {
+ /** Read a specific prescription of a consultation */
+ get: () => fireRequest("getPrescription", [], {}, pathParams),
+
+ /** Administer a prescription */
+ administer: (obj: MedicineAdministrationRecord) =>
+ fireRequest(
+ "administerPrescription",
+ [],
+ obj,
+ pathParams,
+ `administer-medicine-${external_id}`
+ ),
+
+ /** Discontinue a prescription */
+ discontinue: (discontinued_reason: string | undefined) =>
+ fireRequest(
+ "discontinuePrescription",
+ [],
+ { discontinued_reason },
+ pathParams,
+ `discontinue-medicine-${external_id}`
+ ),
+ };
+ },
+ };
+};
+
// HCX Actions
export const HCXActions = {
diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx
index 1278a51ea6d..54276885d0d 100644
--- a/src/Redux/api.tsx
+++ b/src/Redux/api.tsx
@@ -588,12 +588,20 @@ const routes: Routes = {
path: "/api/v1/facility/{facility_external_id}/inventory/delete_last/?item={id}",
method: "DELETE",
},
- discharge: {
- path: "/api/v1/patient/{external_id}/discharge_summary/",
+ dischargeSummaryGenerate: {
+ path: "/api/v1/consultation/{external_id}/generate_discharge_summary/",
+ method: "POST",
+ },
+ dischargeSummaryPreview: {
+ path: "/api/v1/consultation/{external_id}/preview_discharge_summary",
+ method: "GET",
+ },
+ dischargeSummaryEmail: {
+ path: "/api/v1/consultation/{external_id}/email_discharge_summary/",
method: "POST",
},
dischargePatient: {
- path: "/api/v1/patient/{id}/discharge_patient/",
+ path: "/api/v1/consultation/{id}/discharge_patient/",
method: "POST",
},
//Profile
@@ -799,6 +807,43 @@ const routes: Routes = {
method: "GET",
},
+ // Prescription endpoints
+
+ listPrescriptions: {
+ path: "/api/v1/consultation/{consultation_external_id}/prescriptions/",
+ method: "GET",
+ },
+
+ createPrescription: {
+ path: "/api/v1/consultation/{consultation_external_id}/prescriptions/",
+ method: "POST",
+ },
+
+ listAdministrations: {
+ path: "/api/v1/consultation/{consultation_external_id}/prescription_administration/",
+ method: "GET",
+ },
+
+ getAdministration: {
+ path: "/api/v1/consultation/{consultation_external_id}/prescription_administration/{external_id}/",
+ method: "GET",
+ },
+
+ getPrescription: {
+ path: "/api/v1/consultation/{consultation_external_id}/prescriptions/{external_id}/",
+ method: "GET",
+ },
+
+ administerPrescription: {
+ path: "/api/v1/consultation/{consultation_external_id}/prescriptions/{external_id}/administer/",
+ method: "POST",
+ },
+
+ discontinuePrescription: {
+ path: "/api/v1/consultation/{consultation_external_id}/prescriptions/{external_id}/discontinue/",
+ method: "POST",
+ },
+
// HCX Endpoints
listPMJYPackages: {
diff --git a/src/Router/AppRouter.tsx b/src/Router/AppRouter.tsx
index 6bd81da9c0f..c79b5a95974 100644
--- a/src/Router/AppRouter.tsx
+++ b/src/Router/AppRouter.tsx
@@ -71,6 +71,7 @@ import FacilityCNS from "../Components/Facility/FacilityCNS";
import ConsultationClaims from "../Components/Facility/ConsultationClaims";
import { handleSignOut } from "../Utils/utils";
import SessionExpired from "../Components/ErrorPages/SessionExpired";
+import ManagePrescriptions from "../Components/Medicine/ManagePrescriptions";
export default function AppRouter() {
const { static_black_logo, enable_hcx } = useConfig();
@@ -177,6 +178,8 @@ export default function AppRouter() {
unspecified={true}
/>
),
+ "/facility/:facilityId/patient/:patientId/consultation/:consultationId/prescriptions":
+ (path: any) => ,
"/facility/:facilityId/patient/:patientId/consultation/:id/investigation":
({ facilityId, patientId, id }: any) => (
{
+ const { t } = useTranslation();
const { createAudioBlob, confirmAudioBlobExists, reset, setResetRecording } =
props;
const [
@@ -40,8 +43,8 @@ export const VoiceRecorder = (props: any) => {
<>
-
- Recording...
+
+ {t("recording") + "..."}
{
@@ -49,8 +52,8 @@ export const VoiceRecorder = (props: any) => {
confirmAudioBlobExists();
}}
>
-
- Stop
+
+ {t("stop")}
@@ -61,9 +64,12 @@ export const VoiceRecorder = (props: any) => {
) : (
{!audioURL && (
-
-
- Record
+
+
+ {t("record")}
)}
diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts
index 3714f8c33e1..35ad46408b1 100644
--- a/src/Utils/utils.ts
+++ b/src/Utils/utils.ts
@@ -79,6 +79,11 @@ export const relativeDate = (date: string | Date) => {
return `${momentDate.fromNow()} at ${momentDate.format("hh:mm A")}`;
};
+export const relativeTime = (time: string | Date) => {
+ const momentTime = moment(time);
+ return `${momentTime.fromNow()}`;
+};
+
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
export const handleSignOut = (forceReload: boolean) => {